clean up tests - print more useful messages on failure, bring closer to D style

This commit is contained in:
Cameron Ross 2020-09-17 18:19:59 -03:00 committed by The Dlang Bot
parent 22267baaa7
commit 88eaf879ec
10 changed files with 667 additions and 880 deletions

View file

@ -9,48 +9,34 @@ module dyaml.test.common;
version(unittest) version(unittest)
{ {
public import std.conv; import dyaml.node;
public import std.stdio; import dyaml.event;
public import dyaml;
import core.exception; import core.exception;
import std.algorithm; import std.algorithm;
import std.array; import std.array;
import std.conv; import std.conv;
import std.file; import std.file;
import std.range;
import std.path; import std.path;
import std.traits; import std.traits;
import std.typecons; import std.typecons;
package: package:
debug(verbose)
{
enum verbose = true;
enum quiet = false;
}
else
{
enum verbose = false;
debug(noisy) enum quiet = false;
else enum quiet = true;
}
/** /**
* Run an unittest. Run a test.
*
* Params: testName = Name of the unittest. Params:
* testFunction = Unittest function. testFunction = Unittest function.
* unittestExt = Extensions of data files needed for the unittest. unittestExt = Extensions of data files needed for the unittest.
* skipExt = Extensions that must not be used for the unittest. skipExt = Extensions that must not be used for the unittest.
*/ */
void run(D)(string testName, D testFunction, void run(D)(D testFunction, string[] unittestExt, string[] skipExt = [])
string[] unittestExt, string[] skipExt = [])
{ {
immutable string dataDir = __FILE_FULL_PATH__.dirName ~ "/../../../test/data"; immutable string dataDir = __FILE_FULL_PATH__.dirName ~ "/../../../test/data";
auto testFilenames = findTestFilenames(dataDir); auto testFilenames = findTestFilenames(dataDir);
Result[] results;
if (unittestExt.length > 0) if (unittestExt.length > 0)
{ {
outer: foreach (base, extensions; testFilenames) outer: foreach (base, extensions; testFilenames)
@ -58,61 +44,120 @@ void run(D)(string testName, D testFunction,
string[] filenames; string[] filenames;
foreach (ext; unittestExt) foreach (ext; unittestExt)
{ {
if(!extensions.canFind(ext)){continue outer;} if (!extensions.canFind(ext))
{
continue outer;
}
filenames ~= base ~ '.' ~ ext; filenames ~= base ~ '.' ~ ext;
} }
foreach (ext; skipExt) foreach (ext; skipExt)
{ {
if(extensions.canFind(ext)){continue outer;} if (extensions.canFind(ext))
{
continue outer;
}
} }
results ~= execute(testName, testFunction, filenames); execute(testFunction, filenames);
} }
} }
else else
{ {
results ~= execute(testName, testFunction, string[].init); execute(testFunction, string[].init);
} }
display(results); }
// TODO: remove when a @safe ubyte[] file read can be done.
/**
Reads a file as an array of bytes.
Params:
filename = Full path to file to read.
Returns: The file's data.
*/
ubyte[] readData(string filename) @trusted
{
import std.file : read;
return cast(ubyte[])read(filename);
}
void assertNodesEqual(const scope Node gotNode, const scope Node expectedNode) @safe
{
import std.format : format;
assert(gotNode == expectedNode, format!"got %s, expected %s"(gotNode.debugString, expectedNode.debugString));
} }
/** /**
* Prints an exception if verbosity is turned on. Determine if events in events1 are equivalent to events in events2.
* Params: e = Exception to print.
*/
void printException(YAMLException e) @trusted
{
static if(verbose) { writeln(typeid(e).toString(), "\n", e); }
}
void printProgress(T...)(T params) @safe Params:
events1 = A range of events to compare with.
events2 = A second range of events to compare.
Returns: true if the events are equivalent, false otherwise.
*/
bool compareEvents(T, U)(T events1, U events2)
if (isInputRange!T && isInputRange!U && is(ElementType!T == Event) && is(ElementType!U == Event))
{ {
static if(!quiet) foreach (e1, e2; zip(events1, events2))
{ {
writeln(params); //Different event types.
if (e1.id != e2.id)
{
return false;
} }
//Different anchor (if applicable).
if (e1.id.among!(EventID.sequenceStart, EventID.mappingStart, EventID.alias_, EventID.scalar)
&& e1.anchor != e2.anchor)
{
return false;
}
//Different collection tag (if applicable).
if (e1.id.among!(EventID.sequenceStart, EventID.mappingStart) && e1.tag != e2.tag)
{
return false;
}
if (e1.id == EventID.scalar)
{
//Different scalar tag (if applicable).
if (!(e1.implicit || e2.implicit) && e1.tag != e2.tag)
{
return false;
}
//Different scalar value.
if (e1.value != e2.value)
{
return false;
}
}
}
return true;
}
/**
Throw an Error if events in events1 aren't equivalent to events in events2.
Params:
events1 = First event array to compare.
events2 = Second event array to compare.
*/
void assertEventsEqual(T, U)(T events1, U events2)
if (isInputRange!T && isInputRange!U && is(ElementType!T == Event) && is(ElementType!U == Event))
{
auto events1Copy = events1.array;
auto events2Copy = events2.array;
assert(compareEvents(events1Copy, events2Copy), text("Got '", events1Copy, "', expected '", events2Copy, "'"));
} }
private: private:
///Unittest status.
enum TestStatus
{
success, //Unittest passed.
failure, //Unittest failed.
error //There's an error in the unittest.
}
///Unittest result.
alias Result = Tuple!(string, "name", string[], "filenames", TestStatus, "kind", string, "info");
/** /**
* Find unittest input filenames. Find unittest input filenames.
*
* Params: dir = Directory to look in. Params: dir = Directory to look in.
*
* Returns: Test input base filenames and their extensions. Returns: Test input base filenames and their extensions.
*/ */
//@trusted due to dirEntries
string[][string] findTestFilenames(const string dir) @trusted string[][string] findTestFilenames(const string dir) @trusted
{ {
//Groups of extensions indexed by base names. //Groups of extensions indexed by base names.
@ -123,8 +168,14 @@ string[][string] findTestFilenames(const string dir) @trusted
{ {
string base = name.stripExtension(); string base = name.stripExtension();
string ext = name.extension(); string ext = name.extension();
if(ext is null){ext = "";} if (ext is null)
if(ext[0] == '.'){ext = ext[1 .. $];} {
ext = "";
}
if (ext[0] == '.')
{
ext = ext[1 .. $];
}
//If the base name doesn't exist yet, add it; otherwise add new extension. //If the base name doesn't exist yet, add it; otherwise add new extension.
names[base] = ((base in names) is null) ? [ext] : names[base] ~ ext; names[base] = ((base in names) is null) ? [ext] : names[base] ~ ext;
@ -134,98 +185,39 @@ string[][string] findTestFilenames(const string dir) @trusted
} }
/** /**
* Recursively copy an array of strings to a tuple to use for unittest function input. Recursively copy an array of strings to a tuple to use for unittest function input.
*
* Params: index = Current index in the array/tuple. Params:
* tuple = Tuple to copy to. index = Current index in the array/tuple.
* strings = Strings to copy. tuple = Tuple to copy to.
strings = Strings to copy.
*/ */
void stringsToTuple(uint index, F ...)(ref F tuple, const string[] strings) void stringsToTuple(uint index, F ...)(ref F tuple, const string[] strings)
in{assert(F.length == strings.length);} in(F.length == strings.length)
do do
{ {
tuple[index] = strings[index]; tuple[index] = strings[index];
static if(index > 0){stringsToTuple!(index - 1, F)(tuple, strings);} static if (index > 0)
{
stringsToTuple!(index - 1, F)(tuple, strings);
}
} }
/** /**
* Execute an unittest on specified files. Execute an unittest on specified files.
*
* Params: testName = Name of the unittest.
* testFunction = Unittest function.
* filenames = Names of input files to test with.
*
* Returns: Information about the results of the unittest.
*/
Result execute(D)(const string testName, D testFunction,
string[] filenames) @trusted
{
static if(verbose)
{
writeln("===========================================================================");
writeln(testName ~ "(" ~ filenames.join(", ") ~ ")...");
}
auto kind = TestStatus.success; Params:
string info = ""; testName = Name of the unittest.
try testFunction = Unittest function.
filenames = Names of input files to test with.
*/
void execute(D)(D testFunction, string[] filenames)
{ {
//Convert filenames to parameters tuple and call the test function. //Convert filenames to parameters tuple and call the test function.
alias F = Parameters!D[0..$]; alias F = Parameters!D[0..$];
F parameters; F parameters;
stringsToTuple!(F.length - 1, F)(parameters, filenames); stringsToTuple!(F.length - 1, F)(parameters, filenames);
testFunction(parameters); testFunction(parameters);
static if (!quiet){write(".");}
}
catch(Throwable e)
{
info = to!string(typeid(e)) ~ "\n" ~ to!string(e);
kind = (typeid(e) is typeid(AssertError)) ? TestStatus.failure : TestStatus.error;
write((verbose ? to!string(e) : to!string(kind)) ~ " ");
}
stdout.flush();
return Result(testName, filenames, kind, info);
}
/**
* Display unittest results.
*
* Params: results = Unittest results.
*/
void display(Result[] results) @safe
{
if(results.length > 0 && !verbose && !quiet){write("\n");}
size_t failures, errors;
static if(verbose)
{
writeln("===========================================================================");
}
//Results of each test.
foreach(result; results)
{
static if(verbose)
{
writeln(result.name, "(" ~ result.filenames.join(", ") ~ "): ",
to!string(result.kind));
}
if(result.kind == TestStatus.success){continue;}
if(result.kind == TestStatus.failure){++failures;}
else if(result.kind == TestStatus.error){++errors;}
writeln(result.info);
writeln("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
//Totals.
printProgress("===========================================================================");
printProgress("TESTS: ", results.length);
if(failures > 0){writeln("FAILURES: ", failures);}
if(errors > 0) {writeln("ERRORS: ", errors);}
} }
} // version(unittest) } // version(unittest)

View file

@ -6,33 +6,35 @@
module dyaml.test.compare; module dyaml.test.compare;
@safe unittest
version(unittest)
{ {
import dyaml : Loader;
import dyaml.test.common : assertNodesEqual, compareEvents, run;
import dyaml.test.common; /**
import dyaml.test.emitter; Test parser by comparing output from parsing two equivalent YAML files.
import dyaml.token;
Params:
/// Test parser by comparing output from parsing two equivalent YAML files. dataFilename = YAML file to parse.
/// canonicalFilename = Another file to parse, in canonical YAML format.
/// Params: dataFilename = YAML file to parse. */
/// canonicalFilename = Another file to parse, in canonical YAML format. static void testParser(string dataFilename, string canonicalFilename) @safe
void testParser(string dataFilename, string canonicalFilename) @safe
{ {
auto dataEvents = Loader.fromFile(dataFilename).parse(); auto dataEvents = Loader.fromFile(dataFilename).parse();
auto canonicalEvents = Loader.fromFile(canonicalFilename).parse(); auto canonicalEvents = Loader.fromFile(canonicalFilename).parse();
//BUG: the return value isn't checked! This test currently fails...
compareEvents(dataEvents, canonicalEvents); compareEvents(dataEvents, canonicalEvents);
} }
/**
Test loader by comparing output from loading two equivalent YAML files.
/// Test loader by comparing output from loading two equivalent YAML files. Params:
/// dataFilename = YAML file to load.
/// Params: dataFilename = YAML file to load. canonicalFilename = Another file to load, in canonical YAML format.
/// canonicalFilename = Another file to load, in canonical YAML format. */
void testLoader(string dataFilename, string canonicalFilename) @safe static void testLoader(string dataFilename, string canonicalFilename) @safe
{ {
import std.array : array; import std.array : array;
auto data = Loader.fromFile(dataFilename).array; auto data = Loader.fromFile(dataFilename).array;
@ -41,27 +43,9 @@ void testLoader(string dataFilename, string canonicalFilename) @safe
assert(data.length == canonical.length, "Unequal node count"); assert(data.length == canonical.length, "Unequal node count");
foreach (n; 0 .. data.length) foreach (n; 0 .. data.length)
{ {
if(data[n] != canonical[n]) assertNodesEqual(data[n], canonical[n]);
{
static if(verbose)
{
writeln("Normal value:");
writeln(data[n].debugString);
writeln("\n");
writeln("Canonical value:");
writeln(canonical[n].debugString);
}
assert(false, "testLoader(" ~ dataFilename ~ ", " ~ canonicalFilename ~ ") failed");
} }
} }
run(&testParser, ["data", "canonical"]);
run(&testLoader, ["data", "canonical"], ["test_loader_skip"]);
} }
@safe unittest
{
printProgress("D:YAML comparison unittest");
run("testParser", &testParser, ["data", "canonical"]);
run("testLoader", &testLoader, ["data", "canonical"], ["test_loader_skip"]);
}
} // version(unittest)

View file

@ -10,14 +10,14 @@ module dyaml.test.constructor;
version(unittest) version(unittest)
{ {
import std.conv;
import std.datetime; import std.datetime;
import std.exception; import std.exception;
import std.path; import std.path;
import std.string; import std.string;
import std.typecons; import std.typecons;
import dyaml.test.common; import dyaml : Loader, Node, YAMLNull;
///Expected results of loading test inputs. ///Expected results of loading test inputs.
Node[][string] expected; Node[][string] expected;
@ -921,18 +921,24 @@ struct TestStruct
} }
} }
} // version(unittest)
@safe unittest
{
import dyaml.test.common : assertNodesEqual, run;
/** /**
* Constructor unittest. Constructor unittest.
*
* Params: dataFilename = File name to read from. Params:
* codeDummy = Dummy .code filename, used to determine that dataFilename = File name to read from.
* .data file with the same name should be used in this test. codeDummy = Dummy .code filename, used to determine that
.data file with the same name should be used in this test.
*/ */
void testConstructor(string dataFilename, string codeDummy) @safe static void testConstructor(string dataFilename, string codeDummy) @safe
{ {
string base = dataFilename.baseName.stripExtension; string base = dataFilename.baseName.stripExtension;
enforce((base in expected) !is null, assert((base in expected) !is null, "Unimplemented constructor test: " ~ base);
new Exception("Unimplemented constructor test: " ~ base));
auto loader = Loader.fromFile(dataFilename); auto loader = Loader.fromFile(dataFilename);
@ -942,28 +948,10 @@ void testConstructor(string dataFilename, string codeDummy) @safe
size_t i; size_t i;
foreach (node; loader) foreach (node; loader)
{ {
if(node != exp[i]) assertNodesEqual(node, exp[i]);
{
static if(verbose)
{
writeln("Expected value:");
writeln(exp[i].debugString);
writeln("\n");
writeln("Actual value:");
writeln(node.debugString);
}
assert(false);
}
++i; ++i;
} }
assert(i == exp.length); assert(i == exp.length);
} }
run(&testConstructor, ["data", "code"]);
@safe unittest
{
printProgress("D:YAML Constructor unittest");
run("testConstructor", &testConstructor, ["data", "code"]);
} }
} // version(unittest)

