'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 = 0.00000001, $group = 'Other') { // Check for NaN and Inf because the abs() and sign() code won't like those. $equal = FALSE // Equal if both an infinity. || (is_infinite($first) && is_infinite($second)) // Equal if both NaN. || (is_nan($first) && is_nan($second)) // Equal if same absolute value (within limits) and same sign. || ((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)"); } } }