1 /** 2 This module is an attempt to alleviate compile times by including the bare 3 minimum. The idea is that while the reporting usually done by unit-threaded 4 is welcome, it only really matters when tests fail. Otherwise, no news is 5 good news. 6 7 Likewise, naming and selecting tests are features used when certain tests 8 fail. The usual way to run tests is to run all of them and be happy if 9 they all pass. 10 11 This module makes it so that unit-threaded gets out of the way, and if 12 needed the full features can be turned on at the cost of compiling 13 much more slowly. 14 15 There aren't even any template constraints on the `should` functions 16 to avoid imports as much as possible. 17 */ 18 module unit_threaded.light; 19 20 alias UnitTestException = Exception; 21 22 23 /** 24 Dummy version so "normal" code compiles 25 */ 26 mixin template runTestsMain(Modules...) if(Modules.length > 0) { 27 int main() { 28 import unit_threaded.light: runTestsImpl; 29 return runTestsImpl; 30 } 31 } 32 33 /** 34 Dummy version of runTests so "normal" code compiles. 35 */ 36 int runTests(T...)(in string[] args) { 37 return runTestsImpl; 38 } 39 40 /// ditto 41 int runTests(T)(string[] args, T testData) { 42 return runTestsImpl; 43 } 44 45 int runTestsImpl() { 46 import core.runtime: Runtime; 47 import core.stdc.stdio: printf; 48 49 version(Posix) 50 printf("\033[32;1mOk\033[0;;m"); 51 else 52 printf("Ok"); 53 54 printf(": All tests passed\n\n"); 55 56 return 0; 57 } 58 59 60 /** 61 Dummy version so "normal" code compiles 62 */ 63 int[] allTestData(T...)() { 64 return []; 65 } 66 67 /** 68 No-op version of writelnUt 69 */ 70 void writelnUt(T...)(auto ref T args) { 71 72 } 73 74 /** 75 Same as unit_threaded.property.check 76 */ 77 void check(alias F)(int numFuncCalls = 100, 78 in string file = __FILE__, in size_t line = __LINE__) @trusted { 79 import unit_threaded.property: utCheck = check; 80 utCheck!F(numFuncCalls, file, line); 81 } 82 83 /** 84 Same as unit_threaded.property.checkCustom 85 */ 86 void checkCustom(alias Generator, alias Predicate) 87 (int numFuncCalls = 100, in string file = __FILE__, in size_t line = __LINE__) @trusted { 88 import unit_threaded.property: utCheckCustom = checkCustom; 89 utCheckCustom!(Generator, Predicate)(numFuncCalls, file, line); 90 } 91 92 93 /** 94 Generic output interface 95 */ 96 interface Output { 97 void send(in string output) @safe; 98 void flush() @safe; 99 } 100 101 /** 102 Dummy version of unit_threaded.testcase.TestCase 103 */ 104 class TestCase { 105 abstract void test(); 106 void setup() {} 107 void shutdown() {} 108 static TestCase currentTest() { return new class TestCase { override void test() {}}; } 109 Output getWriter() { return new class Output { override void send(in string output) {} override void flush() {}}; } 110 } 111 112 113 /** 114 Same as unit_threaded.mock.mock 115 */ 116 auto mock(T)() { 117 import unit_threaded.mock: utMock = mock; 118 return utMock!T; 119 } 120 121 /** 122 Same as unit_threaded.mock.mockStruct 123 */ 124 auto mockStruct(T...)(auto ref T returns) { 125 import unit_threaded.mock: utMockStruct = mockStruct; 126 return utMockStruct(returns); 127 } 128 129 /** 130 Throw if condition is not true. 131 */ 132 void shouldBeTrue(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) { 133 assert_(cast(bool)condition(), file, line); 134 } 135 136 /// Throw if condition not false. 137 void shouldBeFalse(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) { 138 assert_(!cast(bool)condition(), file, line); 139 } 140 141 /// Assert value is equal to expected 142 void shouldEqual(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 143 144 void checkInputRange(T)(auto ref const(T) _) @trusted { 145 auto obj = cast(T)_; 146 bool e = obj.empty; 147 auto f = obj.front; 148 obj.popFront; 149 } 150 enum isInputRange(T) = is(T: Elt[], Elt) || is(typeof(checkInputRange(T.init))); 151 152 static if(is(V == class)) { 153 154 import unit_threaded.should: isEqual; 155 assert_(isEqual(value, expected), file, line); 156 157 } else static if(isInputRange!V && isInputRange!E) { 158 159 auto ref unqual(OriginalType)(auto ref OriginalType obj) @trusted { 160 161 // copied from std.traits 162 template Unqual(T) { 163 static if (is(T U == immutable U)) alias Unqual = U; 164 else static if (is(T U == shared inout const U)) alias Unqual = U; 165 else static if (is(T U == shared inout U)) alias Unqual = U; 166 else static if (is(T U == shared const U)) alias Unqual = U; 167 else static if (is(T U == shared U)) alias Unqual = U; 168 else static if (is(T U == inout const U)) alias Unqual = U; 169 else static if (is(T U == inout U)) alias Unqual = U; 170 else static if (is(T U == const U)) alias Unqual = U; 171 else alias Unqual = T; 172 } 173 174 static if(__traits(compiles, obj[])) { 175 static if(!is(typeof(obj[]) == OriginalType)) { 176 return unqual(obj[]); 177 } else static if(__traits(compiles, cast(Unqual!OriginalType) obj)) { 178 return cast(Unqual!OriginalType) obj; 179 } else { 180 return obj; 181 } 182 } else static if(__traits(compiles, cast(Unqual!OriginalType) obj)) { 183 return cast(Unqual!OriginalType) obj; 184 } else 185 return obj; 186 } 187 188 import std.algorithm: equal; 189 assert_(equal(unqual(value), unqual(expected)), file, line); 190 191 } else { 192 assert_(cast(const)value == cast(const)expected, file, line); 193 } 194 } 195 196 197 198 /// Assert value is not equal to expected. 199 void shouldNotEqual(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 200 assert_(value != expected, file, line); 201 } 202 203 /// Assert value is null. 204 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) { 205 assert_(value is null, file, line); 206 } 207 208 /// Assert value is not null 209 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) { 210 assert_(value !is null, file, line); 211 } 212 213 enum isLikeAssociativeArray(T, K) = is(typeof({ 214 if(K.init in T) { } 215 if(K.init !in T) { } 216 })); 217 static assert(isLikeAssociativeArray!(string[string], string)); 218 static assert(!isLikeAssociativeArray!(string[string], int)); 219 220 221 /// Assert that value is in container. 222 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__) 223 if(isLikeAssociativeArray!(U, T)) { 224 assert_(cast(bool)(value in container), file, line); 225 } 226 227 /// ditto. 228 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__) 229 if (!isLikeAssociativeArray!(U, T)) 230 { 231 import std.algorithm: find; 232 import std.array: empty; 233 assert_(!find(container, value).empty, file, line); 234 } 235 236 /// Assert value is not in container. 237 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__) 238 if(isLikeAssociativeArray!U) { 239 assert_(!cast(bool)(value in container), file, line); 240 } 241 242 /// ditto. 243 void shouldNotBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__) 244 if (!isLikeAssociativeArray!(U, T)) 245 { 246 import std.algorithm: find; 247 import std.array: empty; 248 assert_(find(container, value).empty, file, line); 249 } 250 251 /// Assert that expr throws. 252 void shouldThrow(T : Throwable = Exception, E) 253 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) { 254 auto threw = false; 255 () @trusted { 256 try { 257 expr(); 258 } catch(T _) { 259 threw = true; 260 } 261 }(); 262 assert_(threw, file, line); 263 } 264 265 /// Assert that expr throws an Exception that must have the type E, derived types won't do. 266 void shouldThrowExactly(T : Throwable = Exception, E) 267 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) 268 { 269 T throwable = null; 270 271 () @trusted { 272 try { 273 expr(); 274 assert_(false, file, line); 275 } catch(T t) { 276 throwable = t; 277 } 278 }(); 279 280 //Object.opEquals is @system and impure 281 const sameType = () @trusted { return throwable !is null && typeid(throwable) == typeid(T); }(); 282 assert_(sameType, file, line); 283 284 } 285 286 /// Assert that expr doesn't throw 287 void shouldNotThrow(T: Throwable = Exception, E) 288 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) { 289 () @trusted { 290 try 291 expr(); 292 catch(T _) 293 assert_(false, file, line); 294 }(); 295 } 296 297 /// Assert that expr throws and the exception message is msg. 298 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr, 299 string msg, 300 string file = __FILE__, 301 size_t line = __LINE__) { 302 T throwable = null; 303 304 () @trusted { 305 try { 306 expr(); 307 } catch(T ex) { 308 throwable = ex; 309 } 310 }(); 311 312 assert_(throwable !is null && throwable.msg == msg, file, line); 313 } 314 315 /// Assert that value is approximately equal to expected. 316 void shouldApproxEqual(V, E)(in V value, in E expected, double maxRelDiff = 1e-2, double maxAbsDiff = 1e-5, string file = __FILE__, size_t line = __LINE__) { 317 import std.math: approxEqual; 318 assert_(approxEqual(value, expected, maxRelDiff, maxAbsDiff), file, line); 319 } 320 321 /// assert that rng is empty. 322 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) { 323 import std.range: isInputRange; 324 import std.traits: isAssociativeArray; 325 import std.array; 326 327 static if(isInputRange!R) 328 assert_(rng.empty, file, line); 329 else static if(isAssociativeArray!R) 330 () @trusted { assert_(rng.keys.empty, file, line); }(); 331 else 332 static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof); 333 } 334 335 /// Assert that rng is not empty. 336 void shouldNotBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) { 337 import std.range: isInputRange; 338 import std.traits: isAssociativeArray; 339 import std.array; 340 341 static if(isInputRange!R) 342 assert_(!rnd.empty, file, line); 343 else static if(isAssociativeArray!R) 344 () @trusted { assert_(!rng.keys.empty, file, line); }(); 345 else 346 static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof); 347 } 348 349 /// Assert that t should be greater than u. 350 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u, 351 in string file = __FILE__, in size_t line = __LINE__) 352 { 353 assert_(t > u, file, line); 354 } 355 356 /// Assert that t should be smaller than u. 357 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u, 358 in string file = __FILE__, in size_t line = __LINE__) 359 { 360 assert_(t < u, file, line); 361 } 362 363 /// Assert that value is the same set as expected (i.e. order doesn't matter) 364 void shouldBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 365 assert_(isSameSet(value, expected), file, line); 366 } 367 368 /// Assert that value is not the same set as expected. 369 void shouldNotBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) { 370 assert_(!isSameSet(value, expected), file, line); 371 } 372 373 private bool isSameSet(T, U)(in auto ref T t, in auto ref U u) { 374 import std.array: array; 375 import std.algorithm: canFind; 376 377 //sort makes the element types have to implement opCmp 378 //instead, try one by one 379 auto ta = t.array; 380 auto ua = u.array; 381 if (ta.length != ua.length) return false; 382 foreach(element; ta) 383 { 384 if (!ua.canFind(element)) return false; 385 } 386 387 return true; 388 } 389 390 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter) 391 void shouldBeSameJsonAs(in string actual, 392 in string expected, 393 in string file = __FILE__, 394 in size_t line = __LINE__) 395 @trusted // not @safe pure due to parseJSON 396 { 397 import std.json: parseJSON, JSONException; 398 399 auto parse(in string str) { 400 try 401 return str.parseJSON; 402 catch(JSONException ex) { 403 assert_(false, "Failed to parse " ~ str, file, line); 404 } 405 assert(0); 406 } 407 408 assert_(parse(actual) == parse(expected), file, line); 409 } 410 411 412 private void assert_(in bool value, in string file, in size_t line) @safe pure { 413 assert_(value, "Assertion failure", file, line); 414 } 415 416 private void assert_(bool value, in string message, in string file, in size_t line) @trusted pure { 417 if(!value) 418 throw new Exception(message, file, line); 419 } 420 421 void fail(in string output, in string file, in size_t line) @safe pure { 422 assert_(false, output, file, line); 423 }