DCD/dsymbol/src/dsymbol/tests.d

755 lines
22 KiB
D

module dsymbol.tests;
import std.experimental.allocator;
import dparse.ast, dparse.parser, dparse.lexer, dparse.rollback_allocator;
import dsymbol.cache_entry, dsymbol.modulecache, dsymbol.symbol;
import dsymbol.conversion, dsymbol.conversion.first, dsymbol.conversion.second, dsymbol.conversion.third;
import dsymbol.semantic, dsymbol.string_interning, dsymbol.builtin.names;
import std.file, std.path, std.format;
import std.stdio : writeln, stdout;
import dsymbol.ufcs;
/**
* Parses `source`, caches its symbols and compares the the cache content
* with the `results`.
*
* Params:
* source = The source code to test.
* results = An array of string array. Each slot represents the variable name
* followed by the type strings.
*/
version (unittest):
void expectSymbolsAndTypes(const string source, const string[][] results,
string file = __FILE_FULL_PATH__, size_t line = __LINE__)
{
import core.exception : AssertError;
import std.exception : enforce;
ModuleCache mcache;
auto pair = generateAutocompleteTrees(source, mcache);
scope(exit) pair.destroy();
size_t i;
foreach(ss; (*pair.symbol)[])
{
if (ss.type)
{
enforce!AssertError(i <= results.length, "not enough results", file, line);
enforce!AssertError(results[i].length > 1,
"at least one type must be present in a result row", file, line);
enforce!AssertError(ss.name == results[i][0],
"expected variableName: `%s` but got `%s`".format(results[i][0], ss.name),
file, line);
auto t = cast() ss.type;
foreach (immutable j; 1..results[i].length)
{
enforce!AssertError(t != null, "null symbol", file, line);
enforce!AssertError(t.name == results[i][j],
"expected typeName: `%s` but got `%s`".format(results[i][j], t.name),
file, line);
if (t.type is t && t.name.length && t.name[0] != '*')
break;
t = t.type;
}
i++;
}
}
enforce!AssertError(i == results.length, "too many expected results, %s is left".format(results[i .. $]), file, line);
}
@system unittest
{
writeln("Running type deduction tests...");
q{bool b; int i;}.expectSymbolsAndTypes([["b", "bool"],["i", "int"]]);
q{auto b = false;}.expectSymbolsAndTypes([["b", "bool"]]);
q{auto b = true;}.expectSymbolsAndTypes([["b", "bool"]]);
q{auto b = [0];}.expectSymbolsAndTypes([["b", "*arr-literal*", "int"]]);
q{auto b = [[0]];}.expectSymbolsAndTypes([["b", "*arr-literal*", "*arr-literal*", "int"]]);
q{auto b = [[[0]]];}.expectSymbolsAndTypes([["b", "*arr-literal*", "*arr-literal*", "*arr-literal*", "int"]]);
q{auto b = [];}.expectSymbolsAndTypes([["b", "*arr-literal*", "void"]]);
q{auto b = [[]];}.expectSymbolsAndTypes([["b", "*arr-literal*", "*arr-literal*", "void"]]);
//q{int* b;}.expectSymbolsAndTypes([["b", "*", "int"]]);
//q{int*[] b;}.expectSymbolsAndTypes([["b", "*arr*", "*", "int"]]);
q{auto b = new class {int i;};}.expectSymbolsAndTypes([["b", "__anonclass1"]]);
// got a crash before but solving is not yet working ("foo" instead of "__anonclass1");
q{class Bar{} auto foo(){return new class Bar{};} auto b = foo();}.expectSymbolsAndTypes([["b", "foo"]]);
}
// this one used to crash, see #125
unittest
{
ModuleCache cache;
auto source = q{ auto a = true ? [42] : []; };
auto pair = generateAutocompleteTrees(source, cache);
}
// https://github.com/dlang-community/D-Scanner/issues/749
unittest
{
ModuleCache cache;
auto source = q{ void test() { foo(new class A {});} };
auto pair = generateAutocompleteTrees(source, cache);
}
// https://github.com/dlang-community/D-Scanner/issues/738
unittest
{
ModuleCache cache;
auto source = q{ void b() { c = } alias b this; };
auto pair = generateAutocompleteTrees(source, cache);
}
unittest
{
ModuleCache cache;
writeln("Running function literal tests...");
const sources = [
q{ int a; auto dg = { }; },
q{ void f() { int a; auto dg = { }; } },
q{ auto f = (int a) { }; },
q{ auto f() { return (int a) { }; } },
q{ auto f() { return g((int a) { }); } },
q{ void f() { g((int a) { }); } },
q{ void f() { auto x = (int a) { }; } },
q{ void f() { auto x = g((int a) { }); } },
];
foreach (src; sources)
{
auto pair = generateAutocompleteTrees(src, cache);
auto a = pair.scope_.getFirstSymbolByNameAndCursor(istring("a"), 35);
assert(a, src);
assert(a.type, src);
assert(a.type.name == "int", src);
}
}
unittest
{
ModuleCache cache;
writeln("Get return type name");
auto source = q{ int meaningOfLife() { return 42; } };
auto pair = generateAutocompleteTrees(source, cache);
auto meaningOfLife = pair.symbol.getFirstPartNamed(istring("meaningOfLife"));
assert(meaningOfLife.type.name == "int");
}
unittest
{
ModuleCache cache;
writeln("Get return type name from class method");
auto source = q{ class Life { uint meaningOfLife() { return 42; } }};
auto pair = generateAutocompleteTrees(source, cache);
auto lifeClass = pair.symbol.getFirstPartNamed(istring("Life"));
auto meaningOfLife = lifeClass.getFirstPartNamed(istring("meaningOfLife"));
assert(meaningOfLife.type.name == "uint");
}
unittest
{
ModuleCache cache;
writeln("Return type of auto should be null");
auto source = q{ class Life { auto meaningOfLife() { return 42; } }};
auto pair = generateAutocompleteTrees(source, cache);
auto lifeClass = pair.symbol.getFirstPartNamed(istring("Life"));
auto meaningOfLife = lifeClass.getFirstPartNamed(istring("meaningOfLife"));
assert(meaningOfLife.type is null);
}
unittest
{
ModuleCache cache;
writeln("Return type of scope should be null");
auto source = q{ class Life { scope meaningOfLife() { return 42; } }};
auto pair = generateAutocompleteTrees(source, cache);
auto lifeClass = pair.symbol.getFirstPartNamed(istring("Life"));
auto meaningOfLife = lifeClass.getFirstPartNamed(istring("meaningOfLife"));
assert(meaningOfLife.type is null);
}
unittest
{
ModuleCache cache;
writeln("Templated return type should be deduced correctly");
auto source = q{
struct AnswerToLife(T) {
T life;
}
AnswerToLife!int meaningOfLife() { return AnswerToLife!int(42); }
};
ScopeSymbolPair pair = generateAutocompleteTrees(source, cache);
auto answerToLife = pair.symbol.getFirstPartNamed(istring("AnswerToLife"));
DSymbol* meaningOfLife = pair.symbol.getFirstPartNamed(istring("meaningOfLife"));
assert(meaningOfLife.type.name == "AnswerToLife");
assert(meaningOfLife.type is answerToLife);
}
unittest
{
ModuleCache cache;
writeln("Array return type should be deduced correctly");
auto source = q{
int[] meaningOfLife() { return [42]; }
};
ScopeSymbolPair pair = generateAutocompleteTrees(source, cache);
DSymbol* meaningOfLife = pair.symbol.getFirstPartNamed(istring("meaningOfLife"));
assert(meaningOfLife.type.name == "*arr*");
}
unittest
{
ModuleCache cache;
writeln("Int* return type should be deduced correctly");
auto source = q{
int* meaningOfLife()
{
auto life = 42;
return &life;
}
};
ScopeSymbolPair pair = generateAutocompleteTrees(source, cache);
DSymbol* meaningOfLife = pair.symbol.getFirstPartNamed(istring("meaningOfLife"));
writeln(meaningOfLife.type.formatType);
assert(meaningOfLife.type.formatType == "int*");
}
unittest
{
ModuleCache cache;
writeln("Running struct constructor tests...");
auto source = q{ struct A {int a; struct B {bool b;} int c;} };
auto pair = generateAutocompleteTrees(source, cache);
auto A = pair.symbol.getFirstPartNamed(internString("A"));
auto B = A.getFirstPartNamed(internString("B"));
auto ACtor = A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME);
auto BCtor = B.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME);
assert(ACtor.callTip == "this(int a, int c)");
assert(BCtor.callTip == "this(bool b)");
}
unittest
{
ModuleCache cache;
writeln("Running union constructor tests...");
auto source = q{ union A {int a; bool b;} };
auto pair = generateAutocompleteTrees(source, cache);
auto A = pair.symbol.getFirstPartNamed(internString("A"));
auto ACtor = A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME);
assert(ACtor.callTip == "this(int a, bool b)");
}
unittest
{
ModuleCache cache;
writeln("Running non-importable symbols tests...");
auto source = q{
class A { this(int a){} }
class B : A {}
class C { A f; alias f this; }
};
auto pair = generateAutocompleteTrees(source, cache);
auto A = pair.symbol.getFirstPartNamed(internString("A"));
auto B = pair.symbol.getFirstPartNamed(internString("B"));
auto C = pair.symbol.getFirstPartNamed(internString("C"));
assert(A.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) !is null);
assert(B.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) is null);
assert(C.getFirstPartNamed(CONSTRUCTOR_SYMBOL_NAME) is null);
}
unittest
{
ModuleCache cache;
writeln("Running alias this tests...");
auto source = q{ struct A {int f;} struct B { A a; alias a this; void fun() { auto var = f; };} };
auto pair = generateAutocompleteTrees(source, cache);
auto A = pair.symbol.getFirstPartNamed(internString("A"));
auto B = pair.symbol.getFirstPartNamed(internString("B"));
auto Af = A.getFirstPartNamed(internString("f"));
auto fun = B.getFirstPartNamed(internString("fun"));
auto var = fun.getFirstPartNamed(internString("var"));
assert(Af is pair.scope_.getFirstSymbolByNameAndCursor(internString("f"), var.location));
}
unittest
{
ModuleCache cache;
writeln("Running anon struct tests...");
auto source = q{ struct A { struct {int a;}} };
auto pair = generateAutocompleteTrees(source, cache);
auto A = pair.symbol.getFirstPartNamed(internString("A"));
assert(A);
auto Aa = A.getFirstPartNamed(internString("a"));
assert(Aa);
}
unittest
{
ModuleCache cache;
writeln("Running anon class tests...");
const sources = [
q{ auto a = new class Object { int i; }; },
q{ auto a = new class Object { int i; void m() { } }; },
q{ auto a = g(new class Object { int i; }); },
q{ auto a = g(new class Object { int i; void m() { } }); },
q{ void f() { new class Object { int i; }; } },
q{ void f() { new class Object { int i; void m() { } }; } },
q{ void f() { g(new class Object { int i; }); } },
q{ void f() { g(new class Object { int i; void m() { } }); } },
q{ void f() { auto a = new class Object { int i; }; } },
q{ void f() { auto a = new class Object { int i; void m() { } }; } },
q{ void f() { auto a = g(new class Object { int i; }); } },
q{ void f() { auto a = g(new class Object { int i; void m() { } }); } },
];
foreach (src; sources)
{
auto pair = generateAutocompleteTrees(src, cache);
auto a = pair.scope_.getFirstSymbolByNameAndCursor(istring("i"), 60);
assert(a, src);
assert(a.type, src);
assert(a.type.name == "int", src);
}
}
unittest
{
ModuleCache cache;
writeln("Running the deduction from index expr tests...");
{
auto source = q{struct S{} S[] s; auto b = s[i];};
auto pair = generateAutocompleteTrees(source, cache);
DSymbol* S = pair.symbol.getFirstPartNamed(internString("S"));
DSymbol* b = pair.symbol.getFirstPartNamed(internString("b"));
assert(S);
assert(b);
assert(b.type);
assert(b.type is S);
}
{
auto source = q{struct S{} S[1] s; auto b = s[i];};
auto pair = generateAutocompleteTrees(source, cache);
DSymbol* S = pair.symbol.getFirstPartNamed(internString("S"));
DSymbol* b = pair.symbol.getFirstPartNamed(internString("b"));
assert(S);
assert(b);
assert(b.type);
assert(b.type is S);
}
{
auto source = q{struct S{} S[][] s; auto b = s[0];};
auto pair = generateAutocompleteTrees(source, cache);
DSymbol* S = pair.symbol.getFirstPartNamed(internString("S"));
DSymbol* b = pair.symbol.getFirstPartNamed(internString("b"));
assert(S);
assert(b);
assert(b.type);
assert(b.type.type is S);
}
{
auto source = q{struct S{} S[][][] s; auto b = s[0][0];};
auto pair = generateAutocompleteTrees(source, cache);
DSymbol* S = pair.symbol.getFirstPartNamed(internString("S"));
DSymbol* b = pair.symbol.getFirstPartNamed(internString("b"));
assert(S);
assert(b);
assert(b.type);
assert(b.type.name == ARRAY_SYMBOL_NAME);
assert(b.type.type is S);
}
{
auto source = q{struct S{} S s; auto b = [s][0];};
auto pair = generateAutocompleteTrees(source, cache);
DSymbol* S = pair.symbol.getFirstPartNamed(internString("S"));
DSymbol* b = pair.symbol.getFirstPartNamed(internString("b"));
assert(S);
assert(b);
assert(b.type is S);
}
}
unittest
{
ModuleCache cache;
writeln("Running `super` tests...");
auto source = q{ class A {} class B : A {} };
auto pair = generateAutocompleteTrees(source, cache);
assert(pair.symbol);
auto A = pair.symbol.getFirstPartNamed(internString("A"));
auto B = pair.symbol.getFirstPartNamed(internString("B"));
auto scopeA = (pair.scope_.getScopeByCursor(A.location + A.name.length));
auto scopeB = (pair.scope_.getScopeByCursor(B.location + B.name.length));
assert(scopeA !is scopeB);
assert(!scopeA.getSymbolsByName(SUPER_SYMBOL_NAME).length);
assert(scopeB.getSymbolsByName(SUPER_SYMBOL_NAME)[0].type is A);
}
unittest
{
ModuleCache cache;
writeln("Running the \"access chain with inherited type\" tests...");
auto source = q{ class A {} class B : A {} };
auto pair = generateAutocompleteTrees(source, cache);
assert(pair.symbol);
auto A = pair.symbol.getFirstPartNamed(internString("A"));
assert(A);
auto B = pair.symbol.getFirstPartNamed(internString("B"));
assert(B);
auto AfromB = B.getFirstPartNamed(internString("A"));
assert(AfromB.kind == CompletionKind.aliasName);
assert(AfromB.type is A);
}
unittest
{
ModuleCache cache;
writeln("Running template type parameters tests...");
{
auto source = q{ struct Foo(T : int){} struct Bar(T : Foo){} };
auto pair = generateAutocompleteTreesProd(source, "", 0, cache);
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
assert(T2.type.name == "int");
DSymbol* T3 = pair.symbol.getFirstPartNamed(internString("Bar"));
DSymbol* T4 = T3.getFirstPartNamed(internString("T"));
assert(T4.type);
assert(T4.type == T1);
}
{
auto source = q{ struct Foo(T){ }};
auto pair = generateAutocompleteTreesProd(source, "", 0, cache);
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
assert(T1);
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
assert(T2);
assert(T2.kind == CompletionKind.typeTmpParam);
}
}
unittest
{
ModuleCache cache;
writeln("Running template variadic parameters tests...");
auto source = q{ struct Foo(T...){ }};
auto pair = generateAutocompleteTreesProd(source, "", 0, cache);
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
assert(T1);
DSymbol* T2 = T1.getFirstPartNamed(internString("T"));
assert(T2);
assert(T2.kind == CompletionKind.variadicTmpParam);
}
unittest
{
writeln("Running public import tests...");
const dir = buildPath(tempDir(), "dsymbol");
const fnameA = buildPath(dir, "a.d");
const fnameB = buildPath(dir, "b.d");
const fnameC = buildPath(dir, "c.d");
const fnameD = buildPath(dir, "d.d");
const srcA = q{ int x; int w; };
const srcB = q{ float y; private float z; };
const srcC = q{ public { import a : x; import b; } import a : w; long t; };
const srcD = q{ public import c; };
// A simpler diagram:
// a = x w
// b = y [z]
// c = t + (x y) [w]
// d = (t x y)
mkdir(dir);
write(fnameA, srcA);
write(fnameB, srcB);
write(fnameC, srcC);
write(fnameD, srcD);
scope (exit)
{
remove(fnameA);
remove(fnameB);
remove(fnameC);
remove(fnameD);
rmdir(dir);
}
ModuleCache cache;
cache.addImportPaths([dir]);
const a = cache.getModuleSymbol(istring(fnameA));
const b = cache.getModuleSymbol(istring(fnameB));
const c = cache.getModuleSymbol(istring(fnameC));
const d = cache.getModuleSymbol(istring(fnameD));
const ax = a.getFirstPartNamed(istring("x"));
const aw = a.getFirstPartNamed(istring("w"));
assert(ax);
assert(aw);
assert(ax.type && ax.type.name == "int");
assert(aw.type && aw.type.name == "int");
const by = b.getFirstPartNamed(istring("y"));
const bz = b.getFirstPartNamed(istring("z"));
assert(by);
assert(bz);
assert(by.type && by.type.name == "float");
assert(bz.type && bz.type.name == "float");
const ct = c.getFirstPartNamed(istring("t"));
const cw = c.getFirstPartNamed(istring("w"));
const cx = c.getFirstPartNamed(istring("x"));
const cy = c.getFirstPartNamed(istring("y"));
const cz = c.getFirstPartNamed(istring("z"));
assert(ct);
assert(ct.type && ct.type.name == "long");
assert(cw is null); // skipOver is true
assert(cx is ax);
assert(cy is by);
assert(cz is bz); // should not be there, but it is handled by DCD
const dt = d.getFirstPartNamed(istring("t"));
const dw = d.getFirstPartNamed(istring("w"));
const dx = d.getFirstPartNamed(istring("x"));
const dy = d.getFirstPartNamed(istring("y"));
const dz = d.getFirstPartNamed(istring("z"));
assert(dt is ct);
assert(dw is null);
assert(dx is cx);
assert(dy is cy);
assert(dz is cz);
}
unittest
{
ModuleCache cache;
writeln("Testing protection scopes");
auto source = q{version(all) { private: } struct Foo{ }};
auto pair = generateAutocompleteTreesProd(source, "", 0, cache);
DSymbol* T1 = pair.symbol.getFirstPartNamed(internString("Foo"));
assert(T1);
assert(T1.protection != tok!"private");
}
// check for memory leaks on thread termination (in static constructors)
version (linux) unittest
{
import core.memory : GC;
import core.thread : Thread;
import fs = std.file;
import std.array : split;
import std.conv : to;
// get the resident set size
static long getRSS()
{
GC.collect();
GC.minimize();
// read Linux process statistics
const txt = fs.readText("/proc/self/stat");
const parts = split(txt);
return to!long(parts[23]);
}
const rssBefore = getRSS();
// create and destroy a lot of dummy threads
foreach (j; 0 .. 50)
{
Thread[100] arr;
foreach (i; 0 .. 100)
arr[i] = new Thread({}).start();
foreach (i; 0 .. 100)
arr[i].join();
}
const rssAfter = getRSS();
// check the process memory increase with some eyeballed threshold
assert(rssAfter - rssBefore < 6_000);
}
// this is for testing that internString data is always on the same address
// since we use this special property for modulecache recursion guard
unittest
{
istring a = internString("foo_bar_baz".idup);
istring b = internString("foo_bar_baz".idup);
assert(a.data.ptr == b.data.ptr);
}
private StringCache stringCache = void;
static this()
{
stringCache = StringCache(StringCache.defaultBucketCount);
}
static ~this()
{
destroy(stringCache);
}
const(Token)[] lex(string source)
{
return lex(source, null);
}
const(Token)[] lex(string source, string filename)
{
import dparse.lexer : getTokensForParser;
import std.string : representation;
LexerConfig config;
config.fileName = filename;
return getTokensForParser(source.dup.representation, config, &stringCache);
}
unittest
{
auto tokens = lex(q{int a = 9;});
foreach(i, t;
cast(IdType[]) [tok!"int", tok!"identifier", tok!"=", tok!"intLiteral", tok!";"])
{
assert(tokens[i] == t);
}
assert(tokens[1].text == "a", tokens[1].text);
assert(tokens[3].text == "9", tokens[3].text);
}
string randomDFilename()
{
import std.uuid : randomUUID;
return "dsymbol_" ~ randomUUID().toString() ~ ".d";
}
ScopeSymbolPair generateAutocompleteTrees(string source, ref ModuleCache cache)
{
return generateAutocompleteTrees(source, randomDFilename, cache);
}
ScopeSymbolPair generateAutocompleteTrees(string source, string filename, ref ModuleCache cache, size_t cursorPosition = -1)
{
auto tokens = lex(source);
RollbackAllocator rba;
Module m = parseModule(tokens, filename, &rba);
scope first = new FirstPass(m, internString(filename), &cache);
first.run();
secondPass(first.rootSymbol, first.moduleScope, cache);
thirdPass(first.rootSymbol, first.moduleScope, cache, cursorPosition);
auto ufcsSymbols = getUFCSSymbolsForCursor(first.moduleScope, tokens, cursorPosition);
auto r = first.rootSymbol.acSymbol;
typeid(SemanticSymbol).destroy(first.rootSymbol);
return ScopeSymbolPair(r, first.moduleScope, ufcsSymbols);
}
ScopeSymbolPair generateAutocompleteTreesProd(string source, string filename, size_t cursorPosition, ref ModuleCache cache)
{
auto tokens = lex(source);
RollbackAllocator rba;
return dsymbol.conversion.generateAutocompleteTrees(
tokens, &rba, cursorPosition, cache);
}
version (linux)
{
unittest
{
enum string ufcsExampleCode =
q{class Incrementer
{
int run(int x)
{
return x++;
}
}
int increment(int x)
{
return x++;
}
void doIncrement()
{
int life = 42;
life.
}};
writeln("Getting UFCS Symbols For life");
ModuleCache cache;
// position of variable life
size_t cursorPos = 139;
auto pair = generateAutocompleteTreesProd(ufcsExampleCode, randomDFilename, cursorPos, cache);
assert(pair.ufcsSymbols.length > 0);
assert(pair.ufcsSymbols[0].name == "increment");
}
enum string ufcsTemplateExampleCode =
q{int increment(T)(T x)
{
return x++;
}
void doIncrement()
{
int life = 42;
life.
}};
unittest
{
enum string ufcsTemplateExampleCode =
q{int increment(T)(T x)
{
return x++;
}
void doIncrement()
{
int life = 42;
life.
}};
writeln("Getting Templated UFCS Symbols For life");
ModuleCache cache;
// position of variable life
size_t cursorPos = 82;
auto pair = generateAutocompleteTreesProd(ufcsTemplateExampleCode, randomDFilename, cursorPos, cache);
assert(pair.ufcsSymbols.length > 0);
assert(pair.ufcsSymbols[0].name == "increment");
}
unittest
{
enum string ufcsPointerExampleCode =
q{void increment(int* x) { }
void doIncrement(int* x, int* y)
{
y.
}};
writeln("Getting Ptr UFCS completion");
ModuleCache cache;
// position of variable life
size_t cursorPos = 65;
auto pair = generateAutocompleteTreesProd(ufcsPointerExampleCode, randomDFilename, cursorPos, cache);
assert(pair.ufcsSymbols[0].name == "increment");
assert(pair.ufcsSymbols[1].name == "doIncrement");
}
}