View file

@ -6,22 +6,18 @@
module dyaml.test.emitter; module dyaml.test.emitter;
@safe unittest
version(unittest)
{ {
import std.array : Appender;
import std.range : ElementType, isInputRange;
import std.algorithm; import dyaml : CollectionStyle, LineBreak, Loader, Mark, ScalarStyle;
import std.file; import dyaml.emitter : Emitter;
import std.range; import dyaml.event : Event, EventID, mappingStartEvent, scalarEvent, sequenceStartEvent;
import std.typecons; import dyaml.test.common : assertEventsEqual, run;
import dyaml.emitter;
import dyaml.event;
import dyaml.test.common;
import dyaml.token;
// Try to emit an event range. // Try to emit an event range.
void emitTestCommon(T)(ref Appender!string emitStream, T events, bool canonical = false) @safe static void emitTestCommon(T)(ref Appender!string emitStream, T events, bool canonical = false) @safe
if (isInputRange!T && is(ElementType!T == Event)) if (isInputRange!T && is(ElementType!T == Event))
{ {
auto emitter = Emitter!(typeof(emitStream), char)(emitStream, canonical, 2, 80, LineBreak.unix); auto emitter = Emitter!(typeof(emitStream), char)(emitStream, canonical, 2, 80, LineBreak.unix);
@ -30,60 +26,17 @@ void emitTestCommon(T)(ref Appender!string emitStream, T events, bool canonical
emitter.emit(event); emitter.emit(event);
} }
} }
/**
Test emitter by getting events from parsing a file, emitting them, parsing
the emitted result and comparing events from parsing the emitted result with
originally parsed events.
/// Determine if events in events1 are equivalent to events in events2. Params:
/// dataFilename = YAML file to parse.
/// Params: events1 = First event array to compare. canonicalFilename = Canonical YAML file used as dummy to determine
/// events2 = Second event array to compare. which data files to load.
/// */
/// Returns: true if the events are equivalent, false otherwise. static void testEmitterOnData(string dataFilename, string canonicalFilename) @safe
bool compareEvents(T, U)(T events1, U events2)
if (isInputRange!T && isInputRange!U && is(ElementType!T == Event) && is(ElementType!U == Event))
{
foreach (e1, e2; zip(events1, events2))
{
//Different event types.
if(e1.id != e2.id){return false;}
//Different anchor (if applicable).
if(e1.id.among!(EventID.sequenceStart,
EventID.mappingStart,
EventID.alias_,
EventID.scalar)
&& e1.anchor != e2.anchor)
{
return false;
}
//Different collection tag (if applicable).
if(e1.id.among!(EventID.sequenceStart, EventID.mappingStart) && e1.tag != e2.tag)
{
return false;
}
if(e1.id == EventID.scalar)
{
//Different scalar tag (if applicable).
if(!(e1.implicit || e2.implicit)
&& e1.tag != e2.tag)
{
return false;
}
//Different scalar value.
if(e1.value != e2.value)
{
return false;
}
}
}
return true;
}
/// Test emitter by getting events from parsing a file, emitting them, parsing
/// the emitted result and comparing events from parsing the emitted result with
/// originally parsed events.
///
/// Params: dataFilename = YAML file to parse.
/// canonicalFilename = Canonical YAML file used as dummy to determine
/// which data files to load.
void testEmitterOnData(string dataFilename, string canonicalFilename) @safe
{ {
//Must exist due to Anchor, Tags reference counts. //Must exist due to Anchor, Tags reference counts.
auto loader = Loader.fromFile(dataFilename); auto loader = Loader.fromFile(dataFilename);
@ -91,25 +44,19 @@ void testEmitterOnData(string dataFilename, string canonicalFilename) @safe
auto emitStream = Appender!string(); auto emitStream = Appender!string();
emitTestCommon(emitStream, events); emitTestCommon(emitStream, events);
static if(verbose)
{
writeln(dataFilename);
writeln("ORIGINAL:\n", readText(dataFilename));
writeln("OUTPUT:\n", emitStream.data);
}
auto loader2 = Loader.fromString(emitStream.data); auto loader2 = Loader.fromString(emitStream.data);
loader2.name = "TEST"; loader2.name = "TEST";
auto newEvents = loader2.parse(); auto newEvents = loader2.parse();
assert(compareEvents(events, newEvents)); assertEventsEqual(events, newEvents);
} }
/**
Test emitter by getting events from parsing a canonical YAML file, emitting
them both in canonical and normal format, parsing the emitted results and
comparing events from parsing the emitted result with originally parsed events.
/// Test emitter by getting events from parsing a canonical YAML file, emitting Params: canonicalFilename = Canonical YAML file to parse.
/// them both in canonical and normal format, parsing the emitted results and */
/// comparing events from parsing the emitted result with originally parsed events. static void testEmitterOnCanonical(string canonicalFilename) @safe
///
/// Params: canonicalFilename = Canonical YAML file to parse.
void testEmitterOnCanonical(string canonicalFilename) @safe
{ {
//Must exist due to Anchor, Tags reference counts. //Must exist due to Anchor, Tags reference counts.
auto loader = Loader.fromFile(canonicalFilename); auto loader = Loader.fromFile(canonicalFilename);
@ -118,26 +65,24 @@ void testEmitterOnCanonical(string canonicalFilename) @safe
{ {
auto emitStream = Appender!string(); auto emitStream = Appender!string();
emitTestCommon(emitStream, events, canonical); emitTestCommon(emitStream, events, canonical);
static if(verbose)
{
writeln("OUTPUT (canonical=", canonical, "):\n",
emitStream.data);
}
auto loader2 = Loader.fromString(emitStream.data); auto loader2 = Loader.fromString(emitStream.data);
loader2.name = "TEST"; loader2.name = "TEST";
auto newEvents = loader2.parse(); auto newEvents = loader2.parse();
assert(compareEvents(events, newEvents)); assertEventsEqual(events, newEvents);
} }
} }
/**
Test emitter by getting events from parsing a file, emitting them with all
possible scalar and collection styles, parsing the emitted results and
comparing events from parsing the emitted result with originally parsed events.
/// Test emitter by getting events from parsing a file, emitting them with all Params:
/// possible scalar and collection styles, parsing the emitted results and dataFilename = YAML file to parse.
/// comparing events from parsing the emitted result with originally parsed events. canonicalFilename = Canonical YAML file used as dummy to determine
/// which data files to load.
/// Params: dataFilename = YAML file to parse. */
/// canonicalFilename = Canonical YAML file used as dummy to determine static void testEmitterStyles(string dataFilename, string canonicalFilename) @safe
/// which data files to load.
void testEmitterStyles(string dataFilename, string canonicalFilename) @safe
{ {
foreach (filename; [dataFilename, canonicalFilename]) foreach (filename; [dataFilename, canonicalFilename])
{ {
@ -173,27 +118,15 @@ void testEmitterStyles(string dataFilename, string canonicalFilename) @safe
} }
auto emitStream = Appender!string(); auto emitStream = Appender!string();
emitTestCommon(emitStream, styledEvents); emitTestCommon(emitStream, styledEvents);
static if(verbose)
{
writeln("OUTPUT (", filename, ", ", to!string(flowStyle), ", ",
to!string(style), ")");
writeln(emitStream.data);
}
auto loader2 = Loader.fromString(emitStream.data); auto loader2 = Loader.fromString(emitStream.data);
loader2.name = "TEST"; loader2.name = "TEST";
auto newEvents = loader2.parse(); auto newEvents = loader2.parse();
assert(compareEvents(events, newEvents)); assertEventsEqual(events, newEvents);
} }
} }
} }
} }
run(&testEmitterOnData, ["data", "canonical"]);
@safe unittest run(&testEmitterOnCanonical, ["canonical"]);
{ run(&testEmitterStyles, ["data", "canonical"]);
printProgress("D:YAML Emitter unittest");
run("testEmitterOnData", &testEmitterOnData, ["data", "canonical"]);
run("testEmitterOnCanonical", &testEmitterOnCanonical, ["canonical"]);
run("testEmitterStyles", &testEmitterStyles, ["data", "canonical"]);
} }
} // version(unittest)

