add auto-generated UFCS correctness checks

This commit is contained in:
WebFreak001 2023-03-20 03:44:27 +01:00 committed by Jan Jurzitza
parent efd8743c9e
commit 911ce077a5
7 changed files with 284 additions and 6 deletions

View File

@ -75,14 +75,14 @@ jobs:
- name: Linux Tests
if: contains(matrix.os, 'ubuntu')
run: |
./run_tests.sh
./run_tests.sh --extra
working-directory: tests
shell: bash
- name: Windows and MacOS Tests
if: contains(matrix.os, 'windows') || contains(matrix.os, 'macos')
run: |
./run_tests.sh
./run_tests.sh --extra
working-directory: tests
shell: bash
continue-on-error: true

View File

@ -34,7 +34,7 @@ echo "STAT:rough build time=${build_time}s"
echo "STAT:"
cd tests
./run_tests.sh --time-server
./run_tests.sh --time-server --extra
echo "STAT:DCD run_tests.sh $(grep -F 'Elapsed (wall clock) time' stderr.txt)"
echo "STAT:DCD run_tests.sh $(grep -F 'Maximum resident set size (kbytes)' stderr.txt)"
@ -49,7 +49,7 @@ rm -rf .dub bin/dcd-server
dub build --build=profile-gc --config=server --compiler=dmd 2>&1 || echo "DCD BUILD FAILED"
cd tests
./run_tests.sh
./run_tests.sh --extra
echo "STAT:top 5 GC sources in server:"
if [ ! -f "profilegc.log" ]; then

View File

@ -137,7 +137,7 @@ ldcserver: githash
${LDC} $(LDC_SERVER_FLAGS) ${SERVER_SRC} -oq -of=bin/dcd-server
test: debugserver dmdclient
cd tests && ./run_tests.sh
cd tests && ./run_tests.sh --extra
release:
./release.sh

View File

@ -0,0 +1 @@
proc_test.d

View File

