diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..34515c506681fe427b49a650d40ce35c296a1139
--- /dev/null
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/tck/FastRDebugTest.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 2016, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package com.oracle.truffle.r.test.tck;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.LinkedList;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.oracle.truffle.api.debug.Debugger;
+import com.oracle.truffle.api.debug.ExecutionEvent;
+import com.oracle.truffle.api.debug.SuspendedEvent;
+import com.oracle.truffle.api.frame.FrameSlot;
+import com.oracle.truffle.api.frame.MaterializedFrame;
+import com.oracle.truffle.api.source.LineLocation;
+import com.oracle.truffle.api.source.Source;
+import com.oracle.truffle.api.vm.EventConsumer;
+import com.oracle.truffle.api.vm.PolyglotEngine;
+import com.oracle.truffle.api.vm.PolyglotEngine.Value;
+import com.oracle.truffle.r.runtime.data.RPromise.EagerPromise;
+
+public class FastRDebugTest {
+    private Debugger debugger;
+    private final LinkedList<Runnable> run = new LinkedList<>();
+    private SuspendedEvent suspendedEvent;
+    private Throwable ex;
+    private ExecutionEvent executionEvent;
+    protected PolyglotEngine engine;
+    protected final ByteArrayOutputStream out = new ByteArrayOutputStream();
+    protected final ByteArrayOutputStream err = new ByteArrayOutputStream();
+
+    @Before
+    public void before() {
+        suspendedEvent = null;
+        executionEvent = null;
+        engine = PolyglotEngine.newBuilder().setOut(out).setErr(err).onEvent(new EventConsumer<ExecutionEvent>(ExecutionEvent.class) {
+            @Override
+            protected void on(ExecutionEvent event) {
+                executionEvent = event;
+                debugger = executionEvent.getDebugger();
+                performWork();
+                executionEvent = null;
+            }
+        }).onEvent(new EventConsumer<SuspendedEvent>(SuspendedEvent.class) {
+            @Override
+            protected void on(SuspendedEvent event) {
+                suspendedEvent = event;
+                performWork();
+                suspendedEvent = null;
+            }
+        }).build();
+        run.clear();
+    }
+
+    @After
+    public void dispose() {
+        if (engine != null) {
+            engine.dispose();
+        }
+    }
+
+    private static Source createFactorial() {
+        return Source.fromText("main <- function() {\n" +
+                        "  res = fac(2)\n" +
+                        "  res\n" +
+                        "}\n" +
+                        "fac <- function(n) {\n" +
+                        "    if (n <= 1) {\n" +
+                        "        1\n" +
+                        "    } else {\n" +
+                        "        nMinusOne = n - 1\n" +
+                        "        nMOFact = Recall(n - 1)\n" +
+                        "        res = n * nMOFact\n" +
+                        "        res\n" +
+                        "    }\n" +
+                        "}\n",
+                        "factorial.r").withMimeType(
+                                        "application/x-r");
+    }
+
+    protected final String getOut() {
+        return new String(out.toByteArray());
+    }
+
+    protected final String getErr() {
+        try {
+            err.flush();
+        } catch (IOException e) {
+        }
+        return new String(err.toByteArray());
+    }
+
+    @Test
+    public void testBreakpoint() throws Throwable {
+        final Source factorial = createFactorial();
+
+        run.addLast(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    assertNull(suspendedEvent);
+                    assertNotNull(executionEvent);
+                    LineLocation nMinusOne = factorial.createLineLocation(9);
+                    debugger.setLineBreakpoint(0, nMinusOne, false);
+                    executionEvent.prepareContinue();
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+        engine.eval(factorial);
+        assertExecutedOK();
+
+        run.addLast(new Runnable() {
+            @Override
+            public void run() {
+                // the breakpoint should hit instead
+            }
+        });
+        assertLocation(9, "nMinusOne = n - 1",
+                        "n", 2.0);
+        continueExecution();
+
+        final Source evalSrc = Source.fromText("main()\n", "debugtest.r").withMimeType("application/x-r");
+        final Value value = engine.eval(evalSrc);
+        assertExecutedOK();
+        Assert.assertEquals("[1] 2\n", getOut());
+        final Number n = value.as(Number.class);
+        assertNotNull(n);
+        assertEquals("Factorial computed OK", 2, n.intValue());
+    }
+
+    @Test
+    public void stepInStepOver() throws Throwable {
+        final Source factorial = createFactorial();
+        engine.eval(factorial);
+
+        // @formatter:on
+        run.addLast(new Runnable() {
+            @Override
+            public void run() {
+                assertNull(suspendedEvent);
+                assertNotNull(executionEvent);
+                executionEvent.prepareStepInto();
+            }
+        });
+
+        assertLocation(2, "res = fac(2)");
+        stepInto(2);
+        assertLocation(9, "nMinusOne = n - 1",
+                        "n", 2.0);
+        stepOver(1);
+        assertLocation(10, "nMOFact = Recall(n - 1)",
+                        "n", 2.0,
+                        "nMinusOne", 1.0);
+        stepOver(1);
+        assertLocation(11, "res = n * nMOFact",
+                        "n", 2.0, "nMinusOne", 1.0,
+                        "nMOFact", 1.0);
+        stepOver(1);
+        assertLocation(12, "res",
+                        "n", 2.0,
+                        "nMinusOne", 1.0,
+                        "nMOFact", 1.0,
+                        "res", 2.0);
+        stepOver(1);
+        assertLocation(2, "fac(2)");
+        stepOver(1);
+        assertLocation(3, "res", "res", 2.0);
+        stepOut();
+
+        final Source evalSource = Source.fromText("main()\n", "evaltest.r").withMimeType("application/x-r");
+        final Value value = engine.eval(evalSource);
+        assertExecutedOK();
+        Assert.assertEquals("[1] 2\n", getOut());
+        final Number n = value.as(Number.class);
+        assertNotNull(n);
+        assertEquals("Factorial computed OK", 2, n.intValue());
+    }
+
+    private void performWork() {
+        try {
+            if (ex == null && !run.isEmpty()) {
+                Runnable c = run.removeFirst();
+                c.run();
+            }
+        } catch (Throwable e) {
+            ex = e;
+        }
+    }
+
+    private void stepOver(final int size) {
+        run.addLast(new Runnable() {
+            @Override
+            public void run() {
+                suspendedEvent.prepareStepOver(size);
+            }
+        });
+    }
+
+    private void stepOut() {
+        run.addLast(new Runnable() {
+            @Override
+            public void run() {
+                suspendedEvent.prepareStepOut();
+            }
+        });
+    }
+
+    private void continueExecution() {
+        run.addLast(new Runnable() {
+            @Override
+            public void run() {
+                suspendedEvent.prepareContinue();
+            }
+        });
+    }
+
+    private void stepInto(final int size) {
+        run.addLast(new Runnable() {
+            @Override
+            public void run() {
+                suspendedEvent.prepareStepInto(size);
+            }
+        });
+    }
+
+    private void assertLocation(final int line, final String code, final Object... expectedFrame) {
+        run.addLast(new Runnable() {
+            @Override
+            public void run() {
+                assertNotNull(suspendedEvent);
+                final int actualLine = suspendedEvent.getNode().getSourceSection().getLineLocation().getLineNumber();
+                Assert.assertEquals(line, actualLine);
+                final String actualCode = suspendedEvent.getNode().getSourceSection().getCode();
+                Assert.assertEquals(code, actualCode);
+                final MaterializedFrame frame = suspendedEvent.getFrame();
+
+                Assert.assertEquals(expectedFrame.length / 2, frame.getFrameDescriptor().getSize());
+
+                for (int i = 0; i < expectedFrame.length; i = i + 2) {
+                    final String expectedIdentifier = (String) expectedFrame[i];
+                    final FrameSlot slot = frame.getFrameDescriptor().findFrameSlot(expectedIdentifier);
+                    Assert.assertNotNull(slot);
+                    final Object expectedValue = expectedFrame[i + 1];
+                    final Object actualValue = getRValue(frame.getValue(slot));
+                    Assert.assertEquals(expectedValue, actualValue);
+                }
+                run.removeFirst().run();
+            }
+        });
+    }
+
+    Object getRValue(Object value) {
+        // This will only work in simple cases
+        if (value instanceof EagerPromise) {
+            return ((EagerPromise) value).getValue();
+        }
+        return value;
+    }
+
+    private void assertExecutedOK() throws Throwable {
+        Assert.assertTrue(getErr(), getErr().isEmpty());
+        if (ex != null) {
+            if (ex instanceof AssertionError) {
+                throw ex;
+            } else {
+                throw new AssertionError("Error during execution", ex);
+            }
+        }
+        assertTrue("Assuming all requests processed: " + run, run.isEmpty());
+    }
+}