View file

@ -6,86 +6,59 @@
module dyaml.test.errors; module dyaml.test.errors;
version(unittest)
{
import std.file;
import dyaml.test.common;
/// Loader error unittest from file stream.
///
/// Params: errorFilename = File name to read from.
void testLoaderError(string errorFilename) @safe
{
import std.array : array;
Node[] nodes;
try { nodes = Loader.fromFile(errorFilename).array; }
catch(YAMLException e)
{
printException(e);
return;
}
assert(false, "Expected an exception");
}
/// Loader error unittest from string.
///
/// Params: errorFilename = File name to read from.
void testLoaderErrorString(string errorFilename) @safe
{
import std.array : array;
try
{
auto nodes = Loader.fromFile(errorFilename).array;
}
catch(YAMLException e)
{
printException(e);
return;
}
assert(false, "Expected an exception");
}
/// Loader error unittest from filename.
///
/// Params: errorFilename = File name to read from.
void testLoaderErrorFilename(string errorFilename) @safe
{
import std.array : array;
try { auto nodes = Loader.fromFile(errorFilename).array; }
catch(YAMLException e)
{
printException(e);
return;
}
assert(false, "testLoaderErrorSingle(" ~ ", " ~ errorFilename ~
") Expected an exception");
}
/// Loader error unittest loading a single document from a file.
///
/// Params: errorFilename = File name to read from.
void testLoaderErrorSingle(string errorFilename) @safe
{
try { auto nodes = Loader.fromFile(errorFilename).load(); }
catch(YAMLException e)
{
printException(e);
return;
}
assert(false, "Expected an exception");
}
@safe unittest @safe unittest
{ {
printProgress("D:YAML Errors unittest"); import std.array : array;
run("testLoaderError", &testLoaderError, ["loader-error"]); import std.exception : assertThrown;
run("testLoaderErrorString", &testLoaderErrorString, ["loader-error"]);
run("testLoaderErrorFilename", &testLoaderErrorFilename, ["loader-error"]); import dyaml : Loader;
run("testLoaderErrorSingle", &testLoaderErrorSingle, ["single-loader-error"]); import dyaml.test.common : run;
/**
Loader error unittest from file stream.
Params: errorFilename = File name to read from.
*/
static void testLoaderError(string errorFilename) @safe
{
assertThrown(Loader.fromFile(errorFilename).array,
__FUNCTION__ ~ "(" ~ errorFilename ~ ") Expected an exception");
} }
} // version(unittest) /**
Loader error unittest from string.
Params: errorFilename = File name to read from.
*/
static void testLoaderErrorString(string errorFilename) @safe
{
assertThrown(Loader.fromFile(errorFilename).array,
__FUNCTION__ ~ "(" ~ errorFilename ~ ") Expected an exception");
}
/**
Loader error unittest from filename.
Params: errorFilename = File name to read from.
*/
static void testLoaderErrorFilename(string errorFilename) @safe
{
assertThrown(Loader.fromFile(errorFilename).array,
__FUNCTION__ ~ "(" ~ errorFilename ~ ") Expected an exception");
}
/**
Loader error unittest loading a single document from a file.
Params: errorFilename = File name to read from.
*/
static void testLoaderErrorSingle(string errorFilename) @safe
{
assertThrown(Loader.fromFile(errorFilename).load(),
__FUNCTION__ ~ "(" ~ errorFilename ~ ") Expected an exception");
}
run(&testLoaderError, ["loader-error"]);
run(&testLoaderErrorString, ["loader-error"]);
run(&testLoaderErrorFilename, ["loader-error"]);
run(&testLoaderErrorSingle, ["single-loader-error"]);
}

