123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- using Jint.Native;
- using Jint.Runtime.Debugger;
- namespace Jint.Tests.Runtime.Debugger;
- public class ScopeTests
- {
- private static JsValue AssertOnlyScopeContains(DebugScopes scopes, string name, DebugScopeType scopeType)
- {
- var containingScope = Assert.Single(scopes, s => s.ScopeType == scopeType && s.BindingNames.Contains(name));
- Assert.DoesNotContain(scopes, s => s != containingScope && s.BindingNames.Contains(name));
- return containingScope.GetBindingValue(name);
- }
- private static void AssertScope(DebugScope actual, DebugScopeType expectedType, params string[] expectedBindingNames)
- {
- Assert.Equal(expectedType, actual.ScopeType);
- // Global scope will have a number of intrinsic bindings that are outside the scope [no pun] of these tests
- if (actual.ScopeType != DebugScopeType.Global)
- {
- Assert.Equal(expectedBindingNames.Length, actual.BindingNames.Count);
- }
- foreach (string expectedName in expectedBindingNames)
- {
- Assert.Contains(expectedName, actual.BindingNames);
- }
- }
- [Fact]
- public void AllowsInspectionOfUninitializedGlobalBindings()
- {
- string script = @"
- debugger;
- const globalConstant = 'test';
- let globalLet = 'test';
- ";
- TestHelpers.TestAtBreak(script, info =>
- {
- // Uninitialized global block scoped ("script scoped") bindings return null (and, just as importantly, don't throw):
- Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalConstant"));
- Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalLet"));
- });
- }
- [Fact]
- public void AllowsInspectionOfUninitializedBlockBindings()
- {
- string script = @"
- function test()
- {
- debugger;
- const globalConstant = 'test';
- let globalLet = 'test';
- }
- test();
- ";
- TestHelpers.TestAtBreak(script, info =>
- {
- // Uninitialized block scoped bindings return null (and, just as importantly, don't throw):
- Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalConstant"));
- Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalLet"));
- });
- }
- [Fact]
- public void ScriptScopeIncludesGlobalConst()
- {
- string script = @"
- const globalConstant = 'test';
- debugger;
- ";
- TestHelpers.TestAtBreak(script, info =>
- {
- var value = AssertOnlyScopeContains(info.CurrentScopeChain, "globalConstant", DebugScopeType.Script);
- Assert.Equal("test", value.AsString());
- });
- }
- [Fact]
- public void ScriptScopeIncludesGlobalLet()
- {
- string script = @"
- let globalLet = 'test';
- debugger;";
- TestHelpers.TestAtBreak(script, info =>
- {
- var value = AssertOnlyScopeContains(info.CurrentScopeChain, "globalLet", DebugScopeType.Script);
- Assert.Equal("test", value.AsString());
- });
- }
- [Fact]
- public void GlobalScopeIncludesGlobalVar()
- {
- string script = @"
- var globalVar = 'test';
- debugger;";
- TestHelpers.TestAtBreak(script, info =>
- {
- var value = AssertOnlyScopeContains(info.CurrentScopeChain, "globalVar", DebugScopeType.Global);
- Assert.Equal("test", value.AsString());
- });
- }
- [Fact]
- public void TopLevelBlockScopeIsIdentified()
- {
- string script = @"
- function test()
- {
- const localConst = 'test';
- debugger;
- }
- test();";
- TestHelpers.TestAtBreak(script, info =>
- {
- Assert.Equal(3, info.CurrentScopeChain.Count);
- Assert.Equal(DebugScopeType.Block, info.CurrentScopeChain[0].ScopeType);
- Assert.True(info.CurrentScopeChain[0].IsTopLevel);
- });
- }
- [Fact]
- public void NonTopLevelBlockScopeIsIdentified()
- {
- string script = @"
- function test()
- {
- {
- const localConst = 'test';
- debugger;
- }
- }
- test();";
- TestHelpers.TestAtBreak(script, info =>
- {
- // We only have 3 scopes, because the function top level block scope is empty.
- Assert.Equal(3, info.CurrentScopeChain.Count);
- Assert.Equal(DebugScopeType.Block, info.CurrentScopeChain[0].ScopeType);
- Assert.False(info.CurrentScopeChain[0].IsTopLevel);
- });
- }
- [Fact]
- public void BlockScopeIncludesLocalConst()
- {
- string script = @"
- function test()
- {
- {
- const localConst = 'test';
- debugger;
- }
- }
- test();";
- TestHelpers.TestAtBreak(script, info =>
- {
- var value = AssertOnlyScopeContains(info.CurrentScopeChain, "localConst", DebugScopeType.Block);
- Assert.Equal("test", value.AsString());
- });
- }
- [Fact]
- public void BlockScopeIncludesLocalLet()
- {
- string script = @"
- function test()
- {
- {
- let localLet = 'test';
- debugger;
- }
- }
- test();";
- TestHelpers.TestAtBreak(script, info =>
- {
- var value = AssertOnlyScopeContains(info.CurrentScopeChain, "localLet", DebugScopeType.Block);
- Assert.Equal("test", value.AsString());
- });
- }
- [Fact]
- public void LocalScopeIncludesLocalVar()
- {
- string script = @"
- function test()
- {
- var localVar = 'test';
- debugger;
- }
- test();";
- TestHelpers.TestAtBreak(script, info =>
- {
- AssertOnlyScopeContains(info.CurrentScopeChain, "localVar", DebugScopeType.Local);
- });
- }
- [Fact]
- public void LocalScopeIncludesBlockVar()
- {
- string script = @"
- function test()
- {
- debugger;
- {
- var localVar = 'test';
- }
- }
- test();";
- TestHelpers.TestAtBreak(script, info =>
- {
- AssertOnlyScopeContains(info.CurrentScopeChain, "localVar", DebugScopeType.Local);
- });
- }
- [Fact]
- public void BlockScopedConstIsVisibleInsideBlock()
- {
- string script = @"
- 'dummy statement';
- {
- const blockConst = 'block';
- debugger; // const isn't initialized until declaration
- }";
- TestHelpers.TestAtBreak(script, info =>
- {
- AssertOnlyScopeContains(info.CurrentScopeChain, "blockConst", DebugScopeType.Block);
- });
- }
- [Fact]
- public void BlockScopedLetIsVisibleInsideBlock()
- {
- string script = @"
- 'dummy statement';
- {
- let blockLet = 'block';
- debugger; // let isn't initialized until declaration
- }";
- TestHelpers.TestAtBreak(script, info =>
- {
- AssertOnlyScopeContains(info.CurrentScopeChain, "blockLet", DebugScopeType.Block);
- });
- }
- [Fact]
- public void HasCorrectScopeChainForFunction()
- {
- string script = @"
- function add(a, b)
- {
- debugger;
- return a + b;
- }
- const x = 1;
- const y = 2;
- const z = add(x, y);";
- TestHelpers.TestAtBreak(script, info =>
- {
- Assert.Collection(info.CurrentScopeChain,
- scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
- scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"),
- scope => AssertScope(scope, DebugScopeType.Global, "add"));
- });
- }
- [Fact]
- public void HasCorrectScopeChainForNestedFunction()
- {
- string script = @"
- function add(a, b)
- {
- function power(a)
- {
- debugger;
- return a * a;
- }
- return power(a) + b;
- }
- const x = 1;
- const y = 2;
- const z = add(x, y);";
- TestHelpers.TestAtBreak(script, info =>
- {
- Assert.Collection(info.CurrentScopeChain,
- scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a"),
- // a, arguments shadowed by local - but still exist in this scope
- scope => AssertScope(scope, DebugScopeType.Closure, "a", "arguments", "b", "power"),
- scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"),
- scope => AssertScope(scope, DebugScopeType.Global, "add"));
- });
- }
- [Fact]
- public void HasCorrectScopeChainForBlock()
- {
- string script = @"
- function add(a, b)
- {
- if (a > 0)
- {
- const y = b / a;
- debugger;
- }
- return a + b;
- }
- const x = 1;
- const y = 2;
- const z = add(x, y);";
- TestHelpers.TestAtBreak(script, info =>
- {
- Assert.Collection(info.CurrentScopeChain,
- scope => AssertScope(scope, DebugScopeType.Block, "y"),
- scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
- scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"), // y is shadowed, but still in the scope
- scope => AssertScope(scope, DebugScopeType.Global, "add"));
- });
- }
- [Fact]
- public void HasCorrectScopeChainForModule()
- {
- string imported = @"
- function add(a, b)
- {
- debugger;
- return a + b;
- }
-
- export { add };";
- string main = @"
- import { add } from 'imported-module';
- const x = 1;
- const y = 2;
- add(x, y);";
- TestHelpers.TestAtBreak(engine =>
- {
- engine.Modules.Add("imported-module", imported);
- engine.Modules.Add("main", main);
- engine.Modules.Import("main");
- },
- info =>
- {
- Assert.Collection(info.CurrentScopeChain,
- scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
- scope => AssertScope(scope, DebugScopeType.Module, "add"),
- scope => AssertScope(scope, DebugScopeType.Global));
- });
- }
- [Fact]
- public void HasCorrectScopeChainForNestedBlock()
- {
- string script = @"
- function add(a, b)
- {
- if (a > 0)
- {
- const y = b / a;
- if (y > 0)
- {
- const x = b / y;
- debugger;
- }
- }
- return a + b;
- }
- const x = 1;
- const y = 2;
- const z = add(x, y);";
- TestHelpers.TestAtBreak(script, info =>
- {
- Assert.Collection(info.CurrentScopeChain,
- scope => AssertScope(scope, DebugScopeType.Block, "x"),
- scope => AssertScope(scope, DebugScopeType.Block, "y"),
- scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
- scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"), // x, y are shadowed, but still in the scope
- scope => AssertScope(scope, DebugScopeType.Global, "add"));
- });
- }
- [Fact]
- public void HasCorrectScopeChainForCatch()
- {
- string script = @"
- function func()
- {
- let a = 1;
- try
- {
- throw new Error('test');
- }
- catch (error)
- {
- debugger;
- }
- }
- func();";
- TestHelpers.TestAtBreak(script, info =>
- {
- Assert.Collection(info.CurrentScopeChain,
- scope => AssertScope(scope, DebugScopeType.Catch, "error"),
- scope => AssertScope(scope, DebugScopeType.Block, "a"),
- scope => AssertScope(scope, DebugScopeType.Local, "arguments"),
- scope => AssertScope(scope, DebugScopeType.Global, "func"));
- });
- }
- [Fact]
- public void HasCorrectScopeChainForWith()
- {
- string script = @"
- const obj = { a: 2, b: 4 };
- with (obj)
- {
- const x = a;
- debugger;
- };";
- TestHelpers.TestAtBreak(script, info =>
- {
- Assert.Collection(info.CurrentScopeChain,
- scope => AssertScope(scope, DebugScopeType.Block, "x"),
- scope => AssertScope(scope, DebugScopeType.With, "a", "b"),
- scope => AssertScope(scope, DebugScopeType.Script, "obj"),
- scope => AssertScope(scope, DebugScopeType.Global));
- });
- }
- [Fact]
- public void ScopeChainIncludesNonEmptyScopes()
- {
- string script = @"
- const x = 2;
- if (x > 0)
- {
- const y = x;
- if (x > 1)
- {
- const z = x;
- debugger;
- }
- }";
- TestHelpers.TestAtBreak(script, info =>
- {
- Assert.Collection(info.CurrentScopeChain,
- scope => AssertScope(scope, DebugScopeType.Block, "z"),
- scope => AssertScope(scope, DebugScopeType.Block, "y"),
- scope => AssertScope(scope, DebugScopeType.Script, "x"),
- scope => AssertScope(scope, DebugScopeType.Global));
- });
- }
- [Fact]
- public void ScopeChainExcludesEmptyScopes()
- {
- string script = @"
- const x = 2;
- if (x > 0)
- {
- if (x > 1)
- {
- const z = x;
- debugger;
- }
- }";
- TestHelpers.TestAtBreak(script, info =>
- {
- Assert.Collection(info.CurrentScopeChain,
- scope => AssertScope(scope, DebugScopeType.Block, "z"),
- scope => AssertScope(scope, DebugScopeType.Script, "x"),
- scope => AssertScope(scope, DebugScopeType.Global));
- });
- }
- [Fact]
- public void ResolvesScopeChainsUpTheCallStack()
- {
- string script = @"
- const x = 1;
- function foo(a, c)
- {
- debugger;
- }
-
- function bar(b)
- {
- foo(b, 2);
- }
- bar(x);";
- TestHelpers.TestAtBreak(script, info =>
- {
- Assert.Collection(info.CallStack,
- frame => Assert.Collection(frame.ScopeChain,
- // in foo()
- scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "c"),
- scope => AssertScope(scope, DebugScopeType.Script, "x"),
- scope => AssertScope(scope, DebugScopeType.Global, "foo", "bar")
- ),
- frame => Assert.Collection(frame.ScopeChain,
- // in bar()
- scope => AssertScope(scope, DebugScopeType.Local, "arguments", "b"),
- scope => AssertScope(scope, DebugScopeType.Script, "x"),
- scope => AssertScope(scope, DebugScopeType.Global, "foo", "bar")
- ),
- frame => Assert.Collection(frame.ScopeChain,
- // in global
- scope => AssertScope(scope, DebugScopeType.Script, "x"),
- scope => AssertScope(scope, DebugScopeType.Global, "foo", "bar")
- )
- );
- });
- }
- [Fact]
- public void InspectsModuleScopedBindings()
- {
- string main = @"const x = 1; debugger;";
- TestHelpers.TestAtBreak(engine =>
- {
- engine.Modules.Add("main", main);
- engine.Modules.Import("main");
- },
- info =>
- {
- // No need for imports - main module is module scoped too, duh.
- var value = AssertOnlyScopeContains(info.CurrentScopeChain, "x", DebugScopeType.Module);
- Assert.Equal(1, value.AsInteger());
- });
- }
- }
|