Tester.php

<?php

namespace Tlf;

/**
 * Base class for test classes
 * @note main class file executes tests. Traits contain everything you use INSIDE a test
 */
class Tester {

    use Tester\Assertions;
    use Tester\Exceptions;
    use Tester\Databasing;
    use Tester\Utilities;
    use Tester\Server;
    use Tester\Other;

    protected $catchers = [];
    /**
     * Comparisons from a single test. Should be reset between tests.
     */
    protected $assertions = ['pass'=> 0, 'fail'=>0];
    protected $enabled = true;

    protected $options = [];

    /**
     * The cli class used to run the tests
     */
    public $cli = null;

    /**
     * The string name of the method being called for the current test. Like `"testSomething"`
     */
    public ?string $current_test = null;

    /**
     * @param $options usually args passed from the command line. 
     */
    public function __construct(array $options=[], $cli=null){
        $this->backward_compatability();
        $this->options = $options;
        if ($this->options['set_error_handler']??true){
            set_error_handler([$this,'throwError']);
        }
        $this->cli = $cli;
        $this->prepare();
    }
    public function throwError($errno, $errstr, $errfile, $errline) {
        throw new \ErrorException($errstr, $errno, 0, $errfile, $errline);
    }


    /**
     * @deprecated This method will be removed in v0.4
     */
    public function backward_compatability(){}


    /**
     * Get array of test methods names
     * @return array like `['testMethodOne', 'testMethodTwo']`
     */
    public function get_test_methods(){
        $list = [];
        foreach (get_class_methods($this) as $method){
            if ($method=='test')continue;
            if (substr($method,0,4)!='test')continue;
            $list[] = $method;
        }

        return $list;
    }

    /**
     * get a readable name from a test method name
     */
    public function get_test_name($method_name): string{
        $name = $method_name;
        if (substr($method_name,0,4)=='test')$name = substr($method_name,4);

        return $name;
    }

    public function run_test_method($method){
        $name = $this->get_test_name($method);
        if ($this->options['test']!=[]
            &&!in_array($name, $this->options['test'])){
            return false;
        }
        $test = 
        [
            'method'=>$method, 
            'error'=>null, 
            'name'=>$name,
            'pass'=>false,
            'enabled'=>$this->enabled,
        ];

        $this->assertions = ['pass'=>0, 'fail'=>0];
        $this->catchers = [];
        $bench_start = microtime(true);
        $ob_level = $this->startOb();
        try {
            $this->current_test = $method;
            $this->$method();
        } catch (\Throwable $t){
            $test['error'] = $t->__toString();
            echo $test['error'];
        } 
        $this->current_test = null;
        $test['enabled'] = $this->enabled;
        $test['assertions'] = $this->assertions;
        $test['output'] = $this->endOb($ob_level);
        $test['bench'] = $this->benchEnd($bench_start);

        if ($this->assertions['pass']>=1
            &&$this->assertions['fail']===0
            &&$test['error'] === null
        ){
            $test['pass'] = true;
        }
        return $test;
    }

    public function print_test_results($test){
        $status = $test['pass'] ? 'PASS' : 'FAIL';
        $symbol = $test['pass'] ? '+' : '-';
        if (in_array($test['name'], $this->options['test'])){
            if ($test['enabled']!=true)$symbol = '/';
            $str = str_repeat($symbol, 15);
            echo "\n$str ".$test['name']."[start] $str\n";
            echo $test['output'];


            if (($c=count($this->catchers))>0){
                echo "\n\n  EXCEPTION FAIL:{$c} exceptions were not handled.";
            }

            $class = get_class($this);
            echo "\n$str ".$test['name']."[end] ($class) $str";

            return;
        }

        if ($test['enabled']!=true)$symbol = '//';
        // $assertions =
            // '+'.$test['assertions']['pass']
            // .', -'.$test['assertions']['fail'];
            // ;

        $bench = '';
        if ($test['bench']['diff']>$this->options['bench.threshold']){
            $ms = $test['bench']['diff'] * 1000;
            $ms = number_format($ms,4);
            $bench=' '.$ms.'ms';
        }
        echo "\n  $symbol ".$test['name']. $bench; //." ($assertions)";

    }
    /**
     * Run tests
     *
     * @param $methods an array of method names to run as tests or NULL to run all methods beginning with 'test'
     */
    public function run(){
        $class = explode('\\',get_class($this));
        $name = array_pop($class);
        echo "\n". array_pop($class).'\\'.$name.': ';
        $methods = $this->get_test_methods();
        $results = [
            'class'=>get_class($this),
            'tests_run'=>0,
            'pass'=>0,
            'fail'=>0,
            'disabled'=>0,
        ];
        $tests = [];
        
        foreach ($methods as $method){
            $this->inverted = false;
            $this->enabled = true;

            $test = $this->run_test_method($method);
            if ($test===false)continue;
            $this->print_test_results($test);

            $tests[] = $test;

            $results['tests_run']++;
            if ($test['enabled']!==true){
                $results['disabled']++;
            } else if ($test['pass']===true){
                $results['pass']++;
            } else {
                $results['fail']++;
            }

            $test['enabled'] = $this->enabled;
        }
        // echo "\n  ".$results['fail'].' fail, '.$results['pass'].' pass';;

        $results['tests'] = $tests;
    
        return $results;
    }
    /**
     * @param $start_time a value from `microtime(true)`
     */
    public function benchEnd($start_time){
        $end = microtime(true);
        $diff = $end - $start_time;
        return [
            'start'=>$start_time,
            'end'=>$end,
            'diff'=>$diff
        ];
    }


}