View file

@ -6,46 +6,57 @@
module dyaml.test.inputoutput; module dyaml.test.inputoutput;
@safe unittest
version(unittest)
{ {
import std.array : join, split;
import std.conv : to;
import std.exception : assertThrown;
import std.file : readText;
import std.system : endian, Endian;
import std.array; import dyaml : Loader, Node, YAMLException;
import std.file; import dyaml.test.common : run;
import std.system;
import dyaml.test.common; /**
Get an UTF-16 byte order mark.
/// Get an UTF-16 byte order mark. Params: wrong = Get the incorrect BOM for this system.
///
/// Params: wrong = Get the incorrect BOM for this system. Returns: UTF-16 byte order mark.
/// */
/// Returns: UTF-16 byte order mark. static wchar bom16(bool wrong = false) pure @safe
wchar bom16(bool wrong = false) pure @safe
{ {
wchar little = '\uFEFF'; wchar little = '\uFEFF';
wchar big = '\uFFFE'; wchar big = '\uFFFE';
if(!wrong){return endian == Endian.littleEndian ? little : big;} if (!wrong)
{
return endian == Endian.littleEndian ? little : big;
}
return endian == Endian.littleEndian ? big : little; return endian == Endian.littleEndian ? big : little;
} }
/**
Get an UTF-32 byte order mark.
/// Get an UTF-32 byte order mark. Params: wrong = Get the incorrect BOM for this system.
///
/// Params: wrong = Get the incorrect BOM for this system. Returns: UTF-32 byte order mark.
/// */
/// Returns: UTF-32 byte order mark. static dchar bom32(bool wrong = false) pure @safe
dchar bom32(bool wrong = false) pure @safe
{ {
dchar little = '\uFEFF'; dchar little = '\uFEFF';
dchar big = '\uFFFE'; dchar big = '\uFFFE';
if(!wrong){return endian == Endian.littleEndian ? little : big;} if (!wrong)
{
return endian == Endian.littleEndian ? little : big;
}
return endian == Endian.littleEndian ? big : little; return endian == Endian.littleEndian ? big : little;
} }
/**
Unicode input unittest. Tests various encodings.
/// Unicode input unittest. Tests various encodings. Params: unicodeFilename = File name to read from.
/// */
/// Params: unicodeFilename = File name to read from. static void testUnicodeInput(string unicodeFilename) @safe
void testUnicodeInput(string unicodeFilename) @safe
{ {
string data = readText(unicodeFilename); string data = readText(unicodeFilename);
string expected = data.split().join(" "); string expected = data.split().join(" ");
@ -60,11 +71,12 @@ void testUnicodeInput(string unicodeFilename) @safe
assert(output.as!string == expected); assert(output.as!string == expected);
} }
} }
/**
Unicode input error unittest. Tests various encodings with incorrect BOMs.
/// Unicode input error unittest. Tests various encodings with incorrect BOMs. Params: unicodeFilename = File name to read from.
/// */
/// Params: unicodeFilename = File name to read from. static void testUnicodeInputErrors(string unicodeFilename) @safe
void testUnicodeInputErrors(string unicodeFilename) @safe
{ {
string data = readText(unicodeFilename); string data = readText(unicodeFilename);
foreach (buffer; [cast(ubyte[]) (data.to!(wchar[])), foreach (buffer; [cast(ubyte[]) (data.to!(wchar[])),
@ -72,22 +84,9 @@ void testUnicodeInputErrors(string unicodeFilename) @safe
cast(ubyte[]) (bom16(true) ~ data.to!(wchar[])), cast(ubyte[]) (bom16(true) ~ data.to!(wchar[])),
cast(ubyte[]) (bom32(true) ~ data.to!(dchar[]))]) cast(ubyte[]) (bom32(true) ~ data.to!(dchar[]))])
{ {
try { Loader.fromBuffer(buffer).load(); } assertThrown(Loader.fromBuffer(buffer).load());
catch(YAMLException e)
{
printException(e);
continue;
}
assert(false, "Expected an exception");
} }
} }
run(&testUnicodeInput, ["unicode"]);
run(&testUnicodeInputErrors, ["unicode"]);
@safe unittest
{
printProgress("D:YAML I/O unittest");
run("testUnicodeInput", &testUnicodeInput, ["unicode"]);
run("testUnicodeInputErrors", &testUnicodeInputErrors, ["unicode"]);
} }
} // version(unittest)

