add an official yaml test suite runner (#209)
add an official yaml test suite runner merged-on-behalf-of: BBasile <BBasile@users.noreply.github.com>
This commit is contained in:
parent
55b745fe2a
commit
f183e4aa80
|
@ -32,6 +32,7 @@ script:
|
|||
- "dub build dyaml:getting-started"
|
||||
- "dub build dyaml:representer"
|
||||
- "dub build dyaml:resolver"
|
||||
- "dub build dyaml:testsuite"
|
||||
- "dub build dyaml:tojson"
|
||||
- "dub build dyaml:yaml_gen"
|
||||
- "dub build dyaml:yaml_stats"
|
||||
|
|
3
dub.json
3
dub.json
|
@ -19,6 +19,7 @@
|
|||
"examples/tojson",
|
||||
"examples/yaml_bench",
|
||||
"examples/yaml_gen",
|
||||
"examples/yaml_stats"
|
||||
"examples/yaml_stats",
|
||||
"testsuite"
|
||||
]
|
||||
}
|
||||
|
|
8
testsuite/dub.json
Normal file
8
testsuite/dub.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "testsuite",
|
||||
"targetType": "executable",
|
||||
"dependencies":
|
||||
{
|
||||
"dyaml": "*"
|
||||
}
|
||||
}
|
323
testsuite/source/app.d
Normal file
323
testsuite/source/app.d
Normal file
|
@ -0,0 +1,323 @@
|
|||
module dyaml.testsuite;
|
||||
|
||||
import dyaml;
|
||||
import dyaml.event;
|
||||
|
||||
import std.algorithm;
|
||||
import std.conv;
|
||||
import std.file;
|
||||
import std.format;
|
||||
import std.json;
|
||||
import std.path;
|
||||
import std.range;
|
||||
import std.stdio;
|
||||
import std.string;
|
||||
import std.typecons;
|
||||
import std.utf;
|
||||
|
||||
auto dumpEventString(string str) @safe
|
||||
{
|
||||
string[] output;
|
||||
try
|
||||
{
|
||||
auto events = Loader.fromString(str).parse();
|
||||
foreach (event; events)
|
||||
{
|
||||
string line;
|
||||
final switch (event.id)
|
||||
{
|
||||
case EventID.scalar:
|
||||
line = "=VAL ";
|
||||
if (event.anchor != "")
|
||||
{
|
||||
line ~= text("&", event.anchor, " ");
|
||||
}
|
||||
if (event.tag != "")
|
||||
{
|
||||
line ~= text("<", event.tag, "> ");
|
||||
}
|
||||
switch(event.scalarStyle)
|
||||
{
|
||||
case ScalarStyle.singleQuoted:
|
||||
line ~= "'";
|
||||
break;
|
||||
case ScalarStyle.doubleQuoted:
|
||||
line ~= '"';
|
||||
break;
|
||||
case ScalarStyle.literal:
|
||||
line ~= "|";
|
||||
break;
|
||||
case ScalarStyle.folded:
|
||||
line ~= ">";
|
||||
break;
|
||||
default:
|
||||
line ~= ":";
|
||||
break;
|
||||
}
|
||||
if (event.value != "")
|
||||
{
|
||||
line ~= text(event.value.substitute("\n", "\\n", `\`, `\\`, "\r", "\\r", "\t", "\\t", "\b", "\\b"));
|
||||
}
|
||||
break;
|
||||
case EventID.streamStart:
|
||||
line = "+STR";
|
||||
break;
|
||||
case EventID.documentStart:
|
||||
line = "+DOC";
|
||||
if (event.explicitDocument)
|
||||
{
|
||||
line ~= text(" ---");
|
||||
}
|
||||
break;
|
||||
case EventID.mappingStart:
|
||||
line = "+MAP";
|
||||
if (event.anchor != "")
|
||||
{
|
||||
line ~= text(" &", event.anchor);
|
||||
}
|
||||
if (event.tag != "")
|
||||
{
|
||||
line ~= text(" <", event.tag, ">");
|
||||
}
|
||||
break;
|
||||
case EventID.sequenceStart:
|
||||
line = "+SEQ";
|
||||
if (event.anchor != "")
|
||||
{
|
||||
line ~= text(" &", event.anchor);
|
||||
}
|
||||
if (event.tag != "")
|
||||
{
|
||||
line ~= text(" <", event.tag, ">");
|
||||
}
|
||||
break;
|
||||
case EventID.streamEnd:
|
||||
line = "-STR";
|
||||
break;
|
||||
case EventID.documentEnd:
|
||||
line = "-DOC";
|
||||
if (event.explicitDocument)
|
||||
{
|
||||
line ~= " ...";
|
||||
}
|
||||
break;
|
||||
case EventID.mappingEnd:
|
||||
line = "-MAP";
|
||||
break;
|
||||
case EventID.sequenceEnd:
|
||||
line = "-SEQ";
|
||||
break;
|
||||
case EventID.alias_:
|
||||
line = text("=ALI *", event.anchor);
|
||||
break;
|
||||
case EventID.invalid:
|
||||
assert(0, "Invalid EventID produced");
|
||||
}
|
||||
output ~= line;
|
||||
}
|
||||
}
|
||||
catch (Exception) {} //Exceptions should just stop adding output
|
||||
return output.join("\n");
|
||||
}
|
||||
|
||||
enum TestState
|
||||
{
|
||||
success,
|
||||
skipped,
|
||||
failure
|
||||
}
|
||||
|
||||
struct TestResult
|
||||
{
|
||||
string name;
|
||||
TestState state;
|
||||
string failMsg;
|
||||
|
||||
const void toString(OutputRange)(ref OutputRange writer)
|
||||
if (isOutputRange!(OutputRange, char))
|
||||
{
|
||||
ubyte statusColour;
|
||||
string statusString;
|
||||
final switch (state) {
|
||||
case TestState.success:
|
||||
statusColour = 32;
|
||||
statusString = "Succeeded";
|
||||
break;
|
||||
case TestState.failure:
|
||||
statusColour = 31;
|
||||
statusString = "Failed";
|
||||
break;
|
||||
case TestState.skipped:
|
||||
statusColour = 93;
|
||||
statusString = "Skipped";
|
||||
break;
|
||||
}
|
||||
writer.formattedWrite!"[\033[%s;1m%s\033[0m] %s"(statusColour, statusString, name);
|
||||
if (state != TestState.success)
|
||||
{
|
||||
writer.formattedWrite!" (%s)"(failMsg.replace("\n", " "));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TestResult runTests(string tml) @safe
|
||||
{
|
||||
TestResult output;
|
||||
output.state = TestState.success;
|
||||
auto splitFile = tml.splitter("\n--- ");
|
||||
output.name = splitFile.front.findSplit("=== ")[2];
|
||||
bool loadFailed, shouldFail;
|
||||
string failMsg;
|
||||
JSONValue json;
|
||||
Node[] nodes;
|
||||
string yamlString;
|
||||
Nullable!string compareYAMLString;
|
||||
Nullable!string events;
|
||||
ulong testsRun;
|
||||
|
||||
void fail(string msg) @safe
|
||||
{
|
||||
output.state = TestState.failure;
|
||||
output.failMsg = msg;
|
||||
}
|
||||
void skip(string msg) @safe
|
||||
{
|
||||
output.state = TestState.skipped;
|
||||
output.failMsg = msg;
|
||||
}
|
||||
void parseYAML(string yaml) @safe
|
||||
{
|
||||
yamlString = yaml;
|
||||
try {
|
||||
nodes = Loader.fromString(yamlString).array;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
loadFailed = true;
|
||||
failMsg = e.msg;
|
||||
}
|
||||
}
|
||||
void compareLineByLine(const string a, const string b, const string msg) @safe
|
||||
{
|
||||
foreach (line1, line2; zip(a.lineSplitter, b.lineSplitter))
|
||||
{
|
||||
if (line1 != line2)
|
||||
{
|
||||
fail(text(msg, " Got ", line1, ", expected ", line2));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (section; splitFile.drop(1))
|
||||
{
|
||||
auto splitSection = section.findSplit("\n");
|
||||
auto splitSectionHeader = splitSection[0].findSplit(":");
|
||||
const splitSectionName = splitSectionHeader[0].findSplit("(");
|
||||
const sectionName = splitSectionName[0];
|
||||
const sectionParams = splitSectionName[2].findSplit(")")[0];
|
||||
string sectionData = splitSection[2];
|
||||
if (sectionData != "")
|
||||
{
|
||||
//< means dedent.
|
||||
if (sectionParams.canFind("<"))
|
||||
{
|
||||
sectionData = sectionData[4..$].substitute("\n ", "\n", "<SPC>", " ", "<TAB>", "\t").toUTF8;
|
||||
}
|
||||
else
|
||||
{
|
||||
sectionData = sectionData.substitute("<SPC>", " ", "<TAB>", "\t").toUTF8;
|
||||
}
|
||||
//Not sure what + means.
|
||||
}
|
||||
switch(sectionName)
|
||||
{
|
||||
case "in-yaml":
|
||||
parseYAML(sectionData);
|
||||
break;
|
||||
case "in-json":
|
||||
json = parseJSON(sectionData);
|
||||
break;
|
||||
case "test-event":
|
||||
events = sectionData;
|
||||
break;
|
||||
case "error":
|
||||
shouldFail = true;
|
||||
testsRun++;
|
||||
break;
|
||||
case "out-yaml":
|
||||
compareYAMLString = sectionData;
|
||||
break;
|
||||
case "emit-yaml":
|
||||
// TODO: Figure out how/if to implement this
|
||||
//fail("Unhandled test - emit-yaml");
|
||||
break;
|
||||
case "lex-token":
|
||||
// TODO: Should this be implemented?
|
||||
//fail("Unhandled test - lex-token");
|
||||
break;
|
||||
case "from": break;
|
||||
case "tags": break;
|
||||
default: assert(false, text("Unhandled section ", sectionName, "in ", output.name));
|
||||
}
|
||||
}
|
||||
if (!loadFailed && !compareYAMLString.isNull && !shouldFail)
|
||||
{
|
||||
Appender!string buf;
|
||||
dumper(buf).dump();
|
||||
compareLineByLine(buf.data, compareYAMLString, "Dumped YAML mismatch");
|
||||
testsRun++;
|
||||
}
|
||||
if (!loadFailed && !events.isNull && !shouldFail)
|
||||
{
|
||||
const compare = dumpEventString(yamlString);
|
||||
compareLineByLine(compare, events, "Event mismatch");
|
||||
testsRun++;
|
||||
}
|
||||
if (loadFailed && !shouldFail)
|
||||
{
|
||||
fail(failMsg);
|
||||
}
|
||||
if (shouldFail && !loadFailed)
|
||||
{
|
||||
fail("Invalid YAML accepted");
|
||||
}
|
||||
if ((testsRun == 0) && (output.state != TestState.failure))
|
||||
{
|
||||
skip("No tests run");
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
// Can't be @safe due to dirEntries()
|
||||
void main(string[] args) @system
|
||||
{
|
||||
string path = "yaml-test-suite/test";
|
||||
|
||||
void printResult(string id, TestResult result)
|
||||
{
|
||||
writeln(id, " ", result);
|
||||
}
|
||||
|
||||
if (args.length > 1)
|
||||
{
|
||||
path = args[1];
|
||||
}
|
||||
|
||||
ulong total;
|
||||
ulong successes;
|
||||
foreach (file; dirEntries(path, "*.tml", SpanMode.shallow))
|
||||
{
|
||||
auto result = runTests(readText(file));
|
||||
if (result.state == TestState.success)
|
||||
{
|
||||
debug(verbose) printResult(file.baseName, result);
|
||||
successes++;
|
||||
}
|
||||
else
|
||||
{
|
||||
printResult(file.baseName, result);
|
||||
}
|
||||
total++;
|
||||
}
|
||||
writefln!"%d/%d tests passed"(successes, total);
|
||||
}
|
Loading…
Reference in a new issue