math_expression.test
File
-
tests/
math_expression.test
View source
<?php
/**
* Tests the MathExpression library of ctools.
*/
class CtoolsMathExpressionTestCase extends DrupalWebTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'Math expressions',
'description' => 'Test the math expression library of ctools.',
'group' => 'ctools',
'dependencies' => array(
'ctools',
),
);
}
/**
* {@inheritdoc}
*/
public function setUp(array $modules = array()) {
$modules[] = 'ctools';
$modules[] = 'ctools_plugin_test';
parent::setUp($modules);
}
/**
* Return the sign of the numeric arg $n as an integer -1, 0, 1.
*
* Note: Not defined when $n is Infinity or NaN (or NULL or ...)!
*
* @param int|float $n
* The number to test.
*
* @return int
* -1 if the $n is negative, 0 if $n is zero or 1 if $n is positive.
*
* @see gmp_sign()
*/
protected static function sign($n) {
return ($n > 0) - ($n < 0);
}
/**
* Returns a random number between 0 and 1.
*
* @return float
* A random number between 0 and 1 inclusive.
*/
protected function rand01() {
return mt_rand(0, PHP_INT_MAX) / PHP_INT_MAX;
}
/**
* A custom assertion with checks the values in a certain range.
*
* @param float $first
* A value to check for equality.
* @param float $second
* A value to check for equality.
* @param string $message
* The message describing the correct behaviour, eg. "2/4 equals 1/2". The
* default message is used if this value is empty.
* @param float $delta
* The precision with which values must match. This accounts for rounding
* errors and imprecise representation errors in the floating point format.
* The value passed in should ideally be proportional to the values being
* compared.
* @param string $group
* Which group this assert belongs to.
*
* @return bool
* TRUE if the assertion was correct (that is, $first == $second within the
* given limits), FALSE otherwise.
*/
protected function assertFloat($first, $second, $message = '', $delta = 1.0E-8, $group = 'Other') {
// Check for NaN and Inf because the abs() and sign() code won't like those.
$equal = FALSE || is_infinite($first) && is_infinite($second) || is_nan($first) && is_nan($second) || abs($first - $second) <= $delta && self::sign($first) === self::sign($second);
if (empty($message)) {
$default = t('Value !first is equal to value !second.', array(
'!first' => var_export($first, TRUE),
'!second' => var_export($second, TRUE),
));
$message = $default;
}
return $this->assert($equal, $message, $group);
}
/**
* Test some arithmetic handling.
*/
public function testArithmetic() {
$math_expr = new ctools_math_expr();
$this->assertEqual($math_expr->evaluate('2'), 2, 'Check Literal 2');
$this->assertEqual($math_expr->e('2+1'), $math_expr->evaluate('2+1'), 'Check that e() and evaluate() are equivalent.');
foreach (range(1, 4) as $n) {
// Test constant expressions.
$random_number = mt_rand(0, 20);
$this->assertEqual($random_number, $math_expr->evaluate((string) $random_number), "Literal {$random_number}");
// Test simple arithmetic.
$number_a = mt_rand(-55, 777);
$number_b = mt_rand(-555, 77);
$this->assertEqual($number_a + $number_b, $math_expr->evaluate("{$number_a} + {$number_b}"), "Addition: {$number_a} + {$number_b}");
$this->assertEqual($number_a - $number_b, $math_expr->evaluate("{$number_a} - {$number_b}"), "Subtraction: {$number_a} + {$number_b}");
$this->assertFloat($number_a * $number_b, $math_expr->evaluate("{$number_a} * {$number_b}"), "Multiplication: {$number_a} * {$number_b} = " . $number_a * $number_b);
$this->assertFloat($number_a / $number_b, $math_expr->evaluate("{$number_a} / {$number_b}"), "Division: {$number_a} / {$number_b} = " . $number_a / $number_b);
// Test Associative property.
$number_c = mt_rand(-99, 77);
$this->assertEqual($math_expr->evaluate("{$number_a} + ({$number_b} + {$number_c})"), $math_expr->evaluate("({$number_a} + {$number_b}) + {$number_c}"), "Associative: {$number_a} + ({$number_b} + {$number_c})");
$this->assertEqual($math_expr->evaluate("{$number_a} * ({$number_b} * {$number_c})"), $math_expr->evaluate("({$number_a} * {$number_b}) * {$number_c}"), "Associative: {$number_a} * ({$number_b} * {$number_c})");
// Test Commutative property.
$this->assertEqual($math_expr->evaluate("{$number_a} + {$number_b}"), $math_expr->evaluate("{$number_b} + {$number_a}"), "Commutative: {$number_a} + {$number_b}");
$this->assertEqual($math_expr->evaluate("{$number_a} * {$number_b}"), $math_expr->evaluate("{$number_b} * {$number_a}"), "Commutative: {$number_a} * {$number_b}");
// Test Distributive property.
$this->assertEqual($math_expr->evaluate("({$number_a} + {$number_b}) * {$number_c}"), $math_expr->evaluate("({$number_a} * {$number_c} + {$number_b} * {$number_c})"), "Distributive: ({$number_a} + {$number_b}) * {$number_c}");
// @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.
$random_number = mt_rand(1, 15);
$random_power = mt_rand(-15, 15);
$this->assertFloat(pow($random_number, $random_power), $math_expr->evaluate("{$random_number} ^ {$random_power}"), "{$random_number} ^ {$random_power}");
$this->assertFloat(pow($random_number, $random_power), $math_expr->evaluate("pow({$random_number}, {$random_power})"), "pow({$random_number}, {$random_power})");
}
}
/**
* Test various built-in transcendental and extended functions.
*/
public function testBuildInFunctions() {
$math_expr = new ctools_math_expr();
foreach (range(1, 4) as $n) {
$random_double = $this->rand01();
$random_int = mt_rand(-65535, 65535);
$this->assertFloat(sin($random_double), $math_expr->evaluate("sin({$random_double})"), "sin({$random_double})");
$this->assertFloat(cos($random_double), $math_expr->evaluate("cos({$random_double})"), "cos({$random_double})");
$this->assertFloat(tan($random_double), $math_expr->evaluate("tan({$random_double})"), "tan({$random_double})");
$this->assertFloat(exp($random_double), $math_expr->evaluate("exp({$random_double})"), "exp({$random_double})");
$this->assertFloat(sqrt($random_double), $math_expr->evaluate("sqrt({$random_double})"), "sqrt({$random_double})");
$this->assertFloat(log($random_double), $math_expr->evaluate("ln({$random_double})"), "ln({$random_double})");
$this->assertFloat(round($random_double), $math_expr->evaluate("round({$random_double})"), "round({$random_double})");
$random_real = $random_double + $random_int;
$this->assertFloat(abs($random_real), $math_expr->evaluate('abs(' . $random_real . ')'), "abs({$random_real})");
$this->assertEqual(round($random_real), $math_expr->evaluate('round(' . $random_real . ')'), "round({$random_real})");
$this->assertEqual(ceil($random_real), $math_expr->evaluate('ceil(' . $random_real . ')'), "ceil({$random_real})");
$this->assertEqual(floor($random_real), $math_expr->evaluate('floor(' . $random_real . ')'), "floor({$random_real})");
}
$this->assertFloat(time(), $math_expr->evaluate('time()'), "time()");
$random_double_a = $this->rand01();
$random_double_b = $this->rand01();
$this->assertFloat(max($random_double_a, $random_double_b), $math_expr->evaluate("max({$random_double_a}, {$random_double_b})"), "max({$random_double_a}, {$random_double_b})");
$this->assertFloat(min($random_double_a, $random_double_b), $math_expr->evaluate("min({$random_double_a}, {$random_double_b})"), "min({$random_double_a}, {$random_double_b})");
}
/**
* Test variable handling.
*/
public function testVariables() {
$math_expr = new ctools_math_expr();
// We should have a definition of pi:
$this->assertFloat(pi(), $math_expr->evaluate('pi'));
// And a definition of e:
$this->assertFloat(exp(1), $math_expr->evaluate('e'));
$number_a = 5;
$number_b = 10;
// Store the first number and use it on a calculation.
$math_expr->evaluate("var = {$number_a}");
$this->assertEqual($number_a + $number_b, $math_expr->evaluate("var + {$number_b}"));
// Change the value and check the new value is used.
$math_expr->evaluate("var = {$number_b}");
$this->assertEqual($number_b + $number_b, $math_expr->evaluate("var + {$number_b}"), "var + {$number_b}");
// Store another number and use it on a calculation.
$math_expr->evaluate("var = {$number_a}");
$math_expr->evaluate("newvar = {$number_a}");
$this->assertEqual($number_a + $number_a, $math_expr->evaluate('var + newvar'), 'var + newvar');
$this->assertFloat($number_a / $number_b, $math_expr->evaluate("var / {$number_b}"), "var / {$number_b}");
}
/**
* Test custom function handling.
*/
public function testCustomFunctions() {
$math_expr = new ctools_math_expr();
$number_a = mt_rand(5, 10);
$number_b = mt_rand(5, 10);
// Create a one-argument function.
$math_expr->evaluate("f(x) = 2 * x");
$this->assertEqual($number_a * 2, $math_expr->evaluate("f({$number_a})"));
$this->assertEqual($number_b * 2, $math_expr->evaluate("f({$number_b})"));
// Create a two-argument function.
$math_expr->evaluate("g(x, y) = 2 * x + y");
$this->assertEqual($number_a * 2 + $number_b, $math_expr->evaluate("g({$number_a}, {$number_b})"), "g({$number_a}, {$number_b})");
// Use a custom function in another function.
$this->assertEqual(($number_a * 2 + $number_b) * 2, $math_expr->evaluate("f(g({$number_a}, {$number_b}))"), "f(g({$number_a}, {$number_b}))");
}
/**
* Test conditional handling.
*/
public function testIf() {
$math_expr = new ctools_math_expr();
$number_a = mt_rand(1, 10);
$number_b = mt_rand(11, 20);
foreach (range(1, 4) as $n) {
// @todo: Doesn't work with negative numbers.
if ($n == 2 || $n == 4) {
//$number_a = -$number_a;
}
if ($n == 3 || $n == 4) {
//$number_b = -$number_b;
}
$this->assertEqual($number_a, $math_expr->evaluate("if(1, {$number_a}, {$number_b})"), "if(1, {$number_a}, {$number_b})");
$this->assertEqual($number_a, $math_expr->evaluate("if(1, {$number_a})", "if(1, {$number_a})"));
$this->assertEqual($number_b, $math_expr->evaluate("if(0, {$number_a}, {$number_b})"), "if(0, {$number_a}, {$number_b})");
// Also add an expression so ensure it's evaluated.
$this->assertEqual($number_b, $math_expr->evaluate("if({$number_a} > {$number_b}, {$number_a}, {$number_b})"), "if({$number_a} > {$number_b}, {$number_a}, {$number_b})");
$this->assertEqual($number_b, $math_expr->evaluate("if({$number_a} < {$number_b}, {$number_b}, {$number_a})"), "if({$number_a} < {$number_b}, {$number_b}, {$number_a})");
}
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
CtoolsMathExpressionTestCase | Tests the MathExpression library of ctools. |