View file

@ -6,52 +6,32 @@
module dyaml.test.reader; module dyaml.test.reader;
@safe unittest
version(unittest)
{ {
import std.exception :assertThrown;
import dyaml.test.common; import dyaml.test.common : readData, run;
import dyaml.reader; import dyaml.reader : Reader, ReaderException;
/**
Try reading entire file through Reader, expecting an error (the file is invalid).
// Try reading entire file through Reader, expecting an error (the file is invalid). Params: data = Stream to read.
// */
// Params: data = Stream to read. static void runReader(ubyte[] fileData) @safe
void runReader(ubyte[] fileData) @safe
{
try
{ {
auto reader = new Reader(fileData); auto reader = new Reader(fileData);
while(reader.peek() != '\0') { reader.forward(); } while(reader.peek() != '\0') { reader.forward(); }
} }
catch(ReaderException e)
/**
Stream error unittest. Tries to read invalid input files, expecting errors.
Params: errorFilename = File name to read from.
*/
static void testStreamError(string errorFilename) @safe
{ {
printException(e); assertThrown!ReaderException(runReader(readData(errorFilename)));
return;
} }
assert(false, "Expected an exception"); run(&testStreamError, ["stream-error"]);
} }
/// Stream error unittest. Tries to read invalid input files, expecting errors.
///
/// Params: errorFilename = File name to read from.
void testStreamError(string errorFilename) @safe
{
runReader(readData(errorFilename));
}
// TODO: remove when a @safe ubyte[] file read can be done.
ubyte[] readData(string filename) @trusted
{
import std.file;
return cast(ubyte[])std.file.read(filename);
}
@safe unittest
{
printProgress("D:YAML Reader unittest");
run("testStreamError", &testStreamError, ["stream-error"]);
}
} // version(unittest)

