1 module tests.common_tests; 2 3 enum TestVariant 4 { 5 quantity, 6 qVariant 7 } 8 9 import quantities.runtime.qvariant; 10 import quantities.compiletime.quantity; 11 import quantities.internal.dimensions; 12 import std.exception; 13 14 mixin template CommonTests(TestVariant v) 15 { 16 import std.math : approxEqual; 17 18 static if (v == TestVariant.quantity) 19 { 20 enum meter = unit!(double, "L"); 21 enum second = unit!(double, "T"); 22 enum radian = unit!(double, null); 23 } 24 else static if (v == TestVariant.qVariant) 25 { 26 enum meter = unit!double("L"); 27 enum second = unit!double("T"); 28 enum radian = unit!double(null); 29 30 void checkIncompatibleDimensions(E)(lazy E expression, 31 QVariant!double lhs, QVariant!double rhs) 32 { 33 auto e = collectException!DimensionException(expression()); 34 assert(e, "No DimensionException was thrown"); 35 assert(e.thisDim == lhs.dimensions); 36 assert(e.otherDim == rhs.dimensions); 37 assert(e.msg == "Incompatible dimensions"); 38 } 39 40 void checkNotDimensionless(E)(lazy E expression, QVariant!double operand) 41 { 42 auto e = collectException!DimensionException(expression()); 43 assert(e, "No DimensionException was thrown"); 44 assert(e.thisDim == operand.dimensions); 45 assert(e.otherDim == Dimensions.init); 46 assert(e.msg == "Not dimensionless"); 47 } 48 } 49 else 50 { 51 static assert(false); 52 } 53 54 alias Length = typeof(meter); 55 alias Time = typeof(second); 56 alias Angle = typeof(radian); 57 58 @("this()") 59 @safe pure unittest 60 { 61 enum distance = Length(meter); 62 enum angle = Angle(3.14); 63 64 static if (isQuantity!Length) 65 { 66 import quantities.runtime.qvariant; 67 68 enum length = unit!double("L"); 69 static assert(length.dimensions == distance.dimensions); 70 static assert(!__traits(compiles, Length(2.0))); 71 } 72 73 static if (isQVariant!Length) 74 { 75 enum distance2 = Length(2.0); 76 static assert(distance2.isDimensionless); 77 } 78 } 79 80 @("get/alias this for dimensionless values") 81 @safe pure unittest 82 { 83 enum double scalar = radian; 84 static assert(scalar == 1); 85 } 86 87 @("value(Q)") 88 @safe pure unittest 89 { 90 enum distance = meter; 91 static assert(distance.value(meter) == 1); 92 } 93 94 @("isDimensionless") 95 @safe pure unittest 96 { 97 static assert(!meter.isDimensionless); 98 static assert(radian.isDimensionless); 99 } 100 101 @("isConsistentWith") 102 @safe pure unittest 103 { 104 static assert(meter.isConsistentWith(meter)); 105 static assert(!meter.isConsistentWith(second)); 106 } 107 108 @("opCast") 109 @safe pure unittest 110 { 111 enum value = cast(double) radian; 112 static if (isQuantity!Length) 113 { 114 static assert(!__traits(compiles, cast(double) meter)); 115 } 116 static if (isQVariant!Length) 117 { 118 checkNotDimensionless(cast(double) meter, meter); 119 } 120 } 121 122 @("opAssign Q") 123 @safe pure unittest 124 { 125 Length l1, l2; 126 l1 = l2 = meter; 127 128 static if (isQuantity!Length) 129 { 130 static assert(!__traits(compiles, l1 = second)); 131 } 132 static if (isQVariant!Length) 133 { 134 // QVariant allows assignment to a quantity with different dimensions 135 l1 = second; 136 } 137 } 138 139 @("opAssign T") 140 @safe pure unittest 141 { 142 Angle angle; 143 angle = 1; 144 } 145 146 @("opUnary + -") 147 @safe pure unittest 148 { 149 enum plus = +meter; 150 static assert(plus.value(meter) == 1); 151 enum minus = -meter; 152 static assert(minus.value(meter) == -1); 153 } 154 155 @("opUnary ++ --") 156 @safe pure unittest 157 { 158 auto len = meter; 159 ++len; 160 assert(len.value(meter).approxEqual(2)); 161 assert((len++).value(meter).approxEqual(2)); 162 assert(len.value(meter).approxEqual(3)); 163 --len; 164 assert(len.value(meter).approxEqual(2)); 165 assert((len--).value(meter).approxEqual(2)); 166 assert(len.value(meter).approxEqual(1)); 167 } 168 169 @("opBinary Q+Q Q-Q") 170 @safe pure unittest 171 { 172 enum plus = meter + meter; 173 static assert(plus.value(meter) == 2); 174 enum minus = meter - meter; 175 static assert(minus.value(meter) == 0); 176 177 static if (isQuantity!Length) 178 { 179 static assert(!__traits(compiles, meter + second)); 180 static assert(!__traits(compiles, meter - second)); 181 } 182 static if (isQVariant!Length) 183 { 184 checkIncompatibleDimensions(meter + second, meter, second); 185 checkIncompatibleDimensions(meter - second, meter, second); 186 } 187 } 188 189 @("opBinary Q+N N+Q Q-N N-Q") 190 @safe pure unittest 191 { 192 enum a1 = radian + 10; 193 static assert(a1.value(radian).approxEqual(11)); 194 enum a2 = radian - 10; 195 static assert(a2.value(radian).approxEqual(-9)); 196 197 enum a3 = 10 + radian; 198 static assert(a3.value(radian).approxEqual(11)); 199 enum a4 = 10 - radian; 200 static assert(a4.value(radian).approxEqual(9)); 201 202 static if (isQuantity!Length) 203 { 204 static assert(!__traits(compiles, meter + 1)); 205 static assert(!__traits(compiles, meter - 1)); 206 static assert(!__traits(compiles, 1 + meter)); 207 static assert(!__traits(compiles, 1 - meter)); 208 } 209 static if (isQVariant!Length) 210 { 211 checkNotDimensionless(meter + 1, meter); 212 checkNotDimensionless(meter - 1, meter); 213 checkNotDimensionless(1 + meter, meter); 214 checkNotDimensionless(1 - meter, meter); 215 } 216 } 217 218 @("opBinary Q*N, N*Q, Q/N, N/Q, Q%N, N%Q") 219 @safe pure unittest 220 { 221 enum m1 = meter * 10; 222 static assert(m1.value(meter).approxEqual(10)); 223 enum m2 = 10 * meter; 224 static assert(m2.value(meter).approxEqual(10)); 225 enum m3 = meter / 10; 226 static assert(m3.value(meter).approxEqual(0.1)); 227 enum m4 = 10 / meter; 228 static assert(m4.dimensions == ~meter.dimensions); 229 static assert(m4.value(1 / meter).approxEqual(10)); 230 enum m5 = m1 % 2; 231 static assert(m5.value(meter).approxEqual(0)); 232 enum m6 = 10 % (2 * radian); 233 static assert(m6.value(radian).approxEqual(0)); 234 } 235 236 @("opBinary Q*Q, Q/Q, Q%Q") 237 @safe pure unittest 238 { 239 enum surface = (10 * meter) * (10 * meter); 240 static assert(surface.value(meter * meter).approxEqual(100)); 241 static assert(surface.dimensions == meter.dimensions.pow(2)); 242 243 enum speed = (10 * meter) / (5 * second); 244 static assert(speed.value(meter / second).approxEqual(2)); 245 static assert(speed.dimensions == meter.dimensions / second.dimensions); 246 247 enum surfaceMod10 = surface % (10 * meter * meter); 248 static assert(surfaceMod10.value(meter * meter).approxEqual(0)); 249 static assert(surfaceMod10.dimensions == surface.dimensions); 250 251 static if (isQuantity!Length) 252 { 253 static assert(!__traits(compiles, meter % second)); 254 } 255 static if (isQVariant!Length) 256 { 257 checkIncompatibleDimensions(meter % second, meter, second); 258 } 259 } 260 261 @("opBinary Q^^I Q^^R") 262 @safe pure unittest 263 { 264 // Operator ^^ is not available for Quantity 265 static if (isQVariant!Length) 266 { 267 enum x = 2 * meter; 268 static assert((x ^^ 3).value(meter * meter * meter).approxEqual(8)); 269 static assert((x ^^ Rational(3)).value(meter * meter * meter).approxEqual(8)); 270 } 271 } 272 273 @("opOpAssign Q+=Q Q-=Q") 274 @safe pure unittest 275 { 276 auto time = 10 * second; 277 time += 50 * second; 278 assert(time.value(second).approxEqual(60)); 279 time -= 40 * second; 280 assert(time.value(second).approxEqual(20)); 281 } 282 283 @("opOpAssign Q*=N Q/=N Q%=N") 284 @safe pure unittest 285 { 286 auto time = 20 * second; 287 time *= 2; 288 assert(time.value(second).approxEqual(40)); 289 time /= 4; 290 assert(time.value(second).approxEqual(10)); 291 292 auto angle = 2 * radian; 293 angle += 4; 294 assert(angle.value(radian).approxEqual(6)); 295 angle -= 1; 296 assert(angle.value(radian).approxEqual(5)); 297 angle %= 2; 298 assert(angle.value(radian).approxEqual(1)); 299 300 static if (isQuantity!Time) 301 { 302 static assert(!__traits(compiles, time %= 3)); 303 } 304 static if (isQVariant!Time) 305 { 306 checkNotDimensionless(time %= 3, time); 307 } 308 } 309 310 @("opOpAssign Q*=Q Q/=Q Q%=Q") 311 @safe pure unittest 312 { 313 static if (isQuantity!Time) 314 { 315 auto angle = 50 * radian; 316 angle *= 2 * radian; 317 assert(angle.value(radian).approxEqual(100)); 318 angle /= 2 * radian; 319 assert(angle.value(radian).approxEqual(50)); 320 angle %= 5 * radian; 321 assert(angle.value(radian).approxEqual(0)); 322 323 auto time = second; 324 static assert(!__traits(compiles, time *= second)); 325 static assert(!__traits(compiles, time /= second)); 326 static assert(!__traits(compiles, time %= second)); 327 } 328 329 static if (isQVariant!Time) 330 { 331 auto angle = 2 * radian; 332 angle *= 2 * radian; 333 assert(angle.value(radian).approxEqual(4)); 334 angle /= 2 * radian; 335 assert(angle.value(radian).approxEqual(2)); 336 337 auto qty = 100 * meter; 338 qty *= second; 339 qty /= 20 * second; 340 qty %= 5 * second; 341 assert(qty.value(meter / second).approxEqual(0)); 342 } 343 } 344 345 @("opEquals Q==Q Q==N") 346 @safe pure unittest 347 { 348 enum minute = 60 * second; 349 static assert(minute == 60 * second); 350 static assert(radian == 1); 351 352 static if (isQuantity!Time) 353 { 354 static assert(!__traits(compiles, meter == second)); 355 static assert(!__traits(compiles, meter == 1)); 356 } 357 static if (isQVariant!Time) 358 { 359 checkIncompatibleDimensions(meter == second, meter, second); 360 checkNotDimensionless(meter == 1, meter); 361 } 362 } 363 364 @("opCmp Q<Q") 365 @safe pure unittest 366 { 367 enum minute = 60 * second; 368 enum hour = 60 * minute; 369 static assert(second < minute); 370 static assert(minute <= minute); 371 static assert(hour > minute); 372 static assert(hour >= hour); 373 374 static if (isQuantity!Time) 375 { 376 static assert(!__traits(compiles, second < meter)); 377 } 378 static if (isQVariant!Time) 379 { 380 checkIncompatibleDimensions(meter < second, meter, second); 381 } 382 } 383 384 @("opCmp Q<N") 385 @safe pure unittest 386 { 387 enum angle = 2 * radian; 388 static assert(angle < 4); 389 static assert(angle <= 2); 390 static assert(angle > 1); 391 static assert(angle >= 2); 392 393 static if (isQuantity!Time) 394 { 395 static assert(!__traits(compiles, meter < 1)); 396 } 397 static if (isQVariant!Time) 398 { 399 checkNotDimensionless(meter < 1, meter); 400 } 401 } 402 403 @("toString") 404 unittest 405 { 406 import std.conv : text; 407 408 auto length = 12 * meter; 409 assert(length.text == "12 [L]", length.text); 410 } 411 412 @("immutable") 413 @safe pure unittest 414 { 415 immutable inch = 0.0254 * meter; 416 immutable minute = 60 * second; 417 immutable speed = inch / minute; 418 } 419 420 @("square/sqrt") 421 @safe unittest 422 { 423 enum m2 = square(3 * meter); 424 static assert(m2.value(meter * meter).approxEqual(9)); 425 enum m = sqrt(m2); 426 static assert(m.value(meter).approxEqual(3)); 427 } 428 429 @("cubic/cbrt") 430 @safe unittest 431 { 432 enum m3 = cubic(2 * meter); 433 static assert(m3.value(meter * meter * meter).approxEqual(8)); 434 435 // Doesn't work at compile time 436 auto m = cbrt(m3); 437 assert(m.value(meter).approxEqual(2)); 438 } 439 440 @("pow/nthRoot") 441 @safe unittest 442 { 443 enum m5 = pow!5(2 * meter); 444 static assert(m5.value(meter * meter * meter * meter * meter).approxEqual(2 ^^ 5)); 445 446 // Doesn't work at compile time 447 auto m = nthRoot!5(m5); 448 assert(m.value(meter).approxEqual(2)); 449 } 450 451 @("abs") 452 @safe unittest 453 { 454 static assert(abs(-meter) == meter); 455 } 456 } 457 458 mixin CommonTests!(TestVariant.qVariant); 459 mixin CommonTests!(TestVariant.quantity);