internal void TestDebuggerSteps(
            NodeDebugger process,
            NodeThread thread,
            string filename,
            IEnumerable<TestStep> steps,
            ExceptionHitTreatment? defaultExceptionTreatment = null,
            ICollection<KeyValuePair<string, ExceptionHitTreatment>> exceptionTreatments = null,
            bool waitForExit = false
        ) {
            if (!Path.IsPathRooted(filename)) {
                filename = DebuggerTestPath + filename;
            }

            // Since Alpha does not support break on unhandled, and the commonly used Express module has handled SyntaxError exceptions,
            // for alpha we have SyntaxErrors set to BreakNever by default.  Here we set it to BreakAlways so unit tests can run
            // assuming BreakAlways is the default.            
            // TODO: Remove once exception treatment is updated for just my code support when it is added after Alpha
            process.SetExceptionTreatment(null, CollectExceptionTreatments(ExceptionHitTreatment.BreakAlways, "SyntaxError"));

            if (defaultExceptionTreatment != null || exceptionTreatments != null) {
                process.SetExceptionTreatment(defaultExceptionTreatment, exceptionTreatments);
            }

            Dictionary<Breakpoint, NodeBreakpoint> breakpoints = new Dictionary<Breakpoint, NodeBreakpoint>();

            AutoResetEvent entryPointHit = new AutoResetEvent(false);
            process.EntryPointHit += (sender, e) => {
                Console.WriteLine("EntryPointHit");
                Assert.AreEqual(e.Thread, thread);
                entryPointHit.Set();
            };

            AutoResetEvent breakpointBound = new AutoResetEvent(false);
            process.BreakpointBound += (sender, e) => {
                Console.WriteLine("BreakpointBound {0} {1}", e.BreakpointBinding.Position.FileName, e.BreakpointBinding.Position.Line);
                breakpointBound.Set();
            };

            AutoResetEvent breakpointUnbound = new AutoResetEvent(false);
            process.BreakpointUnbound += (sender, e) => {
                Console.WriteLine("BreakpointUnbound");
                breakpointUnbound.Set();
            };

            AutoResetEvent breakpointBindFailure = new AutoResetEvent(false);
            process.BreakpointBindFailure += (sender, e) => {
                Console.WriteLine("BreakpointBindFailure");
                breakpointBindFailure.Set();
            };

            AutoResetEvent breakpointHit = new AutoResetEvent(false);
            process.BreakpointHit += (sender, e) => {
                Console.WriteLine("BreakpointHit {0}", e.BreakpointBinding.Target.Line);
                Assert.AreEqual(e.Thread, thread);
                Assert.AreEqual(e.BreakpointBinding.Target.Line, thread.Frames.First().Line);
                breakpointHit.Set();
            };

            AutoResetEvent stepComplete = new AutoResetEvent(false);
            process.StepComplete += (sender, e) => {
                Console.WriteLine("StepComplete");
                Assert.AreEqual(e.Thread, thread);
                stepComplete.Set();
            };

            AutoResetEvent exceptionRaised = new AutoResetEvent(false);
            NodeException exception = null;
            process.ExceptionRaised += (sender, e) => {
                Console.WriteLine("ExceptionRaised");
                Assert.AreEqual(e.Thread, thread);
                exception = e.Exception;
                exceptionRaised.Set();
            };

            AutoResetEvent processExited = new AutoResetEvent(false);
            int exitCode = 0;
            process.ProcessExited += (sender, e) => {
                Console.WriteLine("ProcessExited {0}", e.ExitCode);
                exitCode = e.ExitCode;
                processExited.Set();
            };

            Console.WriteLine("-----------------------------------------");
            Console.WriteLine("Begin debugger step test");
            foreach (var step in steps) {
                Console.WriteLine("Step: {0}", step._action);
                Assert.IsFalse(
                    ((step._expectedEntryPointHit != null ? 1 : 0) +
                     (step._expectedBreakpointHit != null ? 1 : 0) +
                     (step._expectedStepComplete != null ? 1 : 0) +
                     (step._expectedExceptionRaised != null ? 1 : 0)) > 1);
                bool wait = false;
                NodeBreakpoint nodeBreakpoint;
                switch (step._action) {
                    case TestAction.None:
                        break;
                    case TestAction.Wait:
                        wait = true;
                        break;
                    case TestAction.ResumeThread:
                        thread.Resume();
                        wait = true;
                        break;
                    case TestAction.ResumeProcess:
                        process.Resume();
                        wait = true;
                        break;
                    case TestAction.StepOver:
                        thread.StepOver();
                        wait = true;
                        break;
                    case TestAction.StepInto:
                        thread.StepInto();
                        wait = true;
                        break;
                    case TestAction.StepOut:
                        thread.StepOut();
                        wait = true;
                        break;
                    case TestAction.AddBreakpoint:
                        string breakpointFileName = step._targetBreakpointFile;
                        if (breakpointFileName != null) {
                            if (!step._builtin && !Path.IsPathRooted(breakpointFileName)) {
                                breakpointFileName = DebuggerTestPath + breakpointFileName;
                            }
                        } else {
                            breakpointFileName = filename;
                        }
                        int breakpointLine = step._targetBreakpoint.Value;
                        int breakpointColumn = step._targetBreakpointColumn.HasValue ? step._targetBreakpointColumn.Value : 0;
                        Breakpoint breakpoint = new Breakpoint(breakpointFileName, breakpointLine, breakpointColumn);
                        Assert.IsFalse(breakpoints.TryGetValue(breakpoint, out nodeBreakpoint));
                        breakpoints[breakpoint] =
                            AddBreakPoint(
                                process,
                                breakpointFileName,
                                breakpointLine,
                                breakpointColumn,
                                step._enabled ?? true,
                                step._breakOn ?? new BreakOn(),
                                step._condition
                            );
                        if (step._expectFailure) {
                            AssertWaited(breakpointBindFailure);
                            AssertNotSet(breakpointBound);
                            breakpointBindFailure.Reset();
                        } else {
                            AssertWaited(breakpointBound);
                            AssertNotSet(breakpointBindFailure);
                            breakpointBound.Reset();
                        }
                        break;
                    case TestAction.RemoveBreakpoint:
                        breakpointFileName = step._targetBreakpointFile ?? filename;
                        breakpointLine = step._targetBreakpoint.Value;
                        breakpointColumn = step._targetBreakpointColumn.HasValue ? step._targetBreakpointColumn.Value : 0;
                        breakpoint = new Breakpoint(breakpointFileName, breakpointLine, breakpointColumn);
                        breakpoints[breakpoint].Remove().WaitAndUnwrapExceptions();
                        breakpoints.Remove(breakpoint);
                        AssertWaited(breakpointUnbound);
                        breakpointUnbound.Reset();
                        break;
                    case TestAction.UpdateBreakpoint:
                        breakpointFileName = step._targetBreakpointFile ?? filename;
                        breakpointLine = step._targetBreakpoint.Value;
                        breakpointColumn = step._targetBreakpointColumn.HasValue ? step._targetBreakpointColumn.Value : 0;
                        breakpoint = new Breakpoint(breakpointFileName, breakpointLine, breakpointColumn);
                        nodeBreakpoint = breakpoints[breakpoint];
                        foreach (var breakpointBinding in nodeBreakpoint.GetBindings()) {
                            if (step._hitCount != null) {
                                Assert.IsTrue(breakpointBinding.SetHitCountAsync(step._hitCount.Value).WaitAndUnwrapExceptions());
                            }
                            if (step._enabled != null) {
                                Assert.IsTrue(breakpointBinding.SetEnabledAsync(step._enabled.Value).WaitAndUnwrapExceptions());
                            }
                            if (step._breakOn != null) {
                                Assert.IsTrue(breakpointBinding.SetBreakOnAsync(step._breakOn.Value).WaitAndUnwrapExceptions());
                            }
                            if (step._condition != null) {
                                Assert.IsTrue(breakpointBinding.SetConditionAsync(step._condition).WaitAndUnwrapExceptions());
                            }
                        }
                        break;
                    case TestAction.KillProcess:
                        process.Terminate();
                        break;
                    case TestAction.Detach:
                        process.Detach();
                        break;
                }

                if (wait) {
                    if (step._expectedEntryPointHit != null) {
                        AssertWaited(entryPointHit);
                        AssertNotSet(breakpointHit);
                        AssertNotSet(stepComplete);
                        AssertNotSet(exceptionRaised);
                        Assert.IsNull(exception);
                        entryPointHit.Reset();
                    } else if (step._expectedBreakpointHit != null) {
                        if (step._expectReBind) {
                            AssertWaited(breakpointUnbound);
                            AssertWaited(breakpointBound);
                            breakpointUnbound.Reset();
                            breakpointBound.Reset();
                        }
                        AssertWaited(breakpointHit);
                        AssertNotSet(entryPointHit);
                        AssertNotSet(stepComplete);
                        AssertNotSet(exceptionRaised);
                        Assert.IsNull(exception);
                        breakpointHit.Reset();
                    } else if (step._expectedStepComplete != null) {
                        AssertWaited(stepComplete);
                        AssertNotSet(entryPointHit);
                        AssertNotSet(breakpointHit);
                        AssertNotSet(exceptionRaised);
                        Assert.IsNull(exception);
                        stepComplete.Reset();
                    } else if (step._expectedExceptionRaised != null) {
                        AssertWaited(exceptionRaised);
                        AssertNotSet(entryPointHit);
                        AssertNotSet(breakpointHit);
                        AssertNotSet(stepComplete);
                        exceptionRaised.Reset();
                    } else {
                        AssertNotSet(entryPointHit);
                        AssertNotSet(breakpointHit);
                        AssertNotSet(stepComplete);
                        AssertNotSet(exceptionRaised);
                        Assert.IsNull(exception);
                    }
                }

                if (step._expectedEntryPointHit != null) {
                    Assert.AreEqual(step._expectedEntryPointHit.Value, thread.Frames.First().Line);
                } else if (step._expectedBreakpointHit != null) {
                    Assert.AreEqual(step._expectedBreakpointHit.Value, thread.Frames.First().Line);
                } else if (step._expectedStepComplete != null) {
                    Assert.AreEqual(step._expectedStepComplete.Value, thread.Frames.First().Line);
                } else if (step._expectedExceptionRaised != null) {
                    Assert.AreEqual(step._expectedExceptionRaised.TypeName, exception.TypeName);
                    Assert.AreEqual(step._expectedExceptionRaised.Description, exception.Description);
                    if (step._expectedExceptionRaised.LineNo != null) {
                        Assert.AreEqual(step._expectedExceptionRaised.LineNo.Value, thread.Frames[0].Line);
                    }
                    exception = null;
                }
                var expectedBreakFile = step._expectedBreakFile;
                if (expectedBreakFile != null) {
                    if (!step._builtin && !Path.IsPathRooted(expectedBreakFile)) {
                        expectedBreakFile = DebuggerTestPath + expectedBreakFile;
                    }
                    Assert.AreEqual(expectedBreakFile, thread.Frames.First().FileName);
                }
                var expectedBreakFunction = step._expectedBreakFunction;
                if (expectedBreakFunction != null) {
                    Assert.AreEqual(expectedBreakFunction, thread.Frames.First().FunctionName);
                }

                if (step._expectedHitCount != null) {
                    string breakpointFileName = step._targetBreakpointFile ?? filename;
                    if (!step._builtin && !Path.IsPathRooted(breakpointFileName)) {
                        breakpointFileName = DebuggerTestPath + breakpointFileName;
                    }
                    int breakpointLine = step._targetBreakpoint.Value;
                    int breakpointColumn = step._targetBreakpointColumn.HasValue ? step._targetBreakpointColumn.Value : 0;
                    var breakpoint = new Breakpoint(breakpointFileName, breakpointLine, breakpointColumn);
                    nodeBreakpoint = breakpoints[breakpoint];
                    foreach (var breakpointBinding in nodeBreakpoint.GetBindings()) {
                        Assert.AreEqual(step._expectedHitCount.Value, breakpointBinding.GetHitCount());
                    }
                }

                if (step._validation != null) {
                    step._validation(process, thread);
                }

                if (step._expectedExitCode != null) {
                    AssertWaited(processExited);
                    Assert.AreEqual(step._expectedExitCode.Value, exitCode);
                }
            }

            if (waitForExit) {
                process.WaitForExit(10000);
            }

            AssertNotSet(entryPointHit);
            AssertNotSet(breakpointHit);
            AssertNotSet(stepComplete);
            AssertNotSet(exceptionRaised);
            Assert.IsNull(exception);
        }
        internal NodeDebugger DebugProcess(
            string filename,
            Action<NodeDebugger> onProcessCreated = null,
            Action<NodeDebugger, NodeThread> onLoadComplete = null,
            string interpreterOptions = null,
            NodeDebugOptions debugOptions = NodeDebugOptions.None,
            string cwd = null,
            string arguments = "",
            bool resumeOnProcessLoad = true
        ) {
            if (!Path.IsPathRooted(filename)) {
                filename = DebuggerTestPath + filename;
            }
            string fullPath = Path.GetFullPath(filename);
            string dir = cwd ?? Path.GetFullPath(Path.GetDirectoryName(filename));
            if (!String.IsNullOrEmpty(arguments)) {
                arguments = "\"" + fullPath + "\" " + arguments;
            } else {
                arguments = "\"" + fullPath + "\"";
            }

            // Load process
            AutoResetEvent processLoaded = new AutoResetEvent(false);
            Assert.IsNotNull(Nodejs.NodeExePath, "Node isn't installed");
            NodeDebugger process =
                new NodeDebugger(
                    Nodejs.NodeExePath,
                    arguments,
                    dir,
                    null,
                    interpreterOptions,
                    debugOptions,
                    null,
                    createNodeWindow: false);
            if (onProcessCreated != null) {
                onProcessCreated(process);
            }
            process.ProcessLoaded += (sender, args) => {
                // Invoke onLoadComplete delegate, if requested
                if (onLoadComplete != null) {
                    onLoadComplete(process, args.Thread);
                }
                processLoaded.Set();
            };
            process.Start();
            AssertWaited(processLoaded);

            // Resume, if requested
            if (resumeOnProcessLoad) {
                process.Resume();
            }

            return process;
        }
        internal NodeDebugger AttachToNodeProcess(
            Action<NodeDebugger> onProcessCreated = null,
            Action<NodeDebugger, NodeThread> onLoadComplete = null,
            string hostName = "localhost",
            ushort portNumber = 5858,
            int id = 0,
            bool resumeOnProcessLoad = false) {
            // Load process
            AutoResetEvent processLoaded = new AutoResetEvent(false);
            var process = new NodeDebugger(new UriBuilder { Scheme = "tcp", Host = hostName, Port = portNumber }.Uri, id);
            if (onProcessCreated != null) {
                onProcessCreated(process);
            }
            process.ProcessLoaded += (sender, args) => {
                // Invoke onLoadComplete delegate, if requested
                if (onLoadComplete != null) {
                    onLoadComplete(process, args.Thread);
                }
                processLoaded.Set();
            };
            process.StartListening();
            AssertWaited(processLoaded);

            // Resume, if requested
            if (resumeOnProcessLoad) {
                process.Resume();
            }

            return process;
        }