View file

@ -6,74 +6,49 @@
module dyaml.test.representer; module dyaml.test.representer;
@safe unittest
version(unittest)
{ {
import std.array : Appender, array;
import std.meta : AliasSeq;
import std.path : baseName, stripExtension;
import std.utf : toUTF8;
import std.array; import dyaml : dumper, Loader, Node;
import std.exception; import dyaml.test.common : assertNodesEqual, run;
import std.meta; import dyaml.test.constructor : expected;
import std.path;
import std.typecons;
import std.utf;
import dyaml.test.common; /**
import dyaml.test.constructor; Representer unittest. Dumps nodes, then loads them again.
Params:
/// Representer unittest. baseName = Nodes in dyaml.test.constructor.expected for roundtripping.
/// */
/// Params: codeFilename = File name to determine test case from. static void testRepresenterTypes(string baseName) @safe
/// Nothing is read from this file, it only exists
/// to specify that we need a matching unittest.
void testRepresenterTypes(string codeFilename) @safe
{ {
string baseName = codeFilename.baseName.stripExtension; assert((baseName in expected) !is null, "Unimplemented representer test: " ~ baseName);
enforce((baseName in dyaml.test.constructor.expected) !is null,
new Exception("Unimplemented representer test: " ~ baseName));
Node[] expectedNodes = expected[baseName]; Node[] expectedNodes = expected[baseName];
foreach (encoding; AliasSeq!(char, wchar, dchar)) foreach (encoding; AliasSeq!(char, wchar, dchar))
{ {
immutable(encoding)[] output;
Node[] readNodes;
scope(failure)
{
static if(verbose)
{
writeln("Expected nodes:");
foreach(ref n; expectedNodes){writeln(n.debugString, "\n---\n");}
writeln("Read nodes:");
foreach(ref n; readNodes){writeln(n.debugString, "\n---\n");}
() @trusted {
writeln("OUTPUT:\n", cast(string)output);
}();
}
}
auto emitStream = new Appender!(immutable(encoding)[]); auto emitStream = new Appender!(immutable(encoding)[]);
auto dumper = dumper(); auto dumper = dumper();
dumper.dump!encoding(emitStream, expectedNodes); dumper.dump!encoding(emitStream, expectedNodes);
output = emitStream.data; immutable output = emitStream.data;
auto loader = Loader.fromString(emitStream.data.toUTF8); auto loader = Loader.fromString(emitStream.data.toUTF8);
loader.name = "TEST"; loader.name = "TEST";
readNodes = loader.array; const readNodes = loader.array;
assert(expectedNodes.length == readNodes.length); assert(expectedNodes.length == readNodes.length);
foreach (n; 0 .. expectedNodes.length) foreach (n; 0 .. expectedNodes.length)
{ {
assert(expectedNodes[n] == readNodes[n]); assertNodesEqual(expectedNodes[n], readNodes[n]);
} }
} }
} }
foreach (key, _; expected)
@safe unittest
{ {
printProgress("D:YAML Representer unittest"); testRepresenterTypes(key);
run("testRepresenterTypes", &testRepresenterTypes, ["code"]); }
} }
} // version(unittest)

