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);