1 module dsymbol.tests; 2 3 import stdx.allocator; 4 import dparse.ast, dparse.parser, dparse.lexer, dparse.rollback_allocator; 5 import dsymbol.cache_entry, dsymbol.modulecache, dsymbol.symbol; 6 import dsymbol.conversion, dsymbol.conversion.first, dsymbol.conversion.second; 7 import dsymbol.semantic, dsymbol.string_interning, dsymbol.builtin.names; 8 import std.file, std.path, std.format; 9 import std.stdio : writeln, stdout; 10 import std.typecons : scoped; 11 12 /** 13 * Parses `source`, caches its symbols and compares the the cache content 14 * with the `results`. 15 * 16 * Params: 17 * source = The source code to test. 18 * results = An array of string array. Each slot represents the variable name 19 * followed by the type strings. 20 */ 21 version (unittest): 22 void expectSymbolsAndTypes(const string source, const string[][] results, 23 string file = __FILE_FULL_PATH__, size_t line = __LINE__) 24 { 25 import core.exception : AssertError; 26 import std.exception : enforce; 27 28 ModuleCache mcache = ModuleCache(theAllocator); 29 auto pair = generateAutocompleteTrees(source, mcache); 30 scope(exit) pair.destroy(); 31 32 size_t i; 33 foreach(ss; (*pair.symbol)[]) 34 { 35 if (ss.type) 36 { 37 enforce!AssertError(i <= results.length, "not enough results", file, line); 38 enforce!AssertError(results[i].length > 1, 39 "at least one type must be present in a result row", file, line); 40 enforce!AssertError(ss.name == results[i][0], 41 "expected variableName: `%s` but got `%s`".format(results[i][0], ss.name), 42 file, line); 43 44 auto t = cast() ss.type; 45 foreach (immutable j; 1..results[i].length) 46 { 47 enforce!AssertError(t != null, "null symbol", file, line); 48 enforce!AssertError(t.name == results[i][j], 49 "expected typeName: `%s` but got `%s`".format(results[i][j], t.name), 50 file, line); 51 if (t.type is t && t.name.length && t.name[0] != '*') 52 break; 53 t = t.type; 54 } 55 i++; 56 } 57 } 58 } 59 60 @system unittest 61 { 62 writeln("Running type deduction tests..."); 63 q{bool b; int i;}.expectSymbolsAndTypes([["b", "bool"],["i", "int"]]); 64 q{auto b = false;}.expectSymbolsAndTypes([["b", "bool"]]); 65 q{auto b = true;}.expectSymbolsAndTypes([["b", "bool"]]); 66 q{auto b = [0];}.expectSymbolsAndTypes([["b", "*arr-literal*", "int"]]); 67 q{auto b = [[0]];}.expectSymbolsAndTypes([["b", "*arr-literal*", "*arr-literal*", "int"]]); 68 q{auto b = [[[0]]];}.expectSymbolsAndTypes([["b", "*arr-literal*", "*arr-literal*", "*arr-literal*", "int"]]); 69 q{auto b = [];}.expectSymbolsAndTypes([["b", "*arr-literal*", "void"]]); 70 q{auto b = [[]];}.expectSymbolsAndTypes([["b", "*arr-literal*", "*arr-literal*", "void"]]); 71 //q{int* b;}.expectSymbolsAndTypes([["b", "*", "int"]]); 72 //q{int*[] b;}.expectSymbolsAndTypes([["b", "*arr*", "*", "int"]]); 73 74 q{auto b = new class {int i;};}.expectSymbolsAndTypes([["b", "__anonclass1"]]); 75 76 // got a crash before but solving is not yet working ("foo" instead of "__anonclass1"); 77 q{class Bar{} auto foo(){return new class Bar{};} auto b = foo();}.expectSymbolsAndTypes([["b", "foo"]]); 78 } 79 80 // this one used to crash, see #125 81 unittest 82 { 83 ModuleCache cache = ModuleCache(theAllocator); 84 auto source = q{ auto a = true ? [42] : []; }; 85 auto pair = generateAutocompleteTrees(source, cache); 86 } 87 88 // https://github.com/dlang-community/D-Scanner/issues/749 89 unittest 90 { 91 ModuleCache cache = ModuleCache(theAllocator); 92 auto source = q{ void test() { foo(new class A {});} }; 93 auto pair = generateAutocompleteTrees(source, cache); 94 } 95 96 // https://github.com/dlang-community/D-Scanner/issues/738 97 unittest 98 { 99 ModuleCache cache = ModuleCache(theAllocator); 100 auto source = q{ void b() { c = } alias b this; }; 101 auto pair = generateAutocompleteTrees(source, cache); 102 } 103 104 unittest 105 { 106 ModuleCache cache = ModuleCache(theAllocator); 107 108 writeln("Running function literal tests..."); 109 const sources = [ 110 q{ int a; auto dg = { }; }, 111 q{ void f() { int a; auto dg = { }; } }, 112 q{ auto f = (int a) { }; }, 113 q{ auto f() { return (int a) { }; } }, 114 q{ auto f() { return g((int a) { }); } }, 115 q{ void f() { g((int a) { }); } }, 116 q{ void f() { auto x = (int a) { }; } }, 117 q{ void f() { auto x = g((int a) { }); } }, 118 ]; 119 foreach (src; sources) 120 { 121 auto pair = generateAutocompleteTrees(src, cache); 122 auto a = pair.scope_.getFirstSymbolByNameAndCursor(istring("a"), 35); 123 assert(a, src); 124 assert(a.type, src); 125 assert(a.type.name == "int", src); 126 } 127 } 128 129 unittest 130 { 131 ModuleCache cache = ModuleCache(theAllocator); 132 133 writeln("Running struct constructor tests..."); 134 auto source = q{ struct A {int a; struct B {bool b;} int c;} }; 135 auto pair = generateAutocompleteTrees(source, cache); 136 auto A = pair.symbol.getFirstPartNamed(internString("A")); 137 auto B = A.getFirstPartNamed(internString("B")); 138 auto ACtor = A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME); 139 auto BCtor = B.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME); 140 assert(ACtor.callTip == "this(int a, int c)"); 141 assert(BCtor.callTip == "this(bool b)"); 142 } 143 144 unittest 145 { 146 ModuleCache cache = ModuleCache(theAllocator); 147 148 writeln("Running union constructor tests..."); 149 auto source = q{ union A {int a; bool b;} }; 150 auto pair = generateAutocompleteTrees(source, cache); 151 auto A = pair.symbol.getFirstPartNamed(internString("A")); 152 auto ACtor = A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME); 153 assert(ACtor.callTip == "this(int a, bool b)"); 154 } 155 156 unittest 157 { 158 ModuleCache cache = ModuleCache(theAllocator); 159 writeln("Running non-importable symbols tests..."); 160 auto source = q{ 161 class A { this(int a){} } 162 class B : A {} 163 class C { A f; alias f this; } 164 }; 165 auto pair = generateAutocompleteTrees(source, cache); 166 auto A = pair.symbol.getFirstPartNamed(internString("A")); 167 auto B = pair.symbol.getFirstPartNamed(internString("B")); 168 auto C = pair.symbol.getFirstPartNamed(internString("C")); 169 assert(A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) !is null); 170 assert(B.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) is null); 171 assert(C.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) is null); 172 } 173 174 unittest 175 { 176 ModuleCache cache = ModuleCache(theAllocator); 177 178 writeln("Running alias this tests..."); 179 auto source = q{ struct A {int f;} struct B { A a; alias a this; void fun() { auto var = f; };} }; 180 auto pair = generateAutocompleteTrees(source, cache); 181 auto A = pair.symbol.getFirstPartNamed(internString("A")); 182 auto B = pair.symbol.getFirstPartNamed(internString("B")); 183 auto Af = A.getFirstPartNamed(internString("f")); 184 auto fun = B.getFirstPartNamed(internString("fun")); 185 auto var = fun.getFirstPartNamed(internString("var")); 186 assert(Af is pair.scope_.getFirstSymbolByNameAndCursor(internString("f"), var.location)); 187 } 188 189 unittest 190 { 191 ModuleCache cache = ModuleCache(theAllocator); 192 193 writeln("Running anon struct tests..."); 194 auto source = q{ struct A { struct {int a;}} }; 195 auto pair = generateAutocompleteTrees(source, cache); 196 auto A = pair.symbol.getFirstPartNamed(internString("A")); 197 assert(A); 198 auto Aa = A.getFirstPartNamed(internString("a")); 199 assert(Aa); 200 } 201 202 unittest 203 { 204 ModuleCache cache = ModuleCache(theAllocator); 205 206 writeln("Running anon class tests..."); 207 const sources = [ 208 q{ auto a = new class Object { int i; }; }, 209 q{ auto a = new class Object { int i; void m() { } }; }, 210 q{ auto a = g(new class Object { int i; }); }, 211 q{ auto a = g(new class Object { int i; void m() { } }); }, 212 q{ void f() { new class Object { int i; }; } }, 213 q{ void f() { new class Object { int i; void m() { } }; } }, 214 q{ void f() { g(new class Object { int i; }); } }, 215 q{ void f() { g(new class Object { int i; void m() { } }); } }, 216 q{ void f() { auto a = new class Object { int i; }; } }, 217 q{ void f() { auto a = new class Object { int i; void m() { } }; } }, 218 q{ void f() { auto a = g(new class Object { int i; }); } }, 219 q{ void f() { auto a = g(new class Object { int i; void m() { } }); } }, 220 ]; 221 foreach (src; sources) 222 { 223 auto pair = generateAutocompleteTrees(src, cache); 224 auto a = pair.scope_.getFirstSymbolByNameAndCursor(istring("i"), 60); 225 assert(a, src); 226 assert(a.type, src); 227 assert(a.type.name == "int", src); 228 } 229 } 230 231 unittest 232 { 233 ModuleCache cache = ModuleCache(theAllocator); 234 235 writeln("Running the deduction from index expr tests..."); 236 { 237 auto source = q{struct S{} S[] s; auto b = s[i];}; 238 auto pair = generateAutocompleteTrees(source, cache); 239 DSymbol* S = pair.symbol.getFirstPartNamed(internString("S")); 240 DSymbol* b = pair.symbol.getFirstPartNamed(internString("b")); 241 assert(S); 242 assert(b.type is S); 243 } 244 { 245 auto source = q{struct S{} S[1] s; auto b = s[i];}; 246 auto pair = generateAutocompleteTrees(source, cache); 247 DSymbol* S = pair.symbol.getFirstPartNamed(internString("S")); 248 DSymbol* b = pair.symbol.getFirstPartNamed(internString("b")); 249 assert(S); 250 assert(b.type is S); 251 } 252 { 253 auto source = q{struct S{} S[][] s; auto b = s[0];}; 254 auto pair = generateAutocompleteTrees(source, cache); 255 DSymbol* S = pair.symbol.getFirstPartNamed(internString("S")); 256 DSymbol* b = pair.symbol.getFirstPartNamed(internString("b")); 257 assert(S); 258 assert(b.type.type is S); 259 } 260 { 261 auto source = q{struct S{} S[][][] s; auto b = s[0][0];}; 262 auto pair = generateAutocompleteTrees(source, cache); 263 DSymbol* S = pair.symbol.getFirstPartNamed(internString("S")); 264 DSymbol* b = pair.symbol.getFirstPartNamed(internString("b")); 265 assert(S); 266 assert(b.type.name == ARRAY_SYMBOL_NAME); 267 assert(b.type.type is S); 268 } 269 { 270 auto source = q{struct S{} S s; auto b = [s][0];}; 271 auto pair = generateAutocompleteTrees(source, cache); 272 DSymbol* S = pair.symbol.getFirstPartNamed(internString("S")); 273 DSymbol* b = pair.symbol.getFirstPartNamed(internString("b")); 274 assert(S); 275 assert(b.type is S); 276 } 277 } 278 279 unittest 280 { 281 ModuleCache cache = ModuleCache(theAllocator); 282 283 writeln("Running `super` tests..."); 284 auto source = q{ class A {} class B : A {} }; 285 auto pair = generateAutocompleteTrees(source, cache); 286 assert(pair.symbol); 287 auto A = pair.symbol.getFirstPartNamed(internString("A")); 288 auto B = pair.symbol.getFirstPartNamed(internString("B")); 289 auto scopeA = (pair.scope_.getScopeByCursor(A.location + A.name.length)); 290 auto scopeB = (pair.scope_.getScopeByCursor(B.location + B.name.length)); 291 assert(scopeA !is scopeB); 292 293 assert(!scopeA.getSymbolsByName(SUPER_SYMBOL_NAME).length); 294 assert(scopeB.getSymbolsByName(SUPER_SYMBOL_NAME)[0].type is A); 295 } 296 297 unittest 298 { 299 ModuleCache cache = ModuleCache(theAllocator); 300 301 writeln("Running the \"access chain with inherited type\" tests..."); 302 auto source = q{ class A {} class B : A {} }; 303 auto pair = generateAutocompleteTrees(source, cache); 304 assert(pair.symbol); 305 auto A = pair.symbol.getFirstPartNamed(internString("A")); 306 assert(A); 307 auto B = pair.symbol.getFirstPartNamed(internString("B")); 308 assert(B); 309 auto AfromB = B.getFirstPartNamed(internString("A")); 310 assert(AfromB.kind == CompletionKind.aliasName); 311 assert(AfromB.type is A); 312 } 313 314 unittest 315 { 316 ModuleCache cache = ModuleCache(theAllocator); 317 318 writeln("Running template type parameters tests..."); 319 { 320 auto source = q{ struct Foo(T : int){} struct Bar(T : Foo){} }; 321 auto pair = generateAutocompleteTrees(source, "", 0, cache); 322 DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo")); 323 DSymbol* T2 = T1.getFirstPartNamed(internString("T")); 324 assert(T2.type.name == "int"); 325 DSymbol* T3 = pair.symbol.getFirstPartNamed(internString("Bar")); 326 DSymbol* T4 = T3.getFirstPartNamed(internString("T")); 327 assert(T4.type); 328 assert(T4.type == T1); 329 } 330 { 331 auto source = q{ struct Foo(T){ }}; 332 auto pair = generateAutocompleteTrees(source, "", 0, cache); 333 DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo")); 334 assert(T1); 335 DSymbol* T2 = T1.getFirstPartNamed(internString("T")); 336 assert(T2); 337 assert(T2.kind == CompletionKind.typeTmpParam); 338 } 339 } 340 341 unittest 342 { 343 ModuleCache cache = ModuleCache(theAllocator); 344 345 writeln("Running template variadic parameters tests..."); 346 auto source = q{ struct Foo(T...){ }}; 347 auto pair = generateAutocompleteTrees(source, "", 0, cache); 348 DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo")); 349 assert(T1); 350 DSymbol* T2 = T1.getFirstPartNamed(internString("T")); 351 assert(T2); 352 assert(T2.kind == CompletionKind.variadicTmpParam); 353 } 354 355 unittest 356 { 357 writeln("Running public import tests..."); 358 359 const dir = buildPath(tempDir(), "dsymbol"); 360 const fnameA = buildPath(dir, "a.d"); 361 const fnameB = buildPath(dir, "b.d"); 362 const fnameC = buildPath(dir, "c.d"); 363 const fnameD = buildPath(dir, "d.d"); 364 const srcA = q{ int x; int w; }; 365 const srcB = q{ float y; private float z; }; 366 const srcC = q{ public { import a : x; import b; } import a : w; long t; }; 367 const srcD = q{ public import c; }; 368 // A simpler diagram: 369 // a = x w 370 // b = y [z] 371 // c = t + (x y) [w] 372 // d = (t x y) 373 374 mkdir(dir); 375 write(fnameA, srcA); 376 write(fnameB, srcB); 377 write(fnameC, srcC); 378 write(fnameD, srcD); 379 scope (exit) 380 { 381 remove(fnameA); 382 remove(fnameB); 383 remove(fnameC); 384 remove(fnameD); 385 rmdir(dir); 386 } 387 388 ModuleCache cache = ModuleCache(theAllocator); 389 cache.addImportPaths([dir]); 390 391 const a = cache.getModuleSymbol(istring(fnameA)); 392 const b = cache.getModuleSymbol(istring(fnameB)); 393 const c = cache.getModuleSymbol(istring(fnameC)); 394 const d = cache.getModuleSymbol(istring(fnameD)); 395 const ax = a.getFirstPartNamed(istring("x")); 396 const aw = a.getFirstPartNamed(istring("w")); 397 assert(ax); 398 assert(aw); 399 assert(ax.type && ax.type.name == "int"); 400 assert(aw.type && aw.type.name == "int"); 401 const by = b.getFirstPartNamed(istring("y")); 402 const bz = b.getFirstPartNamed(istring("z")); 403 assert(by); 404 assert(bz); 405 assert(by.type && by.type.name == "float"); 406 assert(bz.type && bz.type.name == "float"); 407 const ct = c.getFirstPartNamed(istring("t")); 408 const cw = c.getFirstPartNamed(istring("w")); 409 const cx = c.getFirstPartNamed(istring("x")); 410 const cy = c.getFirstPartNamed(istring("y")); 411 const cz = c.getFirstPartNamed(istring("z")); 412 assert(ct); 413 assert(ct.type && ct.type.name == "long"); 414 assert(cw is null); // skipOver is true 415 assert(cx is ax); 416 assert(cy is by); 417 assert(cz is bz); // should not be there, but it is handled by DCD 418 const dt = d.getFirstPartNamed(istring("t")); 419 const dw = d.getFirstPartNamed(istring("w")); 420 const dx = d.getFirstPartNamed(istring("x")); 421 const dy = d.getFirstPartNamed(istring("y")); 422 const dz = d.getFirstPartNamed(istring("z")); 423 assert(dt is ct); 424 assert(dw is null); 425 assert(dx is cx); 426 assert(dy is cy); 427 assert(dz is cz); 428 } 429 430 unittest 431 { 432 ModuleCache cache = ModuleCache(theAllocator); 433 434 writeln("Testing protection scopes"); 435 auto source = q{version(all) { private: } struct Foo{ }}; 436 auto pair = generateAutocompleteTrees(source, "", 0, cache); 437 DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo")); 438 assert(T1); 439 assert(T1.protection != tok!"private"); 440 } 441 442 // check for memory leaks on thread termination (in static constructors) 443 version (linux) 444 unittest 445 { 446 import core.memory : GC; 447 import core.thread : Thread; 448 import fs = std.file; 449 import std.array : split; 450 import std.conv : to; 451 452 // get the resident set size 453 static long getRSS() 454 { 455 GC.collect(); 456 GC.minimize(); 457 // read Linux process statistics 458 const txt = fs.readText("/proc/self/stat"); 459 const parts = split(txt); 460 return to!long(parts[23]); 461 } 462 463 const rssBefore = getRSS(); 464 // create and destroy a lot of dummy threads 465 foreach (j; 0 .. 50) 466 { 467 Thread[100] arr; 468 foreach (i; 0 .. 100) 469 arr[i] = new Thread({}).start(); 470 foreach (i; 0 .. 100) 471 arr[i].join(); 472 } 473 const rssAfter = getRSS(); 474 // check the process memory increase with some eyeballed threshold 475 assert(rssAfter - rssBefore < 5000); 476 } 477 478 // this is for testing that internString data is always on the same address 479 // since we use this special property for modulecache recursion guard 480 unittest 481 { 482 istring a = internString("foo_bar_baz".idup); 483 istring b = internString("foo_bar_baz".idup); 484 assert(a.data.ptr == b.data.ptr); 485 } 486 487 private StringCache stringCache = void; 488 static this() 489 { 490 stringCache = StringCache(StringCache.defaultBucketCount); 491 } 492 static ~this() 493 { 494 destroy(stringCache); 495 } 496 497 const(Token)[] lex(string source) 498 { 499 return lex(source, null); 500 } 501 502 const(Token)[] lex(string source, string filename) 503 { 504 import dparse.lexer : getTokensForParser; 505 import std.string : representation; 506 LexerConfig config; 507 config.fileName = filename; 508 return getTokensForParser(source.dup.representation, config, &stringCache); 509 } 510 511 unittest 512 { 513 auto tokens = lex(q{int a = 9;}); 514 foreach(i, t; 515 cast(IdType[]) [tok!"int", tok!"identifier", tok!"=", tok!"intLiteral", tok!";"]) 516 { 517 assert(tokens[i] == t); 518 } 519 assert(tokens[1].text == "a", tokens[1].text); 520 assert(tokens[3].text == "9", tokens[3].text); 521 } 522 523 string randomDFilename() 524 { 525 import std.uuid : randomUUID; 526 return "dsymbol_" ~ randomUUID().toString() ~ ".d"; 527 } 528 529 ScopeSymbolPair generateAutocompleteTrees(string source, ref ModuleCache cache) 530 { 531 return generateAutocompleteTrees(source, randomDFilename, cache); 532 } 533 534 ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref ModuleCache cache) 535 { 536 auto tokens = lex(source); 537 RollbackAllocator rba; 538 Module m = parseModule(tokens, filename, &rba); 539 540 auto first = scoped!FirstPass(m, internString(filename), 541 theAllocator, theAllocator, true, &cache); 542 first.run(); 543 544 secondPass(first.rootSymbol, first.moduleScope, cache); 545 auto r = first.rootSymbol.acSymbol; 546 typeid(SemanticSymbol).destroy(first.rootSymbol); 547 return ScopeSymbolPair(r, first.moduleScope); 548 } 549 550 ScopeSymbolPair generateAutocompleteTrees(string source, size_t cursorPosition, ref ModuleCache cache) 551 { 552 return generateAutocompleteTrees(source, null, cache); 553 } 554 555 ScopeSymbolPair generateAutocompleteTrees(string source, string filename, size_t cursorPosition, ref ModuleCache cache) 556 { 557 auto tokens = lex(source); 558 RollbackAllocator rba; 559 return dsymbol.conversion.generateAutocompleteTrees( 560 tokens, theAllocator, &rba, cursorPosition, cache); 561 }