View file

@ -6,52 +6,34 @@
module dyaml.test.resolver; module dyaml.test.resolver;
@safe unittest
version(unittest)
{ {
import std.conv : text;
import std.file : readText;
import std.string : strip;
import std.file; import dyaml : Loader, Node, NodeID;
import std.string; import dyaml.test.common : run;
import dyaml.test.common;
/** /**
* Implicit tag resolution unittest. Implicit tag resolution unittest.
*
* Params: dataFilename = File with unittest data. Params:
* detectFilename = Dummy filename used to specify which data filenames to use. dataFilename = File with unittest data.
detectFilename = Dummy filename used to specify which data filenames to use.
*/ */
void testImplicitResolver(string dataFilename, string detectFilename) @safe static void testImplicitResolver(string dataFilename, string detectFilename) @safe
{ {
string correctTag; const correctTag = readText(detectFilename).strip();
Node node;
scope(failure) auto node = Loader.fromFile(dataFilename).load();
assert(node.nodeID == NodeID.sequence, text("Expected sequence when reading '", dataFilename, "', got ", node.nodeID));
foreach (Node scalar; node)
{ {
if(true) assert(scalar.nodeID == NodeID.scalar, text("Expected sequence of scalars when reading '", dataFilename, "', got sequence of ", scalar.nodeID));
{ assert(scalar.tag == correctTag, text("Expected tag '", correctTag, "' when reading '", dataFilename, "', got '", scalar.tag, "'"));
writeln("Correct tag: ", correctTag);
writeln("Node: ", node.debugString);
} }
} }
run(&testImplicitResolver, ["data", "detect"]);
correctTag = readText(detectFilename).strip();
node = Loader.fromFile(dataFilename).load();
assert(node.nodeID == NodeID.sequence);
foreach(ref Node scalar; node)
{
assert(scalar.nodeID == NodeID.scalar);
assert(scalar.tag == correctTag);
} }
}
@safe unittest
{
printProgress("D:YAML Resolver unittest");
run("testImplicitResolver", &testImplicitResolver, ["data", "detect"]);
}
} // version(unittest)