@ -0,0 +1,266 @@
// This generates functions with all specified test types as first argument +
// variables for each specified test type.
// Then it calls all functions with every type to see which ones are accepted by
// the compiler, to automatically stay up-to-date.
import std;
import fs = std.file;
string[] testTypes = [
"bool",
"byte",
"ubyte",
"short",
"ushort",
"int",
"uint",
"long",
"ulong",
"char",
"wchar",
"dchar",
"float",
"double",
"real",
"BasicStruct",
"AliasThisInt",
];
// index here must map onto varTypePermutations index
string[][] funcTypePermutations = [
// TODO: check for const/inout/immutable/shared in UFCS checks
[
"%s",
// "const(%s)",
"ref %s",
// "ref const(%s)"
],
[
"%s*",
// "const(%s)*",
// "const(%s*)",
"ref %s*",
// "ref const(%s)*",
// "ref const(%s*)"
],
[
"%s[]",
// "const(%s)[]",
// "const(%s[])",
"ref %s[]",
// "ref const(%s)[]",
// "ref const(%s[])"
]
];
string[][] varTypePermutations = [
[
"%s",
// "const(%s)"
],
[
"%s*",
// "const(%s)*",
// "const(%s*)"
],
[
"%s[]",
// "const(%s)[]",
// "const(%s[])"
]
];
string preamble = `
struct BasicStruct { int member1; string member2; }
struct AliasThisInt { int member1; string member2; alias member1 this; }
`;
int main(string[] args)
{
string functionsCode;
string varsCode;
string callCode;
string[] allFunctions;
string[string] funcLookup;
string[string] varLookup;
foreach (ti, type; testTypes)
{
foreach (pi, perms; funcTypePermutations)
{
foreach (i, perm; perms)
{
string resolved = format(perm, type);
string id = getID(ti, pi, i);
allFunctions ~= ("func_" ~ id);
functionsCode ~= "void func_" ~ id ~ "(" ~ resolved ~ " arg) {}\n";
functionsCode ~= resolved ~ " make_" ~ id ~ "() { static " ~ resolved
.chompPrefix("ref ") ~ " x; return x; }\n";
funcLookup["func_" ~ id] = resolved;
}
}
foreach (pi, perms; varTypePermutations)
{
foreach (i, perm; perms)
{
string resolved = format(perm, type);
string id = getID(ti, pi, i);
varsCode ~= resolved ~ " var_" ~ id ~ " = make_" ~ id ~ "();\n";
varLookup["var_" ~ id] = resolved;
foreach (cti, subType; testTypes)
foreach (ci, subPerms; funcTypePermutations)
foreach (fi, subPerm; subPerms)
{
callCode ~= "var_" ~ id ~ ".func_" ~ getID(cti, ci, fi) ~ "();\n";
}
}
}
}
allFunctions.sort!"a<b";
string code = preamble
~ functionsCode
~ "\nvoid main() {\n"
~ varsCode
~ callCode
~ "}\n";
string[] lines = code.splitLines;
fs.write("proc_test.d", code);
auto output = executeShell("$DC -verrors=0 -c proc_test.d").output;
size_t numErrors = 0;
string[][string] variableIncompatibilities;
foreach (err; output.lineSplitter)
{
if (!err.startsWith("proc_test.d("))
continue;
err = err["proc_test.d(".length .. $];
auto lineNo = err.parse!int;
if (!err.startsWith("): Error: "))
continue;
err = err["): Error: ".length .. $];
string line = lines[lineNo - 1];
enforce(line.endsWith("();"), "Unexpected error in line " ~ lineNo.to!string);
line = line[0 .. $ - 3];
string varName = line.findSplit(".")[0];
string funcName = line.findSplit(".")[2];
// writeln("variable type ", varLookup[varName], " can't call ", funcLookup[funcName]);
variableIncompatibilities[varName] ~= funcName;
numErrors++;
}
enforce(numErrors > 1_000, "compiler didn't error as expected, need to adjust tests!");
writeln("Total incompatible type combinations: ", numErrors);
string[][string] wrongDCDCompletions;
foreach (varName; varLookup.byKey)
{
string input = code[0 .. $ - 2]
~ "\n"
~ varName ~ ".func_";
string[] dcdClient = ["../../../bin/dcd-client"];
if (args[1].length)
dcdClient ~= args[1];
auto proc = pipeProcess(dcdClient ~ ["-c" ~ to!string(input.length)]);
proc.stdin.rawWrite(input);
proc.stdin.rawWrite("\n}\n");
proc.stdin.close();
string[] dcdResult;
size_t i = 0;
foreach (line; proc.stdout.byLineCopy)
{
if (i++ == 0)
{
enforce(line == "identifiers");
continue;
}
auto parts = line.split("\t");
if (parts[1] != "F")
continue;
dcdResult ~= parts[0];
}
enforce(i > 0, "every variable must auto-complete something! Missing completion for var " ~ varName
~ " of type " ~ varLookup[varName] ~ generateEmptyResponseReproductionCode(
varLookup[varName]));
enforce(dcdResult.length > 0, "Wrongly no UFCS completion for var " ~ varName
~ " of type " ~ varLookup[varName] ~ generateEmptyResponseReproductionCode(
varLookup[varName]));
dcdResult.sort!"a<b";
string[] minusExpect = variableIncompatibilities[varName];
minusExpect.sort!"a<b";
variableIncompatibilities[varName] = minusExpect = minusExpect.uniq.array;
auto neededFunctions = setDifference(allFunctions, minusExpect);
auto unneccessaryFunctions = setDifference(dcdResult, neededFunctions);
auto missingFunctions = setDifference(neededFunctions, setIntersection(
dcdResult, neededFunctions));
string[] diff =
unneccessaryFunctions.map!(ln => '+' ~ ln)
.chain(missingFunctions.map!(ln => '-' ~ ln))
.array;
// writeln(varLookup[varName], " -> ", dcdResult);
if (diff.length)
wrongDCDCompletions[varName] = diff;
}
foreach (varName, wrongTypes; wrongDCDCompletions)
{
writeln("Incorrect results for ", varLookup[varName], ":");
wrongTypes.sort!"a<b";
char prevChar = ' ';
foreach (wrongType; wrongTypes)
{
if (prevChar != wrongType[0])
{
prevChar = wrongType[0];
if (prevChar == '+')
writeln("\tDCD errornously matched these argument types:");
else if (prevChar == '-')
writeln("\tDCD errornously did not match these argument types:");
}
wrongType = wrongType[1 .. $];
writeln("\t\t", funcLookup[wrongType]);
}
writeln();
}
return wrongDCDCompletions.length ? 1 : 0;
}
string getID(size_t ti, size_t pi, size_t i)
{
return format!"%s_%s_%s"(ti, pi, i);
}
string generateEmptyResponseReproductionCode(string type)
{
string prefix =
"void ufcsFunc(" ~ type ~ " v) {}\n\n"
~ "void main() {\n"
~ " " ~ type ~ " myVar;\n"
~ " myVar.ufcs";
return "\n\nReproduction code:\n```d\n"
~ prefix ~ ";\n"
~ "}\n"
~ "```\n\n"
~ "call `dcd-client -c" ~ prefix.length.to!string ~ "`";
}

View File

@ -0,0 +1,7 @@
#!/bin/bash
if [ -z "${DC:-}" ]; then
DC=dmd
fi
DC="$DC" "$DC" -run generate_tests.d "$1"

View File

@ -7,6 +7,7 @@ IMPORTS=$(pwd)/imports
export IMPORTS
SOCKETMODES="unix tcp"
TIME_SERVER=0
EXTRA_TESTCASES=
# `--arguments` must come before test dirs!
while (( "$#" )); do
@ -22,6 +23,9 @@ while (( "$#" )); do
# socket mode can still be overriden with `--tcp-only`
TIME_SERVER=1
SOCKETMODES="unix"
elif [[ "$1" == "--extra" ]]; then
# also include tests in the "extra" directory that long to complete
EXTRA_TESTCASES="extra/*/"
elif [[ "$1" =~ ^-- ]]; then
echo "Unrecognized test argument: $1"
exit 1
@ -108,7 +112,7 @@ for socket in $SOCKETMODES; do # supported: unix tcp
done
# Run tests
for testCase in $TESTCASES; do
for testCase in $TESTCASES $EXTRA_TESTCASES; do
cd $testCase
./run.sh "$tcp"