1
|
<?php
|
2
|
|
3
|
/**
|
4
|
* Tests the MathExpression library of ctools.
|
5
|
*/
|
6
|
class CtoolsMathExpressionTestCase extends DrupalWebTestCase {
|
7
|
|
8
|
/**
|
9
|
* {@inheritdoc}
|
10
|
*/
|
11
|
public static function getInfo() {
|
12
|
return array(
|
13
|
'name' => 'Math expressions',
|
14
|
'description' => 'Test the math expression library of ctools.',
|
15
|
'group' => 'ctools',
|
16
|
'dependencies' => array('ctools'),
|
17
|
);
|
18
|
}
|
19
|
|
20
|
/**
|
21
|
* {@inheritdoc}
|
22
|
*/
|
23
|
public function setUp(array $modules = array()) {
|
24
|
$modules[] = 'ctools';
|
25
|
$modules[] = 'ctools_plugin_test';
|
26
|
parent::setUp($modules);
|
27
|
}
|
28
|
|
29
|
/**
|
30
|
* Return the sign of the numeric arg $n as an integer -1, 0, 1.
|
31
|
*
|
32
|
* Note: Not defined when $n is Infinity or NaN (or NULL or ...)!
|
33
|
*
|
34
|
* @param int|float $n
|
35
|
* The number to test.
|
36
|
*
|
37
|
* @return int
|
38
|
* -1 if the $n is negative, 0 if $n is zero or 1 if $n is positive.
|
39
|
*
|
40
|
* @see gmp_sign()
|
41
|
*/
|
42
|
protected static function sign($n) {
|
43
|
return ($n > 0) - ($n < 0);
|
44
|
}
|
45
|
|
46
|
/**
|
47
|
* Returns a random number between 0 and 1.
|
48
|
*
|
49
|
* @return float
|
50
|
* A random number between 0 and 1 inclusive.
|
51
|
*/
|
52
|
protected function rand01() {
|
53
|
return mt_rand(0, PHP_INT_MAX) / PHP_INT_MAX;
|
54
|
}
|
55
|
|
56
|
/**
|
57
|
* A custom assertion with checks the values in a certain range.
|
58
|
*
|
59
|
* @param float $first
|
60
|
* A value to check for equality.
|
61
|
* @param float $second
|
62
|
* A value to check for equality.
|
63
|
* @param string $message
|
64
|
* The message describing the correct behaviour, eg. "2/4 equals 1/2". The
|
65
|
* default message is used if this value is empty.
|
66
|
* @param float $delta
|
67
|
* The precision with which values must match. This accounts for rounding
|
68
|
* errors and imprecise representation errors in the floating point format.
|
69
|
* The value passed in should ideally be proportional to the values being
|
70
|
* compared.
|
71
|
* @param string $group
|
72
|
* Which group this assert belongs to.
|
73
|
*
|
74
|
* @return bool
|
75
|
* TRUE if the assertion was correct (that is, $first == $second within the
|
76
|
* given limits), FALSE otherwise.
|
77
|
*/
|
78
|
protected function assertFloat($first, $second, $message = '', $delta = 0.00000001, $group = 'Other') {
|
79
|
// Check for NaN and Inf because the abs() and sign() code won't like those.
|
80
|
$equal = FALSE
|
81
|
// Equal if both an infinity.
|
82
|
|| (is_infinite($first) && is_infinite($second))
|
83
|
|
84
|
// Equal if both NaN.
|
85
|
|| (is_nan($first) && is_nan($second))
|
86
|
|
87
|
// Equal if same absolute value (within limits) and same sign.
|
88
|
|| ((abs($first - $second) <= $delta) && (self::sign($first) === self::sign($second)));
|
89
|
|
90
|
if (empty($message)) {
|
91
|
$default = t('Value !first is equal to value !second.',
|
92
|
array(
|
93
|
'!first' => var_export($first, TRUE),
|
94
|
'!second' => var_export($second, TRUE),
|
95
|
));
|
96
|
$message = $default;
|
97
|
}
|
98
|
|
99
|
return $this->assert($equal, $message, $group);
|
100
|
}
|
101
|
|
102
|
/**
|
103
|
* Test some arithmetic handling.
|
104
|
*/
|
105
|
public function testArithmetic() {
|
106
|
$math_expr = new ctools_math_expr();
|
107
|
|
108
|
$this->assertEqual($math_expr->evaluate('2'), 2, 'Check Literal 2');
|
109
|
|
110
|
$this->assertEqual($math_expr->e('2+1'), $math_expr->evaluate('2+1'), 'Check that e() and evaluate() are equivalent.');
|
111
|
|
112
|
foreach (range(1, 4) as $n) {
|
113
|
// Test constant expressions.
|
114
|
$random_number = mt_rand(0, 20);
|
115
|
$this->assertEqual($random_number, $math_expr->evaluate((string) $random_number), "Literal $random_number");
|
116
|
|
117
|
// Test simple arithmetic.
|
118
|
$number_a = mt_rand(-55, 777);
|
119
|
$number_b = mt_rand(-555, 77);
|
120
|
$this->assertEqual(
|
121
|
$number_a + $number_b,
|
122
|
$math_expr->evaluate("$number_a + $number_b"),
|
123
|
"Addition: $number_a + $number_b");
|
124
|
$this->assertEqual(
|
125
|
$number_a - $number_b,
|
126
|
$math_expr->evaluate("$number_a - $number_b"),
|
127
|
"Subtraction: $number_a + $number_b");
|
128
|
$this->assertFloat(
|
129
|
($number_a * $number_b),
|
130
|
$math_expr->evaluate("$number_a * $number_b"),
|
131
|
"Multiplication: $number_a * $number_b = " . ($number_a * $number_b));
|
132
|
$this->assertFloat(
|
133
|
($number_a / $number_b),
|
134
|
$math_expr->evaluate("$number_a / $number_b"),
|
135
|
"Division: $number_a / $number_b = " . ($number_a / $number_b));
|
136
|
|
137
|
// Test Associative property.
|
138
|
$number_c = mt_rand(-99, 77);
|
139
|
$this->assertEqual(
|
140
|
$math_expr->evaluate("$number_a + ($number_b + $number_c)"),
|
141
|
$math_expr->evaluate("($number_a + $number_b) + $number_c"),
|
142
|
"Associative: $number_a + ($number_b + $number_c)");
|
143
|
$this->assertEqual(
|
144
|
$math_expr->evaluate("$number_a * ($number_b * $number_c)"),
|
145
|
$math_expr->evaluate("($number_a * $number_b) * $number_c"),
|
146
|
"Associative: $number_a * ($number_b * $number_c)");
|
147
|
|
148
|
// Test Commutative property.
|
149
|
$this->assertEqual(
|
150
|
$math_expr->evaluate("$number_a + $number_b"),
|
151
|
$math_expr->evaluate("$number_b + $number_a"),
|
152
|
"Commutative: $number_a + $number_b");
|
153
|
$this->assertEqual(
|
154
|
$math_expr->evaluate("$number_a * $number_b"),
|
155
|
$math_expr->evaluate("$number_b * $number_a"),
|
156
|
"Commutative: $number_a * $number_b");
|
157
|
|
158
|
// Test Distributive property.
|
159
|
$this->assertEqual(
|
160
|
$math_expr->evaluate("($number_a + $number_b) * $number_c"),
|
161
|
$math_expr->evaluate("($number_a * $number_c + $number_b * $number_c)"),
|
162
|
"Distributive: ($number_a + $number_b) * $number_c");
|
163
|
|
164
|
// @todo: Doesn't work with zero or negative powers when number is zero or negative, e.g. 0^0, 0^-2, -2^0, -2^-2.
|
165
|
$random_number = mt_rand(1, 15);
|
166
|
$random_power = mt_rand(-15, 15);
|
167
|
|
168
|
$this->assertFloat(
|
169
|
pow($random_number, $random_power),
|
170
|
$math_expr->evaluate("$random_number ^ $random_power"),
|
171
|
"$random_number ^ $random_power");
|
172
|
|
173
|
$this->assertFloat(
|
174
|
pow($random_number, $random_power),
|
175
|
$math_expr->evaluate("pow($random_number, $random_power)"),
|
176
|
"pow($random_number, $random_power)");
|
177
|
}
|
178
|
}
|
179
|
|
180
|
/**
|
181
|
* Test various built-in transcendental and extended functions.
|
182
|
*/
|
183
|
public function testBuildInFunctions() {
|
184
|
$math_expr = new ctools_math_expr();
|
185
|
|
186
|
foreach (range(1, 4) as $n) {
|
187
|
$random_double = $this->rand01();
|
188
|
$random_int = mt_rand(-65535, 65535);
|
189
|
$this->assertFloat(sin($random_double), $math_expr->evaluate("sin($random_double)"), "sin($random_double)");
|
190
|
$this->assertFloat(cos($random_double), $math_expr->evaluate("cos($random_double)"), "cos($random_double)");
|
191
|
$this->assertFloat(tan($random_double), $math_expr->evaluate("tan($random_double)"), "tan($random_double)");
|
192
|
$this->assertFloat(exp($random_double), $math_expr->evaluate("exp($random_double)"), "exp($random_double)");
|
193
|
$this->assertFloat(sqrt($random_double), $math_expr->evaluate("sqrt($random_double)"), "sqrt($random_double)");
|
194
|
$this->assertFloat(log($random_double), $math_expr->evaluate("ln($random_double)"), "ln($random_double)");
|
195
|
$this->assertFloat(round($random_double), $math_expr->evaluate("round($random_double)"), "round($random_double)");
|
196
|
|
197
|
$random_real = $random_double + $random_int;
|
198
|
$this->assertFloat(abs($random_real), $math_expr->evaluate('abs(' . $random_real . ')'), "abs($random_real)");
|
199
|
$this->assertEqual(round($random_real), $math_expr->evaluate('round(' . $random_real . ')'), "round($random_real)");
|
200
|
$this->assertEqual(ceil($random_real), $math_expr->evaluate('ceil(' . $random_real . ')'), "ceil($random_real)");
|
201
|
$this->assertEqual(floor($random_real), $math_expr->evaluate('floor(' . $random_real . ')'), "floor($random_real)");
|
202
|
}
|
203
|
|
204
|
$this->assertFloat(time(), $math_expr->evaluate('time()'), "time()");
|
205
|
|
206
|
$random_double_a = $this->rand01();
|
207
|
$random_double_b = $this->rand01();
|
208
|
$this->assertFloat(
|
209
|
max($random_double_a, $random_double_b),
|
210
|
$math_expr->evaluate("max($random_double_a, $random_double_b)"),
|
211
|
"max($random_double_a, $random_double_b)");
|
212
|
$this->assertFloat(
|
213
|
min($random_double_a, $random_double_b),
|
214
|
$math_expr->evaluate("min($random_double_a, $random_double_b)"),
|
215
|
"min($random_double_a, $random_double_b)");
|
216
|
}
|
217
|
|
218
|
/**
|
219
|
* Test variable handling.
|
220
|
*/
|
221
|
public function testVariables() {
|
222
|
$math_expr = new ctools_math_expr();
|
223
|
|
224
|
// We should have a definition of pi:
|
225
|
$this->assertFloat(pi(), $math_expr->evaluate('pi'));
|
226
|
|
227
|
// And a definition of e:
|
228
|
$this->assertFloat(exp(1), $math_expr->evaluate('e'));
|
229
|
|
230
|
$number_a = 5;
|
231
|
$number_b = 10;
|
232
|
|
233
|
// Store the first number and use it on a calculation.
|
234
|
$math_expr->evaluate("var = $number_a");
|
235
|
$this->assertEqual($number_a + $number_b, $math_expr->evaluate("var + $number_b"));
|
236
|
|
237
|
// Change the value and check the new value is used.
|
238
|
$math_expr->evaluate("var = $number_b");
|
239
|
$this->assertEqual(
|
240
|
$number_b + $number_b,
|
241
|
$math_expr->evaluate("var + $number_b"),
|
242
|
"var + $number_b");
|
243
|
|
244
|
// Store another number and use it on a calculation.
|
245
|
$math_expr->evaluate("var = $number_a");
|
246
|
$math_expr->evaluate("newvar = $number_a");
|
247
|
|
248
|
$this->assertEqual(
|
249
|
$number_a + $number_a,
|
250
|
$math_expr->evaluate('var + newvar'),
|
251
|
'var + newvar');
|
252
|
|
253
|
$this->assertFloat(
|
254
|
$number_a / $number_b,
|
255
|
$math_expr->evaluate("var / $number_b"),
|
256
|
"var / $number_b");
|
257
|
}
|
258
|
|
259
|
/**
|
260
|
* Test custom function handling.
|
261
|
*/
|
262
|
public function testCustomFunctions() {
|
263
|
$math_expr = new ctools_math_expr();
|
264
|
|
265
|
$number_a = mt_rand(5, 10);
|
266
|
$number_b = mt_rand(5, 10);
|
267
|
|
268
|
// Create a one-argument function.
|
269
|
$math_expr->evaluate("f(x) = 2 * x");
|
270
|
$this->assertEqual($number_a * 2, $math_expr->evaluate("f($number_a)"));
|
271
|
$this->assertEqual($number_b * 2, $math_expr->evaluate("f($number_b)"));
|
272
|
|
273
|
// Create a two-argument function.
|
274
|
$math_expr->evaluate("g(x, y) = 2 * x + y");
|
275
|
$this->assertEqual(
|
276
|
$number_a * 2 + $number_b,
|
277
|
$math_expr->evaluate("g($number_a, $number_b)"),
|
278
|
"g($number_a, $number_b)");
|
279
|
|
280
|
// Use a custom function in another function.
|
281
|
$this->assertEqual(
|
282
|
($number_a * 2 + $number_b) * 2,
|
283
|
$math_expr->evaluate("f(g($number_a, $number_b))"),
|
284
|
"f(g($number_a, $number_b))");
|
285
|
}
|
286
|
|
287
|
/**
|
288
|
* Test conditional handling.
|
289
|
*/
|
290
|
public function testIf() {
|
291
|
$math_expr = new ctools_math_expr();
|
292
|
|
293
|
$number_a = mt_rand(1, 10);
|
294
|
$number_b = mt_rand(11, 20);
|
295
|
|
296
|
foreach (range(1, 4) as $n) {
|
297
|
// @todo: Doesn't work with negative numbers.
|
298
|
if ($n == 2 || $n == 4) {
|
299
|
//$number_a = -$number_a;
|
300
|
}
|
301
|
|
302
|
if ($n == 3 || $n == 4) {
|
303
|
//$number_b = -$number_b;
|
304
|
}
|
305
|
|
306
|
$this->assertEqual(
|
307
|
$number_a,
|
308
|
$math_expr->evaluate("if(1, $number_a, $number_b)"),
|
309
|
"if(1, $number_a, $number_b)");
|
310
|
|
311
|
$this->assertEqual(
|
312
|
$number_a,
|
313
|
$math_expr->evaluate("if(1, $number_a)",
|
314
|
"if(1, $number_a)"));
|
315
|
|
316
|
$this->assertEqual(
|
317
|
$number_b,
|
318
|
$math_expr->evaluate("if(0, $number_a, $number_b)"),
|
319
|
"if(0, $number_a, $number_b)");
|
320
|
|
321
|
// Also add an expression so ensure it's evaluated.
|
322
|
$this->assertEqual(
|
323
|
$number_b,
|
324
|
$math_expr->evaluate("if($number_a > $number_b, $number_a, $number_b)"),
|
325
|
"if($number_a > $number_b, $number_a, $number_b)");
|
326
|
|
327
|
$this->assertEqual(
|
328
|
$number_b,
|
329
|
$math_expr->evaluate("if($number_a < $number_b, $number_b, $number_a)"),
|
330
|
"if($number_a < $number_b, $number_b, $number_a)");
|
331
|
}
|
332
|
}
|
333
|
|
334
|
}
|