<?php
namespace Tlf\Tester;
class Runner extends \Tlf\Cli {
public $server_port = null;
public function backward_compatability(){
if (isset($this->args['dir.test'])&&!is_array($this->args['dir.test'])){
$this->args['dir.test'] = [$this->args['dir.test']];
}
}
/**
* get the server host
* @if called by `phptest server`, @then generate random port & write to file
* @if called by running `->get()` tests, @then load port from file
*
*/
public function get_server_host(){
if ($this->server_port!=null)return 'localhost:'.$this->server_port;
$dir = getcwd() .'/'. $this->args['server.dir'];
$file = $dir.'/.phptest-host';
if ($this->command == 'server'){
$port = random_int(3001, 5000);
file_put_contents($file, $port);
return 'localhost:'.$port;
}
$this->server_port = file_get_contents($file);
return 'localhost:'.$this->server_port;
}
/**
* get the server protocol
* @todo add configurability & dynamic stuff
*/
public function get_server_protocol(){
return 'http://';
}
/**
* Start a localhost server for testing
* @warning this early implementation will change
*/
public function start_server($cli, $args){
$host = $this->get_server_host();
$dir = $this->pwd .'/'. $this->args['server.dir'];
$delivery_script = $dir .'/'. $this->args['server.router'];
$command = "php -S $host -t $dir $delivery_script";
echo "Execute:\n $command\n\n";
system($command);
}
/**
* Copies sample test files into `getcwd().'/test'`
*/
public function init($cli, $args){
$dir = getcwd().'/test';
$continue = readline("Initialize test dir at $dir? (y/n) ");
if ($continue!=='y')return;
mkdir($dir);
mkdir($dir.'/run');
mkdir($dir.'/src');
mkdir($dir.'/input');
mkdir($dir.'/Server');
mkdir($dir.'/input/Compare');
$files = [
'run/Compare.php',
'run/Server.php',
"src/Tester.php",
'input/Compare/Compare.php',
'input/Compare/Compare.txt',
'Server/deliver.php',
'bootstrap.php',
'config.json',
];
foreach ($files as $f){
$dest = $dir.'/'.$f;
if (file_exists($dest)){
echo "\nSkip: $dest";
} else {
echo "\nCreate: $dest";
copy(dirname(__DIR__).'/test/'.$f, $dir.'/'.$f);
}
}
}
public function require_directory($path){
foreach (scandir($path) as $f){
if ($f=='.'||$f=='..')continue;
if (is_dir($path.'/'.$f)){
$this->require_directory($path.'/'.$f);
} else if (substr($f,-4)=='.php'){
(function() use ($path, $f){
require_once($path.'/'.$f);
})();
}
}
}
/**
* executes all tests inside test directories (config `dir.test`)
*/
public function run_dir($cli, $args){
$info = [
'pass'=>0,
'fail'=>0,
'tests_run'=>0,
'disabled'=>0,
];
$dir = $cli->pwd;
// load required files
foreach ($args['file.require'] as $file){
$path = $dir.'/'.$file;
// var_dump($path);
if (is_dir($path)){
$this->require_directory($path);
} else {
(function() use ($path){
require_once($path);
})();
}
}
$phpFiles = $this->get_php_files($dir, $args['dir.test']);
$excludes = $args['dir.exclude'];
$test_files = [];
$classes_to_test = $args['class'] ?? [];
foreach ($phpFiles as $relPath){
if ($this->is_excluded($excludes, $relPath))continue;
$filePath = $dir.'/'.$relPath;
$class = $this->get_test_class($filePath);
$class_base = substr($class, strrpos($class, '\\')+1);
if (count($classes_to_test)>0&&!in_array($class_base, $classes_to_test))continue;
$test_files[$filePath] = $class;
}
//sort test files if you like
$tests = [];
foreach ($test_files as $file=>$class){
$results = $this->test_class($class, $args,$cli);
if ($results===false)continue;
$info['pass']+=$results['pass'];
$info['fail']+=$results['fail'];
$info['disabled']+=$results['disabled'];
$info['tests_run']+=$results['tests_run'];
$info['class'][$class] = $results;
}
echo "\n";
echo "\nTests: ".$info['tests_run'];
echo "\nPass: ".$info['pass'];
echo "\nFail: ".$info['fail'];
echo "\nDisabled: ".$info['disabled'];
echo "\n";
return $info;
}
public function test_class($class, $args, $cli){
// $ob_level = Utility::startOb();
//run the test class
if (!class_exists($class,true))return false;
$tester = new $class($args, $cli);
$results = $tester->run();
// $output = Utility::endOb($ob_level);
return $results;
}
/**
* Get all php files for testing. @see(get_test_class) is used to filter out files that don't contain a test class.
*
* @param string $dir the root directory for the project
* @param array $sub_dirs the sub-directories where files should be searched for
*/
public function get_php_files(string $dir, array $sub_dirs){
// find all files that need testing
$files = [];
foreach ($sub_dirs as $sub_dir){
$search_dir = $dir.'/'.$sub_dir;
$files = array_merge($files, \Tlf\Tester\Utility::getAllFiles($search_dir,$dir,'.php'));
}
return $files;
}
/**
* Check if a file is excluded from testing
*
* @param $excludes generally, the 'dir.excludes' config
* @param $relPath the relative path of a file that we're checking for
* @return bool true or false
*/
public function is_excluded($excludes, $relPath){
//check if file is excluded from testing
foreach ($excludes as $e){
$re = $relPath;
if ($re[0]!='/')$re = '/'.$re;
if ($e[0]!='/')$e = '/'.$e;
if (substr($relPath,0,strlen($e))==$e)return true;
}
if (in_array($relPath, $excludes))return true;
return false;
}
/**
* Get name of class in file. The class in the file must be a subclass of \Tlf\Tester
*
* @param $filePath the path to the file containing a test class
* @return class name or null
* @side_effect require_once the file
*/
public function get_test_class($filePath){
(function() use ($filePath){
require_once($filePath);
})();
$class = Utility::getClassFromFile($filePath);
if ($class==null){
$this->report("No class found in $filePath");
return null;
}
if (!is_a($class, '\\Tlf\\Tester', true))return null;
return $class;
}
public function report($msg){
echo "\n$msg\n";
}
}