Skip to content

Commit

Permalink
Debugger improvements #2 (#810)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jither committed Dec 3, 2020
1 parent cccd41e commit 3ffb2dc
Show file tree
Hide file tree
Showing 13 changed files with 1,185 additions and 101 deletions.
170 changes: 170 additions & 0 deletions Jint.Tests/Runtime/Debugger/BreakPointTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
using Esprima;
using Jint.Runtime.Debugger;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Xunit;

namespace Jint.Tests.Runtime.Debugger
{
public class BreakPointTests
{
[Fact]
public void BreakPointBreaksAtPosition()
{
string script = @"let x = 1, y = 2;
if (x === 1)
{
x++; y *= 2;
}";

var engine = new Engine(options => options.DebugMode());

bool didBreak = false;
engine.Break += (sender, info) =>
{
Assert.Equal(4, info.CurrentStatement.Location.Start.Line);
Assert.Equal(5, info.CurrentStatement.Location.Start.Column);
didBreak = true;
return StepMode.None;
};

engine.BreakPoints.Add(new BreakPoint(4, 5));
engine.Execute(script);
Assert.True(didBreak);
}

[Fact]
public void BreakPointBreaksInCorrectSource()
{
string script1 = @"let x = 1, y = 2;
if (x === 1)
{
x++; y *= 2;
}";

string script2 = @"function test(x)
{
return x + 2;
}";

string script3 = @"const z = 3;
test(z);";

var engine = new Engine(options => { options.DebugMode(); });

engine.BreakPoints.Add(new BreakPoint("script2", 3, 0));

bool didBreak = false;
engine.Break += (sender, info) =>
{
Assert.Equal("script2", info.CurrentStatement.Location.Source);
Assert.Equal(3, info.CurrentStatement.Location.Start.Line);
Assert.Equal(0, info.CurrentStatement.Location.Start.Column);
didBreak = true;
return StepMode.None;
};

// We need to specify the source to the parser.
// And we need locations too (Jint specifies that in its default options)
engine.Execute(script1, new ParserOptions("script1") { Loc = true });
Assert.False(didBreak);

engine.Execute(script2, new ParserOptions("script2") { Loc = true });
Assert.False(didBreak);

// Note that it's actually script3 that executes the function in script2
// and triggers the breakpoint
engine.Execute(script3, new ParserOptions("script3") { Loc = true });
Assert.True(didBreak);
}

[Fact]
public void DebuggerStatementTriggersBreak()
{
string script = @"'dummy';
debugger;
'dummy';";

var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
return StepMode.None;
};

engine.Execute(script);

Assert.True(didBreak);
}

[Fact(Skip = "Non-source breakpoint is triggered before Statement, while debugger statement is now triggered by ExecuteInternal")]
public void DebuggerStatementAndBreakpointTriggerSingleBreak()
{
string script = @"'dummy';
debugger;
'dummy';";

var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

engine.BreakPoints.Add(new BreakPoint(2, 0));

int breakTriggered = 0;
engine.Break += (sender, info) =>
{
breakTriggered++;
return StepMode.None;
};

engine.Execute(script);

Assert.Equal(1, breakTriggered);
}

[Fact]
public void BreakpointOverridesStepOut()
{
string script = @"function test()
{
'dummy';
'source';
'dummy';
'target';
}
test();";

var engine = new Engine(options => options.DebugMode());

engine.BreakPoints.Add(new BreakPoint(4, 0));
engine.BreakPoints.Add(new BreakPoint(6, 0));

int step = 0;
engine.Break += (sender, info) =>
{
step++;
switch (step)
{
case 1:
return StepMode.Out;
case 2:
Assert.True(info.ReachedLiteral("target"));
break;
}
return StepMode.None;
};

engine.Execute(script);

Assert.Equal(2, step);
}
}
}
228 changes: 228 additions & 0 deletions Jint.Tests/Runtime/Debugger/CallStackTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
using Jint.Runtime.Debugger;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace Jint.Tests.Runtime.Debugger
{
public class CallStackTests
{
[Fact]
public void NamesRegularFunction()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("regularFunction", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"function regularFunction() { debugger; }
regularFunction()");

Assert.True(didBreak);
}

[Fact]
public void NamesFunctionExpression()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("functionExpression", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"const functionExpression = function() { debugger; }
functionExpression()");

Assert.True(didBreak);
}

[Fact]
public void NamesNamedFunctionExpression()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("namedFunction", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"const functionExpression = function namedFunction() { debugger; }
functionExpression()");

Assert.True(didBreak);
}

[Fact]
public void NamesArrowFunction()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("arrowFunction", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"const arrowFunction = () => { debugger; }
arrowFunction()");

Assert.True(didBreak);
}

[Fact]
public void NamesNewFunction()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
// Ideally, this should be "(anonymous)", but FunctionConstructor sets the "anonymous" name.
Assert.Equal("anonymous", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"const newFunction = new Function('debugger;');
newFunction()");

Assert.True(didBreak);
}

[Fact]
public void NamesMemberFunction()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("memberFunction", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"const obj = { memberFunction() { debugger; } };
obj.memberFunction()");

Assert.True(didBreak);
}

[Fact]
public void NamesAnonymousFunction()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("(anonymous)", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"(function()
{
debugger;
}());");

Assert.True(didBreak);
}

[Fact(Skip = "Debugger has no accessor awareness yet")]
public void NamesGetAccessor()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("get accessor", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"
const obj = {
get accessor()
{
debugger;
return 'test';
}
};
const x = obj.accessor;");

Assert.True(didBreak);
}

[Fact(Skip = "Debugger has no accessor awareness yet")]
public void NamesSetAccessor()
{
var engine = new Engine(options => options
.DebugMode()
.DebuggerStatementHandling(DebuggerStatementHandling.Script));

bool didBreak = false;
engine.Break += (sender, info) =>
{
didBreak = true;
Assert.Equal("set accessor", info.CallStack.Peek());
return StepMode.None;
};

engine.Execute(
@"
const obj = {
set accessor(value)
{
debugger;
this.value = value;
}
};
obj.accessor = 42;");

Assert.True(didBreak);
}
}
}

0 comments on commit 3ffb2dc

Please sign in to comment.