// Copyright Ferdinand Majerech 2011. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) module dyaml.test.common; version(unittest) { import dyaml.node; import dyaml.event; import core.exception; import std.algorithm; import std.array; import std.conv; import std.file; import std.range; import std.path; import std.traits; import std.typecons; package: /** Run a test. Params: testFunction = Unittest function. unittestExt = Extensions of data files needed for the unittest. skipExt = Extensions that must not be used for the unittest. */ void run(D)(D testFunction, string[] unittestExt, string[] skipExt = []) { immutable string dataDir = __FILE_FULL_PATH__.dirName ~ "/../../../test/data"; auto testFilenames = findTestFilenames(dataDir); if (unittestExt.length > 0) { outer: foreach (base, extensions; testFilenames) { string[] filenames; foreach (ext; unittestExt) { if (!extensions.canFind(ext)) { continue outer; } filenames ~= base ~ '.' ~ ext; } foreach (ext; skipExt) { if (extensions.canFind(ext)) { continue outer; } } execute(testFunction, filenames); } } else { execute(testFunction, string[].init); } } // 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)); } /** Determine if events in events1 are equivalent to events in events2. 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)) { 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; } /** 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: /** Find unittest input filenames. Params: dir = Directory to look in. Returns: Test input base filenames and their extensions. */ //@trusted due to dirEntries string[][string] findTestFilenames(const string dir) @trusted { //Groups of extensions indexed by base names. string[][string] names; foreach (string name; dirEntries(dir, SpanMode.shallow)) { if (isFile(name)) { string base = name.stripExtension(); string ext = name.extension(); if (ext is null) { ext = ""; } if (ext[0] == '.') { ext = ext[1 .. $]; } //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; } } return names; } /** Recursively copy an array of strings to a tuple to use for unittest function input. Params: index = Current index in the array/tuple. tuple = Tuple to copy to. strings = Strings to copy. */ void stringsToTuple(uint index, F ...)(ref F tuple, const string[] strings) in(F.length == strings.length) do { tuple[index] = strings[index]; static if (index > 0) { stringsToTuple!(index - 1, F)(tuple, strings); } } /** Execute an unittest on specified files. Params: testName = Name of the unittest. 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. alias F = Parameters!D[0..$]; F parameters; stringsToTuple!(F.length - 1, F)(parameters, filenames); testFunction(parameters); } } // version(unittest)