View file

@ -6,39 +6,32 @@
module dyaml.test.tokens; module dyaml.test.tokens;
@safe unittest
version(unittest)
{ {
import std.array : split;
import std.conv : text;
import std.file : readText;
import std.array; import dyaml.test.common : run;
import std.file; import dyaml.reader : Reader;
import dyaml.scanner : Scanner;
import dyaml.token : TokenID;
import dyaml.test.common; // Read and scan a YAML doc, returning a range of tokens.
import dyaml.reader; static auto scanTestCommon(string filename) @safe
import dyaml.scanner;
import dyaml.token;
// Read and scan a YAML doc, returning the tokens.
const(Token)[] scanTestCommon(string filename) @safe
{ {
ubyte[] yamlData; ubyte[] yamlData = cast(ubyte[])readText(filename).dup;
() @trusted { yamlData = cast(ubyte[])std.file.read(filename); }(); return Scanner(new Reader(yamlData));
auto scanner = Scanner(new Reader(yamlData));
const(Token)[] result;
foreach (token; scanner)
{
result ~= token;
}
return result;
} }
/** /**
* Test tokens output by scanner. Test tokens output by scanner.
*
* Params: dataFilename = File to scan. Params:
* tokensFilename = File containing expected tokens. dataFilename = File to scan.
tokensFilename = File containing expected tokens.
*/ */
void testTokens(string dataFilename, string tokensFilename) @safe static void testTokens(string dataFilename, string tokensFilename) @safe
{ {
//representations of YAML tokens in tokens file. //representations of YAML tokens in tokens file.
auto replace = [ auto replace = [
@ -62,51 +55,39 @@ void testTokens(string dataFilename, string tokensFilename) @safe
TokenID.value: ":" TokenID.value: ":"
]; ];
string[] tokens1; string[] tokens;
string[] tokens2 = readText(tokensFilename).split(); string[] expectedTokens = readText(tokensFilename).split();
scope(exit)
{
static if(verbose){writeln("tokens1: ", tokens1, "\ntokens2: ", tokens2);}
}
foreach (token; scanTestCommon(dataFilename)) foreach (token; scanTestCommon(dataFilename))
{ {
if (token.id != TokenID.streamStart && token.id != TokenID.streamEnd) if (token.id != TokenID.streamStart && token.id != TokenID.streamEnd)
{ {
tokens1 ~= replace[token.id]; tokens ~= replace[token.id];
} }
} }
assert(tokens1 == tokens2); assert(tokens == expectedTokens,
text("In token test for '", tokensFilename, "', expected '", expectedTokens, "', got '", tokens, "'"));
} }
/** /**
* Test scanner by scanning a file, expecting no errors. Test scanner by scanning a file, expecting no errors.
*
* Params: dataFilename = File to scan. Params:
* canonicalFilename = Another file to scan, in canonical YAML format. dataFilename = File to scan.
canonicalFilename = Another file to scan, in canonical YAML format.
*/ */
void testScanner(string dataFilename, string canonicalFilename) @safe static void testScanner(string dataFilename, string canonicalFilename) @safe
{ {
foreach (filename; [dataFilename, canonicalFilename]) foreach (filename; [dataFilename, canonicalFilename])
{ {
string[] tokens; string[] tokens;
scope(exit) foreach (token; scanTestCommon(filename))
{ {
static if(verbose){writeln(tokens);} tokens ~= token.id.text;
}
foreach(ref token; scanTestCommon(filename))
{
tokens ~= to!string(token.id);
} }
} }
} }
run(&testTokens, ["data", "tokens"]);
@safe unittest run(&testScanner, ["data", "canonical"]);
{
printProgress("D:YAML tokens unittest");
run("testTokens", &testTokens, ["data", "tokens"]);
run("testScanner", &testScanner, ["data", "canonical"]);
} }
} // version(unittest)