1 module unit_threaded.reflection;
2
3 import std.traits: isSomeString;
4 import std.meta: allSatisfy, anySatisfy;
5 import std.traits;
6 import unit_threaded.uda;
7
8 /**
9 * Common data for test functions and test classes
10 */
11 alias void delegate() TestFunction;
12 struct TestData {
13 string name;
14 TestFunction testFunction; ///only used for functions, null for classes
15 bool hidden;
16 bool shouldFail;
17 bool singleThreaded;
18 bool builtin;
19 string suffix; // append to end of getPath
20 string[] tags;
21 TypeInfo exceptionTypeInfo; // for ShouldFailWith
22
23 string getPath() const pure nothrow {
24 string path = name.dup;
25 import std.array: empty;
26 if(!suffix.empty) path ~= "." ~ suffix;
27 return path;
28 }
29
30 bool isTestClass() @safe const pure nothrow {
31 return testFunction is null;
32 }
33 }
34
35
36 /**
37 * Finds all test cases (functions, classes, built-in unittest blocks)
38 * Template parameters are module strings
39 */
40 const(TestData)[] allTestData(MOD_STRINGS...)() if(allSatisfy!(isSomeString, typeof(MOD_STRINGS))) {
41 import std.array: join;
42 import std.range : iota;
43 import std.format : format;
44 import std.algorithm : map;
45
46 string getModulesString() {
47 string[] modules;
48 foreach(i, module_; MOD_STRINGS) modules ~= "module%d = %s".format(i, module_);
49 return modules.join(", ");
50 }
51
52 enum modulesString = getModulesString;
53 mixin("import " ~ modulesString ~ ";");
54 mixin("return allTestData!(" ~ 0.iota(MOD_STRINGS.length).map!(i => "module%d".format(i)).join(", ") ~ ");");
55 }
56
57
58 /**
59 * Finds all test cases (functions, classes, built-in unittest blocks)
60 * Template parameters are module symbols
61 */
62 const(TestData)[] allTestData(MOD_SYMBOLS...)() if(!anySatisfy!(isSomeString, typeof(MOD_SYMBOLS))) {
63 auto allTestsWithFunc(string expr, MOD_SYMBOLS...)() pure {
64 import std.traits: ReturnType;
65 import std.meta: AliasSeq;
66 //tests is whatever type expr returns
67 ReturnType!(mixin(expr ~ q{!(MOD_SYMBOLS[0])})) tests;
68 foreach(module_; AliasSeq!MOD_SYMBOLS) {
69 tests ~= mixin(expr ~ q{!module_()}); //e.g. tests ~= moduleTestClasses!module_
70 }
71 return tests;
72 }
73
74 return allTestsWithFunc!(q{moduleTestClasses}, MOD_SYMBOLS) ~
75 allTestsWithFunc!(q{moduleTestFunctions}, MOD_SYMBOLS) ~
76 allTestsWithFunc!(q{moduleUnitTests}, MOD_SYMBOLS);
77 }
78
79
80 private template Identity(T...) if(T.length > 0) {
81 static if(__traits(compiles, { alias x = T[0]; }))
82 alias Identity = T[0];
83 else
84 enum Identity = T[0];
85 }
86
87
88 /**
89 * Finds all built-in unittest blocks in the given module.
90 * Recurses into structs, classes, and unions of the module.
91 *
92 * @return An array of TestData structs
93 */
94 TestData[] moduleUnitTests(alias module_)() pure nothrow {
95
96 // Return a name for a unittest block. If no @Name UDA is found a name is
97 // created automatically, else the UDA is used.
98 // the weird name for the first template parameter is so that it doesn't clash
99 // with a package name
100 string unittestName(alias _theUnitTest, int index)() @safe nothrow {
101 import std.conv: text, to;
102 import std.traits: fullyQualifiedName;
103 import std.traits: getUDAs;
104 import std.meta: Filter;
105 import unit_threaded.attrs: Name;
106
107 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible
108
109 enum nameAttrs = getUDAs!(_theUnitTest, Name);
110 static assert(nameAttrs.length == 0 || nameAttrs.length == 1, "Found multiple Name UDAs on unittest");
111
112 enum strAttrs = Filter!(isStringUDA, __traits(getAttributes, _theUnitTest));
113 enum hasName = nameAttrs.length || strAttrs.length == 1;
114 enum prefix = fullyQualifiedName!(__traits(parent, _theUnitTest)) ~ ".";
115
116 static if(hasName) {
117 static if(nameAttrs.length == 1)
118 return prefix ~ nameAttrs[0].value;
119 else
120 return prefix ~ strAttrs[0];
121 } else {
122 string name;
123 try {
124 return prefix ~ "unittest" ~ (index).to!string;
125 } catch(Exception) {
126 assert(false, text("Error converting ", index, " to string"));
127 }
128 }
129 }
130
131 void function() getUDAFunction(alias composite, alias uda)() pure nothrow {
132 import std.traits: moduleName, isSomeFunction, hasUDA;
133 mixin(`import ` ~ moduleName!composite ~ `;`);
134 void function()[] ret;
135 foreach(memberStr; __traits(allMembers, composite)) {
136 static if(__traits(compiles, Identity!(__traits(getMember, composite, memberStr)))) {
137 alias member = Identity!(__traits(getMember, composite, memberStr));
138 static if(__traits(compiles, &member)) {
139 static if(isSomeFunction!member && hasUDA!(member, uda)) {
140 ret ~= &member;
141 }
142 }
143 }
144 }
145
146 return ret.length ? ret[0] : null;
147 }
148
149 TestData[] testData;
150
151 void addMemberUnittests(alias composite)() pure nothrow {
152
153 import unit_threaded.attrs;
154 import unit_threaded.uda: hasUtUDA;
155 import std.traits: hasUDA;
156 import std.meta: Filter, aliasSeqOf;
157
158 foreach(index, eLtEstO; __traits(getUnitTests, composite)) {
159
160 enum dontTest = hasUDA!(eLtEstO, DontTest);
161
162 static if(!dontTest) {
163
164 enum name = unittestName!(eLtEstO, index);
165 enum hidden = hasUDA!(eLtEstO, HiddenTest);
166 enum shouldFail = hasUDA!(eLtEstO, ShouldFail) || hasUtUDA!(eLtEstO, ShouldFailWith);
167 enum singleThreaded = hasUDA!(eLtEstO, Serial);
168 enum builtin = true;
169 enum suffix = "";
170
171 // let's check for @Values UDAs, which are actually of type ValuesImpl
172 enum isValues(alias T) = is(typeof(T)) && is(typeof(T):ValuesImpl!U, U);
173 alias valuesUDAs = Filter!(isValues, __traits(getAttributes, eLtEstO));
174
175 enum isTags(alias T) = is(typeof(T)) && is(typeof(T) == Tags);
176 enum tags = tagsFromAttrs!(Filter!(isTags, __traits(getAttributes, eLtEstO)));
177 enum exceptionTypeInfo = getExceptionTypeInfo!eLtEstO;
178
179 static if(valuesUDAs.length == 0) {
180 testData ~= TestData(name,
181 () {
182 auto setup = getUDAFunction!(composite, Setup);
183 auto shutdown = getUDAFunction!(composite, Shutdown);
184
185 if(setup) setup();
186 scope(exit) if(shutdown) shutdown();
187
188 eLtEstO();
189 },
190 hidden,
191 shouldFail,
192 singleThreaded,
193 builtin,
194 suffix,
195 tags,
196 exceptionTypeInfo);
197 } else {
198 import std.range;
199
200 // cartesianProduct doesn't work with only one range, so in the usual case
201 // of only one @Values UDA, we bind to prod with a range of tuples, just
202 // as returned by cartesianProduct.
203
204 static if(valuesUDAs.length == 1) {
205 import std.typecons;
206 enum prod = valuesUDAs[0].values.map!(a => tuple(a));
207 } else {
208 mixin(`enum prod = cartesianProduct(` ~ valuesUDAs.length.iota.map!
209 (a => `valuesUDAs[` ~ guaranteedToString(a) ~ `].values`).join(", ") ~ `);`);
210 }
211
212 foreach(comb; aliasSeqOf!prod) {
213 enum valuesName = valuesName(comb);
214
215 static if(hasUDA!(eLtEstO, AutoTags))
216 enum realTags = tags ~ valuesName.split(".").array;
217 else
218 enum realTags = tags;
219
220 testData ~= TestData(name ~ "." ~ valuesName,
221 () {
222 foreach(i; aliasSeqOf!(comb.length.iota))
223 ValueHolder!(typeof(comb[i])).values[i] = comb[i];
224 eLtEstO();
225 },
226 hidden, shouldFail, singleThreaded, builtin, suffix, realTags, exceptionTypeInfo);
227
228 }
229 }
230 }
231 }
232 }
233
234
235 // Keeps track of mangled names of everything visited.
236 bool[string] visitedMembers;
237
238 void addUnitTestsRecursively(alias composite)() pure nothrow {
239 import std.traits: fullyQualifiedName;
240
241 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible
242
243 if (composite.mangleof in visitedMembers)
244 return;
245 visitedMembers[composite.mangleof] = true;
246 addMemberUnittests!composite();
247 foreach(member; __traits(allMembers, composite)){
248 enum notPrivate = __traits(compiles, mixin(member)); //only way I know to check if private
249 static if (
250 notPrivate &&
251 // If visibility of the member is deprecated, the next line still returns true
252 // and yet spills deprecation warning. If deprecation is turned into error,
253 // all works as intended.
254 __traits(compiles, __traits(getMember, composite, member)) &&
255 __traits(compiles, __traits(allMembers, __traits(getMember, composite, member))) &&
256 __traits(compiles, recurse!(__traits(getMember, composite, member)))
257 ) {
258 recurse!(__traits(getMember, composite, member));
259 }
260 }
261 }
262
263 void recurse(child)() pure nothrow {
264 enum notPrivate = __traits(compiles, child.init); //only way I know to check if private
265 static if (is(child == class) || is(child == struct) || is(child == union)) {
266 addUnitTestsRecursively!child;
267 }
268 }
269
270 addUnitTestsRecursively!module_();
271 return testData;
272 }
273
274 private TypeInfo getExceptionTypeInfo(alias Test)() {
275 import unit_threaded.should: UnitTestException;
276 import unit_threaded.uda: hasUtUDA, getUtUDAs;
277 import unit_threaded.attrs: ShouldFailWith;
278
279 static if(hasUtUDA!(Test, ShouldFailWith)) {
280 alias uda = getUtUDAs!(Test, ShouldFailWith)[0];
281 return typeid(uda.Type);
282 } else
283 return null;
284 }
285
286
287 private string valuesName(T)(T tuple) {
288 import std.range: iota;
289 import std.meta: aliasSeqOf;
290
291 string[] parts;
292 foreach(a; aliasSeqOf!(tuple.length.iota))
293 parts ~= guaranteedToString(tuple[a]);
294 return parts.join(".");
295 }
296
297 private string guaranteedToString(T)(T value) nothrow pure @safe {
298 import std.conv;
299 try
300 return value.to!string;
301 catch(Exception ex)
302 assert(0, "Could not convert value to string");
303 }
304
305 private string getValueAsString(T)(T value) nothrow pure @safe {
306 import std.conv;
307 try
308 return value.to!string;
309 catch(Exception ex)
310 assert(0, "Could not convert value to string");
311 }
312
313
314 private template isStringUDA(alias T) {
315 static if(__traits(compiles, isSomeString!(typeof(T))))
316 enum isStringUDA = isSomeString!(typeof(T));
317 else
318 enum isStringUDA = false;
319 }
320
321 unittest {
322 static assert(isStringUDA!"foo");
323 static assert(!isStringUDA!5);
324 }
325
326 private template isPrivate(alias module_, string moduleMember) {
327 import std.traits: fullyQualifiedName;
328
329 mixin(`import ` ~ fullyQualifiedName!module_ ~ `: ` ~ moduleMember ~ `;`);
330 static if(__traits(compiles, isSomeFunction!(mixin(moduleMember)))) {
331 alias member = Identity!(mixin(moduleMember));
332 static if(__traits(compiles, &member))
333 enum isPrivate = false;
334 else static if(__traits(compiles, new member))
335 enum isPrivate = false;
336 else static if(__traits(compiles, HasTypes!member))
337 enum isPrivate = !HasTypes!member;
338 else
339 enum isPrivate = true;
340 } else {
341 enum isPrivate = true;
342 }
343 }
344
345
346 // if this member is a test function or class, given the predicate
347 private template PassesTestPred(alias module_, alias pred, string moduleMember) {
348 import std.traits: fullyQualifiedName;
349 import unit_threaded.meta: importMember;
350 import unit_threaded.uda: HasAttribute;
351 import unit_threaded.attrs: DontTest;
352
353 //should be the line below instead but a compiler bug prevents it
354 //mixin(importMember!module_(moduleMember));
355 mixin("import " ~ fullyQualifiedName!module_ ~ ";");
356 alias I(T...) = T;
357 static if(!__traits(compiles, I!(__traits(getMember, module_, moduleMember)))) {
358 enum PassesTestPred = false;
359 } else {
360 alias member = I!(__traits(getMember, module_, moduleMember));
361
362 template canCheckIfSomeFunction(T...) {
363 enum canCheckIfSomeFunction = T.length == 1 && __traits(compiles, isSomeFunction!(T[0]));
364 }
365
366 private string funcCallMixin(alias T)() {
367 import std.conv: to;
368 string[] args;
369 foreach(i, ParamType; Parameters!T) {
370 args ~= `arg` ~ i.to!string;
371 }
372
373 return moduleMember ~ `(` ~ args.join(`,`) ~ `);`;
374 }
375
376 private string argsMixin(alias T)() {
377 import std.conv: to;
378 string[] args;
379 foreach(i, ParamType; Parameters!T) {
380 args ~= ParamType.stringof ~ ` arg` ~ i.to!string ~ `;`;
381 }
382
383 return args.join("\n");
384 }
385
386 template canCallMember() {
387 void _f() {
388 mixin(argsMixin!member);
389 mixin(funcCallMixin!member);
390 }
391 }
392
393 template canInstantiate() {
394 void _f() {
395 mixin(`auto _ = new ` ~ moduleMember ~ `;`);
396 }
397 }
398
399 template isPrivate() {
400 static if(!canCheckIfSomeFunction!member) {
401 enum isPrivate = !__traits(compiles, __traits(getMember, module_, moduleMember));
402 } else {
403 static if(isSomeFunction!member) {
404 enum isPrivate = !__traits(compiles, canCallMember!());
405 } else static if(is(member)) {
406 static if(isAggregateType!member)
407 enum isPrivate = !__traits(compiles, canInstantiate!());
408 else
409 enum isPrivate = !__traits(compiles, __traits(getMember, module_, moduleMember));
410 } else {
411 enum isPrivate = !__traits(compiles, __traits(getMember, module_, moduleMember));
412 }
413 }
414 }
415
416 enum notPrivate = !isPrivate!();
417 enum PassesTestPred = !isPrivate!() && pred!(module_, moduleMember) &&
418 !HasAttribute!(module_, moduleMember, DontTest);
419 }
420 }
421
422
423 /**
424 * Finds all test classes (classes implementing a test() function)
425 * in the given module
426 */
427 TestData[] moduleTestClasses(alias module_)() pure nothrow {
428
429 template isTestClass(alias module_, string moduleMember) {
430 import unit_threaded.meta: importMember;
431 import unit_threaded.uda: HasAttribute;
432 import unit_threaded.attrs: UnitTest;
433
434 mixin(importMember!module_(moduleMember));
435
436 alias member = Identity!(mixin(moduleMember));
437
438 static if(isPrivate!(module_, moduleMember)) {
439 enum isTestClass = false;
440 } else static if(!__traits(compiles, isAggregateType!(member))) {
441 enum isTestClass = false;
442 } else static if(!isAggregateType!(member)) {
443 enum isTestClass = false;
444 } else static if(!__traits(compiles, mixin("new " ~ moduleMember))) {
445 enum isTestClass = false; //can't new it, can't use it
446 } else {
447 enum hasUnitTest = HasAttribute!(module_, moduleMember, UnitTest);
448 enum hasTestMethod = __traits(hasMember, member, "test");
449 enum isTestClass = is(member == class) && (hasTestMethod || hasUnitTest);
450 }
451 }
452
453
454 return moduleTestData!(module_, isTestClass, memberTestData);
455 }
456
457
458 /**
459 * Finds all test functions in the given module.
460 * Returns an array of TestData structs
461 */
462 TestData[] moduleTestFunctions(alias module_)() pure {
463
464 enum isTypesAttr(alias T) = is(T) && is(T:Types!U, U...);
465
466 template isTestFunction(alias module_, string moduleMember) {
467 import unit_threaded.meta: importMember;
468 import unit_threaded.attrs: UnitTest;
469 import unit_threaded.uda: HasAttribute, GetTypes;
470 import std.meta: AliasSeq;
471 import std.traits: isSomeFunction;
472
473 mixin(importMember!module_(moduleMember));
474
475 static if(isPrivate!(module_, moduleMember)) {
476 enum isTestFunction = false;
477 } else static if(AliasSeq!(mixin(moduleMember)).length != 1) {
478 enum isTestFunction = false;
479 } else static if(isSomeFunction!(mixin(moduleMember))) {
480 enum isTestFunction = hasTestPrefix!(module_, moduleMember) ||
481 HasAttribute!(module_, moduleMember, UnitTest);
482 } else static if(__traits(compiles, __traits(getAttributes, mixin(moduleMember)))) {
483 // in this case we handle the possibility of a template function with
484 // the @Types UDA attached to it
485 alias types = GetTypes!(mixin(moduleMember));
486 enum isTestFunction = hasTestPrefix!(module_, moduleMember) &&
487 types.length > 0;
488 } else {
489 enum isTestFunction = false;
490 }
491
492 }
493
494 template hasTestPrefix(alias module_, string member) {
495 import std.uni: isUpper;
496 import unit_threaded.meta: importMember;
497
498 mixin(importMember!module_(member));
499
500 enum prefix = "test";
501 enum minSize = prefix.length + 1;
502
503 static if(member.length >= minSize && member[0 .. prefix.length] == prefix &&
504 isUpper(member[prefix.length])) {
505 enum hasTestPrefix = true;
506 } else {
507 enum hasTestPrefix = false;
508 }
509 }
510
511 return moduleTestData!(module_, isTestFunction, createFuncTestData);
512 }
513
514 private TestData[] createFuncTestData(alias module_, string moduleMember)() {
515 import unit_threaded.meta: importMember;
516 import unit_threaded.uda: GetAttributes, HasAttribute, GetTypes, HasTypes;
517 import unit_threaded.attrs;
518 import std.meta: aliasSeqOf;
519
520 mixin(importMember!module_(moduleMember));
521 /*
522 Get all the test functions for this module member. There might be more than one
523 when using parametrized unit tests.
524
525 Examples:
526 ------
527 void testFoo() {} // -> the array contains one element, testFoo
528 @(1, 2, 3) void testBar(int) {} // The array contains 3 elements, one for each UDA value
529 @Types!(int, float) void testBaz(T)() {} //The array contains 2 elements, one for each type
530 ------
531 */
532 // if the predicate returned true (which is always the case here), then it's either
533 // a regular function or a templated one. If regular we can get a pointer to it
534 enum isRegularFunction = __traits(compiles, &__traits(getMember, module_, moduleMember));
535
536 static if(isRegularFunction) {
537
538 enum func = &__traits(getMember, module_, moduleMember);
539 enum arity = arity!func;
540
541 static if(arity == 0)
542 // the reason we're creating a lambda to call the function is that test functions
543 // are ordinary functions, but we're storing delegates
544 return [ memberTestData!(module_, moduleMember)(() { func(); }) ]; //simple case, just call the function
545 else {
546
547 // the function has parameters, check if it has UDAs for value parameters to be passed to it
548 alias params = Parameters!func;
549
550 import std.range: iota;
551 import std.algorithm: any;
552 import std.typecons: tuple, Tuple;
553
554 bool hasAttributesForAllParams() {
555 auto ret = true;
556 foreach(p; params) {
557 if(tuple(GetAttributes!(module_, moduleMember, p)).length == 0) {
558 ret = false;
559 }
560 }
561 return ret;
562 }
563
564 static if(!hasAttributesForAllParams) {
565 import std.conv: text;
566 pragma(msg, text("Warning: ", moduleMember, " passes the criteria for a value-parameterized test function",
567 " but doesn't have the appropriate value UDAs.\n",
568 " Consider changing its name or annotating it with @DontTest"));
569 return [];
570 } else {
571
572 static if(arity == 1) {
573 // bind a range of tuples to prod just as cartesianProduct returns
574 enum prod = [GetAttributes!(module_, moduleMember, params[0])].map!(a => tuple(a));
575 } else {
576 import std.conv: text;
577
578 mixin(`enum prod = cartesianProduct(` ~ params.length.iota.map!
579 (a => `[GetAttributes!(module_, moduleMember, params[` ~ guaranteedToString(a) ~ `])]`).join(", ") ~ `);`);
580 }
581
582 TestData[] testData;
583 foreach(comb; aliasSeqOf!prod) {
584 enum valuesName = valuesName(comb);
585
586 static if(HasAttribute!(module_, moduleMember, AutoTags))
587 enum extraTags = valuesName.split(".").array;
588 else
589 enum string[] extraTags = [];
590
591
592 testData ~= memberTestData!(module_, moduleMember, extraTags)(
593 // func(value0, value1, ...)
594 () { func(comb.expand); },
595 valuesName);
596 }
597
598 return testData;
599 }
600 }
601 } else static if(HasTypes!(mixin(moduleMember))) { //template function with @Types
602 alias types = GetTypes!(mixin(moduleMember));
603 TestData[] testData;
604 foreach(type; types) {
605
606 static if(HasAttribute!(module_, moduleMember, AutoTags))
607 enum extraTags = [type.stringof];
608 else
609 enum string[] extraTags = [];
610
611 alias member = Identity!(mixin(moduleMember));
612
613 testData ~= memberTestData!(module_, moduleMember, extraTags)(
614 () { member!type(); },
615 type.stringof);
616 }
617 return testData;
618 } else {
619 return [];
620 }
621 }
622
623
624
625 // this funtion returns TestData for either classes or test functions
626 // built-in unittest modules are handled by moduleUnitTests
627 // pred determines what qualifies as a test
628 // createTestData must return TestData[]
629 private TestData[] moduleTestData(alias module_, alias pred, alias createTestData)() pure {
630 import std.traits: fullyQualifiedName;
631 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible
632 TestData[] testData;
633 foreach(moduleMember; __traits(allMembers, module_)) {
634
635 static if(PassesTestPred!(module_, pred, moduleMember))
636 testData ~= createTestData!(module_, moduleMember);
637 }
638
639 return testData;
640
641 }
642
643 // TestData for a member of a module (either a test function or test class)
644 private TestData memberTestData(alias module_, string moduleMember, string[] extraTags = [])
645 (TestFunction testFunction = null, string suffix = "") {
646
647 import unit_threaded.uda: HasAttribute, GetAttributes, hasUtUDA;
648 import unit_threaded.attrs;
649 import std.traits: fullyQualifiedName;
650
651 mixin("import " ~ fullyQualifiedName!module_ ~ ";"); //so it's visible
652
653 immutable singleThreaded = HasAttribute!(module_, moduleMember, Serial);
654 enum builtin = false;
655 enum tags = tagsFromAttrs!(GetAttributes!(module_, moduleMember, Tags));
656 enum exceptionTypeInfo = getExceptionTypeInfo!(mixin(moduleMember));
657
658 return TestData(fullyQualifiedName!module_~ "." ~ moduleMember,
659 testFunction,
660 HasAttribute!(module_, moduleMember, HiddenTest),
661 HasAttribute!(module_, moduleMember, ShouldFail) || hasUtUDA!(mixin(moduleMember), ShouldFailWith),
662 singleThreaded,
663 builtin,
664 suffix,
665 tags ~ extraTags,
666 exceptionTypeInfo);
667 }
668
669 string[] tagsFromAttrs(T...)() {
670 static assert(T.length <= 1, "@Tags can only be applied once");
671 static if(T.length)
672 return T[0].values;
673 else
674 return [];
675 }
676
677 version(unittest) {
678
679 import unit_threaded.tests.module_with_tests; //defines tests and non-tests
680 import unit_threaded.asserts;
681 import std.algorithm;
682 import std.array;
683
684 //helper function for the unittest blocks below
685 private auto addModPrefix(string[] elements,
686 string module_ = "unit_threaded.tests.module_with_tests") nothrow {
687 return elements.map!(a => module_ ~ "." ~ a).array;
688 }
689 }
690
691 unittest {
692 const expected = addModPrefix([ "FooTest", "BarTest", "Blergh"]);
693 const actual = moduleTestClasses!(unit_threaded.tests.module_with_tests).
694 map!(a => a.name).array;
695 assertEqual(actual, expected);
696 }
697
698 unittest {
699 const expected = addModPrefix([ "testFoo", "testBar", "funcThatShouldShowUpCosOfAttr"]);
700 const actual = moduleTestFunctions!(unit_threaded.tests.module_with_tests).
701 map!(a => a.getPath).array;
702 assertEqual(actual, expected);
703 }
704
705
706 unittest {
707 const expected = addModPrefix(["unittest0", "unittest1", "myUnitTest",
708 "StructWithUnitTests.InStruct", "StructWithUnitTests.unittest1"]);
709 const actual = moduleUnitTests!(unit_threaded.tests.module_with_tests).
710 map!(a => a.name).array;
711 assertEqual(actual, expected);
712 }
713
714 version(unittest) {
715 import unit_threaded.testcase: TestCase;
716 private void assertFail(TestCase test, string file = __FILE__, size_t line = __LINE__) {
717 import core.exception;
718 import std.conv;
719
720 test.silence;
721 assert(test() != [],
722 file ~ ":" ~ line.to!string ~ " Expected test case " ~ test.getPath ~
723 " to fail but it didn't");
724 }
725
726 private void assertPass(TestCase test, string file = __FILE__, size_t line = __LINE__) {
727 import unit_threaded.should: fail;
728 if(test() != [])
729 fail("'" ~ test.getPath ~ "' was expected to pass but failed", file, line);
730 }
731 }
732
733 @("Test that parametrized value tests work")
734 unittest {
735 import unit_threaded.factory;
736 import unit_threaded.testcase;
737 import unit_threaded.tests.parametrized;
738
739 const testData = allTestData!(unit_threaded.tests.parametrized).
740 filter!(a => a.name.endsWith("testValues")).array;
741
742 auto tests = createTestCases(testData);
743 assertEqual(tests.length, 3);
744
745 // the first and third test should pass, the second should fail
746 assertPass(tests[0]);
747 assertPass(tests[2]);
748
749 assertFail(tests[1]);
750 }
751
752
753 @("Test that parametrized type tests work")
754 unittest {
755 import unit_threaded.factory;
756 import unit_threaded.testcase;
757 import unit_threaded.tests.parametrized;
758
759 const testData = allTestData!(unit_threaded.tests.parametrized).
760 filter!(a => a.name.endsWith("testTypes")).array;
761 const expected = addModPrefix(["testTypes.float", "testTypes.int"],
762 "unit_threaded.tests.parametrized");
763 const actual = testData.map!(a => a.getPath).array;
764 assertEqual(actual, expected);
765
766 auto tests = createTestCases(testData);
767 assertEqual(tests.map!(a => a.getPath).array, expected);
768
769 assertPass(tests[1]);
770 assertFail(tests[0]);
771 }
772
773 @("Value parametrized built-in unittests")
774 unittest {
775 import unit_threaded.factory;
776 import unit_threaded.testcase;
777 import unit_threaded.tests.parametrized;
778
779 const testData = allTestData!(unit_threaded.tests.parametrized).
780 filter!(a => a.name.canFind("builtinIntValues")).array;
781
782 auto tests = createTestCases(testData);
783 assertEqual(tests.length, 4);
784
785 // these should be ok
786 assertPass(tests[1]);
787
788 //these should fail
789 assertFail(tests[0]);
790 assertFail(tests[2]);
791 assertFail(tests[3]);
792 }
793
794
795 @("Tests can be selected by tags") unittest {
796 import unit_threaded.factory;
797 import unit_threaded.testcase;
798 import unit_threaded.tests.tags;
799
800 const testData = allTestData!(unit_threaded.tests.tags).array;
801 auto testsNoTags = createTestCases(testData);
802 assertEqual(testsNoTags.length, 4);
803 assertPass(testsNoTags[0]);
804 assertFail(testsNoTags.find!(a => a.getPath.canFind("unittest1")).front);
805 assertFail(testsNoTags[2]);
806 assertFail(testsNoTags[3]);
807
808 auto testsNinja = createTestCases(testData, ["@ninja"]);
809 assertEqual(testsNinja.length, 1);
810 assertPass(testsNinja[0]);
811
812 auto testsMake = createTestCases(testData, ["@make"]);
813 assertEqual(testsMake.length, 3);
814 assertPass(testsMake.find!(a => a.getPath.canFind("testMake")).front);
815 assertPass(testsMake.find!(a => a.getPath.canFind("unittest0")).front);
816 assertFail(testsMake.find!(a => a.getPath.canFind("unittest2")).front);
817
818 auto testsNotNinja = createTestCases(testData, ["~@ninja"]);
819 assertEqual(testsNotNinja.length, 3);
820 assertPass(testsNotNinja.find!(a => a.getPath.canFind("testMake")).front);
821 assertFail(testsNotNinja.find!(a => a.getPath.canFind("unittest1")).front);
822 assertFail(testsNotNinja.find!(a => a.getPath.canFind("unittest2")).front);
823
824 assertEqual(createTestCases(testData, ["unit_threaded.tests.tags.testMake", "@ninja"]).length, 0);
825 }
826
827 @("Parametrized built-in tests with @AutoTags get tagged by value")
828 unittest {
829 import unit_threaded.factory;
830 import unit_threaded.testcase;
831 import unit_threaded.tests.parametrized;
832
833 const testData = allTestData!(unit_threaded.tests.parametrized).
834 filter!(a => a.name.canFind("builtinIntValues")).array;
835
836 auto two = createTestCases(testData, ["@2"]);
837
838 assertEqual(two.length, 1);
839 assertFail(two[0]);
840
841 auto three = createTestCases(testData, ["@3"]);
842 assertEqual(three.length, 1);
843 assertPass(three[0]);
844 }
845
846 @("Value parametrized function tests with @AutoTags get tagged by value")
847 unittest {
848 import unit_threaded.factory;
849 import unit_threaded.testcase;
850 import unit_threaded.tests.parametrized;
851
852 const testData = allTestData!(unit_threaded.tests.parametrized).
853 filter!(a => a.name.canFind("testValues")).array;
854
855 auto two = createTestCases(testData, ["@2"]);
856 assertEqual(two.length, 1);
857 assertFail(two[0]);
858 }
859
860 @("Type parameterized tests with @AutoTags get tagged by type")
861 unittest {
862 import unit_threaded.factory;
863 import unit_threaded.testcase;
864 import unit_threaded.tests.parametrized;
865
866 const testData = allTestData!(unit_threaded.tests.parametrized).
867 filter!(a => a.name.canFind("testTypes")).array;
868
869 auto tests = createTestCases(testData, ["@int"]);
870 assertEqual(tests.length, 1);
871 assertPass(tests[0]);
872 }
873
874 @("Cartesian parameterized built-in values") unittest {
875 import unit_threaded.factory;
876 import unit_threaded.testcase;
877 import unit_threaded.should: shouldBeSameSetAs;
878 import unit_threaded.tests.parametrized;
879 import unit_threaded.attrs: getValue;
880
881 const testData = allTestData!(unit_threaded.tests.parametrized).
882 filter!(a => a.name.canFind("cartesianBuiltinNoAutoTags")).array;
883
884 auto tests = createTestCases(testData);
885 tests.map!(a => a.getPath).array.shouldBeSameSetAs(
886 addModPrefix(["foo.red", "foo.blue", "foo.green", "bar.red", "bar.blue", "bar.green"].
887 map!(a => "cartesianBuiltinNoAutoTags." ~ a).array,
888 "unit_threaded.tests.parametrized"));
889 assertEqual(tests.length, 6);
890
891 auto fooRed = tests.find!(a => a.getPath.canFind("foo.red")).front;
892 assertPass(fooRed);
893 assertEqual(getValue!(string, 0), "foo");
894 assertEqual(getValue!(string, 1), "red");
895 assertEqual(testData.find!(a => a.getPath.canFind("foo.red")).front.tags, []);
896
897 auto barGreen = tests.find!(a => a.getPath.canFind("bar.green")).front;
898 assertFail(barGreen);
899 assertEqual(getValue!(string, 0), "bar");
900 assertEqual(getValue!(string, 1), "green");
901
902 assertEqual(testData.find!(a => a.getPath.canFind("bar.green")).front.tags, []);
903 assertEqual(allTestData!(unit_threaded.tests.parametrized).
904 filter!(a => a.name.canFind("cartesianBuiltinAutoTags")).array.
905 find!(a => a.getPath.canFind("bar.green")).front.tags,
906 ["bar", "green"]);
907 }
908
909 @("Cartesian parameterized function values") unittest {
910 import unit_threaded.factory;
911 import unit_threaded.testcase;
912 import unit_threaded.should: shouldBeSameSetAs;
913
914 const testData = allTestData!(unit_threaded.tests.parametrized).
915 filter!(a => a.name.canFind("CartesianFunction")).array;
916
917 auto tests = createTestCases(testData);
918 tests.map!(a => a.getPath).array.shouldBeSameSetAs(
919 addModPrefix(["1.foo", "1.bar", "2.foo", "2.bar", "3.foo", "3.bar"].
920 map!(a => "testCartesianFunction." ~ a).array,
921 "unit_threaded.tests.parametrized"));
922
923 foreach(test; tests) {
924 test.getPath.canFind("2.bar")
925 ? assertPass(test)
926 : assertFail(test);
927 }
928
929 assertEqual(testData.find!(a => a.getPath.canFind("2.bar")).front.tags,
930 ["2", "bar"]);
931
932 }
933
934 @("module setup and shutdown")
935 unittest {
936 import unit_threaded.testcase;
937 import unit_threaded.factory;
938 import unit_threaded.tests.module_with_setup: gNumBefore, gNumAfter;
939
940 const testData = allTestData!"unit_threaded.tests.module_with_setup".array;
941 auto tests = createTestCases(testData);
942 assertEqual(tests.length, 2);
943
944 assertPass(tests[0]);
945 assertEqual(gNumBefore, 1);
946 assertEqual(gNumAfter, 1);
947
948 assertFail(tests[1]);
949 assertEqual(gNumBefore, 2);
950 assertEqual(gNumAfter, 2);
951 }
952
953 @("issue 33") unittest {
954 import unit_threaded.factory;
955 import unit_threaded.testcase;
956 import test.issue33;
957
958 const testData = allTestData!"test.issue33";
959 assertEqual(testData.length, 1);
960 }
961
962 @("issue 43") unittest {
963 import unit_threaded.factory;
964 import unit_threaded.asserts;
965 import unit_threaded.tests.module_with_tests;
966 import std.algorithm: canFind;
967 import std.array: array;
968
969 const testData = allTestData!"unit_threaded.tests.module_with_tests";
970 assertEqual(testData.canFind!(a => a.getPath.canFind("InStruct" )), true);
971 auto inStructTest = testData
972 .find!(a => a.getPath.canFind("InStruct"))
973 .array
974 .createTestCases[0];
975 assertFail(inStructTest);
976 }
977
978 @("@DontTest should work for unittest blocks") unittest {
979 import unit_threaded.factory;
980 import unit_threaded.asserts;
981 import unit_threaded.tests.module_with_tests;
982 import std.algorithm: canFind;
983 import std.array: array;
984
985 const testData = allTestData!"unit_threaded.tests.module_with_attrs";
986 assertEqual(testData.canFind!(a => a.getPath.canFind("DontTestBlock" )), false);
987 }
988
989 @("@ShouldFail") unittest {
990 import unit_threaded.factory;
991 import unit_threaded.asserts;
992 import unit_threaded.tests.module_with_tests;
993 import std.algorithm: find, canFind;
994 import std.array: array;
995
996 const testData = allTestData!"unit_threaded.tests.module_with_attrs";
997
998 auto willFail = testData
999 .filter!(a => a.getPath.canFind("will fail"))
1000 .array
1001 .createTestCases[0];
1002 assertPass(willFail);
1003 }
1004
1005
1006 @("@ShouldFailWith") unittest {
1007 import unit_threaded.factory;
1008 import unit_threaded.asserts;
1009 import unit_threaded.tests.module_with_attrs;
1010 import unit_threaded.should: shouldThrowExactly, UnitTestException;
1011 import std.algorithm: find, canFind;
1012 import std.array: array;
1013
1014 const testData = allTestData!"unit_threaded.tests.module_with_attrs";
1015
1016 auto doesntFail = testData
1017 .filter!(a => a.getPath.canFind("ShouldFailWith that fails due to not failing"))
1018 .array
1019 .createTestCases[0];
1020 assertFail(doesntFail);
1021
1022 auto wrongType = testData
1023 .find!(a => a.getPath.canFind("ShouldFailWith that fails due to wrong type"))
1024 .array
1025 .createTestCases[0];
1026 assertFail(wrongType);
1027
1028 auto passes = testData
1029 .find!(a => a.getPath.canFind("ShouldFailWith that passes"))
1030 .array
1031 .createTestCases[0];
1032 assertPass(passes);
1033 }
1034
1035 @("structs are not classes") unittest {
1036 import unit_threaded.should;
1037 import unit_threaded.tests.structs_are_not_classes;
1038 const testData = allTestData!"unit_threaded.tests.structs_are_not_classes";
1039 testData.shouldBeEmpty;
1040 }