ScopeTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. using Jint.Native;
  2. using Jint.Runtime.Debugger;
  3. using System.Linq;
  4. using Xunit;
  5. namespace Jint.Tests.Runtime.Debugger
  6. {
  7. public class ScopeTests
  8. {
  9. private static JsValue AssertOnlyScopeContains(DebugScopes scopes, string name, DebugScopeType scopeType)
  10. {
  11. var containingScope = Assert.Single(scopes, s => s.ScopeType == scopeType && s.BindingNames.Contains(name));
  12. Assert.DoesNotContain(scopes, s => s != containingScope && s.BindingNames.Contains(name));
  13. return containingScope.GetBindingValue(name);
  14. }
  15. private static void AssertScope(DebugScope actual, DebugScopeType expectedType, params string[] expectedBindingNames)
  16. {
  17. Assert.Equal(expectedType, actual.ScopeType);
  18. // Global scope will have a number of intrinsic bindings that are outside the scope [no pun] of these tests
  19. if (actual.ScopeType != DebugScopeType.Global)
  20. {
  21. Assert.Equal(expectedBindingNames.Length, actual.BindingNames.Count);
  22. }
  23. foreach (string expectedName in expectedBindingNames)
  24. {
  25. Assert.Contains(expectedName, actual.BindingNames);
  26. }
  27. }
  28. [Fact]
  29. public void AllowsInspectionOfUninitializedGlobalBindings()
  30. {
  31. string script = @"
  32. debugger;
  33. const globalConstant = 'test';
  34. let globalLet = 'test';
  35. ";
  36. TestHelpers.TestAtBreak(script, info =>
  37. {
  38. // Uninitialized global block scoped ("script scoped") bindings return null (and, just as importantly, don't throw):
  39. Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalConstant"));
  40. Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalLet"));
  41. });
  42. }
  43. [Fact]
  44. public void AllowsInspectionOfUninitializedBlockBindings()
  45. {
  46. string script = @"
  47. function test()
  48. {
  49. debugger;
  50. const globalConstant = 'test';
  51. let globalLet = 'test';
  52. }
  53. test();
  54. ";
  55. TestHelpers.TestAtBreak(script, info =>
  56. {
  57. // Uninitialized block scoped bindings return null (and, just as importantly, don't throw):
  58. Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalConstant"));
  59. Assert.Null(info.CurrentScopeChain[0].GetBindingValue("globalLet"));
  60. });
  61. }
  62. [Fact]
  63. public void ScriptScopeIncludesGlobalConst()
  64. {
  65. string script = @"
  66. const globalConstant = 'test';
  67. debugger;
  68. ";
  69. TestHelpers.TestAtBreak(script, info =>
  70. {
  71. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "globalConstant", DebugScopeType.Script);
  72. Assert.Equal("test", value.AsString());
  73. });
  74. }
  75. [Fact]
  76. public void ScriptScopeIncludesGlobalLet()
  77. {
  78. string script = @"
  79. let globalLet = 'test';
  80. debugger;";
  81. TestHelpers.TestAtBreak(script, info =>
  82. {
  83. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "globalLet", DebugScopeType.Script);
  84. Assert.Equal("test", value.AsString());
  85. });
  86. }
  87. [Fact]
  88. public void GlobalScopeIncludesGlobalVar()
  89. {
  90. string script = @"
  91. var globalVar = 'test';
  92. debugger;";
  93. TestHelpers.TestAtBreak(script, info =>
  94. {
  95. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "globalVar", DebugScopeType.Global);
  96. Assert.Equal("test", value.AsString());
  97. });
  98. }
  99. [Fact]
  100. public void TopLevelBlockScopeIsIdentified()
  101. {
  102. string script = @"
  103. function test()
  104. {
  105. const localConst = 'test';
  106. debugger;
  107. }
  108. test();";
  109. TestHelpers.TestAtBreak(script, info =>
  110. {
  111. Assert.Equal(3, info.CurrentScopeChain.Count);
  112. Assert.Equal(DebugScopeType.Block, info.CurrentScopeChain[0].ScopeType);
  113. Assert.True(info.CurrentScopeChain[0].IsTopLevel);
  114. });
  115. }
  116. [Fact]
  117. public void NonTopLevelBlockScopeIsIdentified()
  118. {
  119. string script = @"
  120. function test()
  121. {
  122. {
  123. const localConst = 'test';
  124. debugger;
  125. }
  126. }
  127. test();";
  128. TestHelpers.TestAtBreak(script, info =>
  129. {
  130. // We only have 3 scopes, because the function top level block scope is empty.
  131. Assert.Equal(3, info.CurrentScopeChain.Count);
  132. Assert.Equal(DebugScopeType.Block, info.CurrentScopeChain[0].ScopeType);
  133. Assert.False(info.CurrentScopeChain[0].IsTopLevel);
  134. });
  135. }
  136. [Fact]
  137. public void BlockScopeIncludesLocalConst()
  138. {
  139. string script = @"
  140. function test()
  141. {
  142. {
  143. const localConst = 'test';
  144. debugger;
  145. }
  146. }
  147. test();";
  148. TestHelpers.TestAtBreak(script, info =>
  149. {
  150. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "localConst", DebugScopeType.Block);
  151. Assert.Equal("test", value.AsString());
  152. });
  153. }
  154. [Fact]
  155. public void BlockScopeIncludesLocalLet()
  156. {
  157. string script = @"
  158. function test()
  159. {
  160. {
  161. let localLet = 'test';
  162. debugger;
  163. }
  164. }
  165. test();";
  166. TestHelpers.TestAtBreak(script, info =>
  167. {
  168. var value = AssertOnlyScopeContains(info.CurrentScopeChain, "localLet", DebugScopeType.Block);
  169. Assert.Equal("test", value.AsString());
  170. });
  171. }
  172. [Fact]
  173. public void LocalScopeIncludesLocalVar()
  174. {
  175. string script = @"
  176. function test()
  177. {
  178. var localVar = 'test';
  179. debugger;
  180. }
  181. test();";
  182. TestHelpers.TestAtBreak(script, info =>
  183. {
  184. AssertOnlyScopeContains(info.CurrentScopeChain, "localVar", DebugScopeType.Local);
  185. });
  186. }
  187. [Fact]
  188. public void LocalScopeIncludesBlockVar()
  189. {
  190. string script = @"
  191. function test()
  192. {
  193. debugger;
  194. {
  195. var localVar = 'test';
  196. }
  197. }
  198. test();";
  199. TestHelpers.TestAtBreak(script, info =>
  200. {
  201. AssertOnlyScopeContains(info.CurrentScopeChain, "localVar", DebugScopeType.Local);
  202. });
  203. }
  204. [Fact]
  205. public void BlockScopedConstIsVisibleInsideBlock()
  206. {
  207. string script = @"
  208. 'dummy statement';
  209. {
  210. const blockConst = 'block';
  211. debugger; // const isn't initialized until declaration
  212. }";
  213. TestHelpers.TestAtBreak(script, info =>
  214. {
  215. AssertOnlyScopeContains(info.CurrentScopeChain, "blockConst", DebugScopeType.Block);
  216. });
  217. }
  218. [Fact]
  219. public void BlockScopedLetIsVisibleInsideBlock()
  220. {
  221. string script = @"
  222. 'dummy statement';
  223. {
  224. let blockLet = 'block';
  225. debugger; // let isn't initialized until declaration
  226. }";
  227. TestHelpers.TestAtBreak(script, info =>
  228. {
  229. AssertOnlyScopeContains(info.CurrentScopeChain, "blockLet", DebugScopeType.Block);
  230. });
  231. }
  232. [Fact]
  233. public void HasCorrectScopeChainForFunction()
  234. {
  235. string script = @"
  236. function add(a, b)
  237. {
  238. debugger;
  239. return a + b;
  240. }
  241. const x = 1;
  242. const y = 2;
  243. const z = add(x, y);";
  244. TestHelpers.TestAtBreak(script, info =>
  245. {
  246. Assert.Collection(info.CurrentScopeChain,
  247. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
  248. scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"),
  249. scope => AssertScope(scope, DebugScopeType.Global, "add"));
  250. });
  251. }
  252. [Fact]
  253. public void HasCorrectScopeChainForNestedFunction()
  254. {
  255. string script = @"
  256. function add(a, b)
  257. {
  258. function power(a)
  259. {
  260. debugger;
  261. return a * a;
  262. }
  263. return power(a) + b;
  264. }
  265. const x = 1;
  266. const y = 2;
  267. const z = add(x, y);";
  268. TestHelpers.TestAtBreak(script, info =>
  269. {
  270. Assert.Collection(info.CurrentScopeChain,
  271. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a"),
  272. scope => AssertScope(scope, DebugScopeType.Closure, "b", "power"), // a, this, arguments shadowed by local
  273. scope => AssertScope(scope, DebugScopeType.Script, "x", "y", "z"),
  274. scope => AssertScope(scope, DebugScopeType.Global, "add"));
  275. });
  276. }
  277. [Fact]
  278. public void HasCorrectScopeChainForBlock()
  279. {
  280. string script = @"
  281. function add(a, b)
  282. {
  283. if (a > 0)
  284. {
  285. const y = b / a;
  286. debugger;
  287. }
  288. return a + b;
  289. }
  290. const x = 1;
  291. const y = 2;
  292. const z = add(x, y);";
  293. TestHelpers.TestAtBreak(script, info =>
  294. {
  295. Assert.Collection(info.CurrentScopeChain,
  296. scope => AssertScope(scope, DebugScopeType.Block, "y"),
  297. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
  298. scope => AssertScope(scope, DebugScopeType.Script, "x", "z"), // y shadowed
  299. scope => AssertScope(scope, DebugScopeType.Global, "add"));
  300. });
  301. }
  302. [Fact]
  303. public void HasCorrectScopeChainForNestedBlock()
  304. {
  305. string script = @"
  306. function add(a, b)
  307. {
  308. if (a > 0)
  309. {
  310. const y = b / a;
  311. if (y > 0)
  312. {
  313. const x = b / y;
  314. debugger;
  315. }
  316. }
  317. return a + b;
  318. }
  319. const x = 1;
  320. const y = 2;
  321. const z = add(x, y);";
  322. TestHelpers.TestAtBreak(script, info =>
  323. {
  324. Assert.Collection(info.CurrentScopeChain,
  325. scope => AssertScope(scope, DebugScopeType.Block, "x"),
  326. scope => AssertScope(scope, DebugScopeType.Block, "y"),
  327. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "b"),
  328. scope => AssertScope(scope, DebugScopeType.Script, "z"), // x, y shadowed
  329. scope => AssertScope(scope, DebugScopeType.Global, "add"));
  330. });
  331. }
  332. [Fact]
  333. public void HasCorrectScopeChainForCatch()
  334. {
  335. string script = @"
  336. function func()
  337. {
  338. let a = 1;
  339. try
  340. {
  341. throw new Error('test');
  342. }
  343. catch (error)
  344. {
  345. debugger;
  346. }
  347. }
  348. func();";
  349. TestHelpers.TestAtBreak(script, info =>
  350. {
  351. Assert.Collection(info.CurrentScopeChain,
  352. scope => AssertScope(scope, DebugScopeType.Catch, "error"),
  353. scope => AssertScope(scope, DebugScopeType.Block, "a"),
  354. scope => AssertScope(scope, DebugScopeType.Local, "arguments"),
  355. scope => AssertScope(scope, DebugScopeType.Global, "func"));
  356. });
  357. }
  358. [Fact]
  359. public void HasCorrectScopeChainForWith()
  360. {
  361. string script = @"
  362. const obj = { a: 2, b: 4 };
  363. with (obj)
  364. {
  365. const x = a;
  366. debugger;
  367. };";
  368. TestHelpers.TestAtBreak(script, info =>
  369. {
  370. Assert.Collection(info.CurrentScopeChain,
  371. scope => AssertScope(scope, DebugScopeType.Block, "x"),
  372. scope => AssertScope(scope, DebugScopeType.With, "a", "b"),
  373. scope => AssertScope(scope, DebugScopeType.Script, "obj"),
  374. scope => AssertScope(scope, DebugScopeType.Global));
  375. });
  376. }
  377. [Fact]
  378. public void ScopeChainIncludesNonEmptyScopes()
  379. {
  380. string script = @"
  381. const x = 2;
  382. if (x > 0)
  383. {
  384. const y = x;
  385. if (x > 1)
  386. {
  387. const z = x;
  388. debugger;
  389. }
  390. }";
  391. TestHelpers.TestAtBreak(script, info =>
  392. {
  393. Assert.Collection(info.CurrentScopeChain,
  394. scope => AssertScope(scope, DebugScopeType.Block, "z"),
  395. scope => AssertScope(scope, DebugScopeType.Block, "y"),
  396. scope => AssertScope(scope, DebugScopeType.Script, "x"),
  397. scope => AssertScope(scope, DebugScopeType.Global));
  398. });
  399. }
  400. [Fact]
  401. public void ScopeChainExcludesEmptyScopes()
  402. {
  403. string script = @"
  404. const x = 2;
  405. if (x > 0)
  406. {
  407. if (x > 1)
  408. {
  409. const z = x;
  410. debugger;
  411. }
  412. }";
  413. TestHelpers.TestAtBreak(script, info =>
  414. {
  415. Assert.Collection(info.CurrentScopeChain,
  416. scope => AssertScope(scope, DebugScopeType.Block, "z"),
  417. scope => AssertScope(scope, DebugScopeType.Script, "x"),
  418. scope => AssertScope(scope, DebugScopeType.Global));
  419. });
  420. }
  421. [Fact]
  422. public void ResolvesScopeChainsUpTheCallStack()
  423. {
  424. string script = @"
  425. const x = 1;
  426. function foo(a, c)
  427. {
  428. debugger;
  429. }
  430. function bar(b)
  431. {
  432. foo(b, 2);
  433. }
  434. bar(x);";
  435. TestHelpers.TestAtBreak(script, info =>
  436. {
  437. Assert.Collection(info.CallStack,
  438. frame => Assert.Collection(frame.ScopeChain,
  439. // in foo()
  440. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "a", "c"),
  441. scope => AssertScope(scope, DebugScopeType.Script, "x"),
  442. scope => AssertScope(scope, DebugScopeType.Global, "foo", "bar")
  443. ),
  444. frame => Assert.Collection(frame.ScopeChain,
  445. // in bar()
  446. scope => AssertScope(scope, DebugScopeType.Local, "arguments", "b"),
  447. scope => AssertScope(scope, DebugScopeType.Script, "x"),
  448. scope => AssertScope(scope, DebugScopeType.Global, "foo", "bar")
  449. ),
  450. frame => Assert.Collection(frame.ScopeChain,
  451. // in global
  452. scope => AssertScope(scope, DebugScopeType.Script, "x"),
  453. scope => AssertScope(scope, DebugScopeType.Global, "foo", "bar")
  454. )
  455. );
  456. });
  457. }
  458. }
  459. }