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.