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 }