Files
phpredis/tests/TestSuite.php
michael-grunder d9c48b788d KeyDB: Get our tests passing against KeyDB.
This commit fixes our unit tests so they also pass against the KeyDB
server.  We didn't ned to change all that much.  Most of it was just
adding a version/keydb check.

The only change to PhpRedis itself was to relax the reply requirements
for XAUTOCLAIM.  Redis 7.0.0 added a third "these elements were recently
removed" reply which KeyDB does not have.

Fixes #2466
2024-03-22 07:47:43 -07:00

331 lines
10 KiB
PHP

<?php defined('PHPREDIS_TESTRUN') or die("Use TestRedis.php to run tests!\n");
/* A specific exception for when we skip a test */
class TestSkippedException extends Exception {}
// phpunit is such a pain to install, we're going with pure-PHP here.
class TestSuite
{
/* Host and port the unit tests will use */
private $str_host;
private $i_port = 6379;
/* Redis authentication we'll use */
private $auth;
/* Redis server version */
protected $version;
protected $is_keydb;
private static $_boo_colorize = false;
private static $BOLD_ON = "\033[1m";
private static $BOLD_OFF = "\033[0m";
private static $BLACK = "\033[0;30m";
private static $DARKGRAY = "\033[1;30m";
private static $BLUE = "\033[0;34m";
private static $PURPLE = "\033[0;35m";
private static $GREEN = "\033[0;32m";
private static $YELLOW = "\033[0;33m";
private static $RED = "\033[0;31m";
public static $errors = [];
public static $warnings = [];
public function __construct($str_host, $i_port, $auth) {
$this->str_host = $str_host;
$this->i_port = $i_port;
$this->auth = $auth;
}
public function getHost() { return $this->str_host; }
public function getPort() { return $this->i_port; }
public function getAuth() { return $this->auth; }
/**
* Returns the fully qualified host path,
* which may be used directly for php.ini parameters like session.save_path
*
* @return null|string
*/
protected function getFullHostPath()
{
return $this->str_host
? 'tcp://' . $this->str_host . ':' . $this->i_port
: null;
}
public static function make_bold($str_msg) {
return self::$_boo_colorize
? self::$BOLD_ON . $str_msg . self::$BOLD_OFF
: $str_msg;
}
public static function make_success($str_msg) {
return self::$_boo_colorize
? self::$GREEN . $str_msg . self::$BOLD_OFF
: $str_msg;
}
public static function make_fail($str_msg) {
return self::$_boo_colorize
? self::$RED . $str_msg . self::$BOLD_OFF
: $str_msg;
}
public static function make_warning($str_msg) {
return self::$_boo_colorize
? self::$YELLOW . $str_msg . self::$BOLD_OFF
: $str_msg;
}
protected function assertFalse($bool) {
if(!$bool)
return true;
$bt = debug_backtrace(false);
self::$errors []= sprintf("Assertion failed: %s:%d (%s)\n",
$bt[0]["file"], $bt[0]["line"], $bt[1]["function"]);
return false;
}
protected function assertTrue($bool, $msg='') {
if($bool)
return true;
$bt = debug_backtrace(false);
self::$errors []= sprintf("Assertion failed: %s:%d (%s) %s\n",
$bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $msg);
return false;
}
protected function assertInArray($ele, $arr, $cb = NULL) {
if ($cb && !is_callable($cb))
die("Fatal: assertInArray callback must be callable!\n");
if (($in = in_array($ele, $arr)) && (!$cb || $cb($arr[array_search($ele, $arr)])))
return true;
$bt = debug_backtrace(false);
$ex = $in ? 'validation' : 'missing';
self::$errors []= sprintf("Assertion failed: %s:%d (%s) [%s '%s']\n",
$bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $ex, $ele);
return false;
}
protected function assertArrayKey($arr, $key, $cb = NULL) {
if ($cb && !is_callable($cb))
die("Fatal: assertArrayKey callback must be callable\n");
if (($exists = isset($arr[$key])) && (!$cb || $cb($arr[$key])))
return true;
$bt = debug_backtrace(false);
$ex = $exists ? 'validation' : 'missing';
self::$errors []= sprintf("Assertion failed: %s:%d (%s) [%s '%s']\n",
$bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $ex, $key);
return false;
}
protected function assertValidate($val, $cb) {
if ( ! is_callable($cb))
die("Fatal: Callable assertValidate callback required\n");
if ($cb($val))
return true;
$bt = debug_backtrace(false);
self::$errors []= sprintf("Assertion failed: %s:%d (%s)\n--- VALUE ---\n%s\n",
$bt[0]["file"], $bt[0]["line"], $bt[1]["function"], print_r($val, true));
return false;
}
protected function assertThrowsMatch($arg, $cb, $regex = NULL) {
$threw = $match = false;
if ( ! is_callable($cb))
die("Fatal: Callable assertThrows callback required\n");
try {
$cb($arg);
} catch (Exception $ex) {
$threw = true;
$match = !$regex || preg_match($regex, $ex->getMessage());
}
if ($threw && $match)
return true;
$bt = debug_backtrace(false);
$ex = !$threw ? 'no exception' : "no match '$regex'";
self::$errors []= sprintf("Assertion failed: %s:%d (%s) [%s]\n",
$bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $ex);
return false;
}
protected function assertLess($a, $b) {
if($a < $b)
return;
$bt = debug_backtrace(false);
self::$errors[] = sprintf("Assertion failed (%s >= %s): %s: %d (%s\n",
print_r($a, true), print_r($b, true),
$bt[0]["file"], $bt[0]["line"], $bt[1]["function"]);
}
protected function assertEquals($a, $b) {
if($a === $b)
return;
$bt = debug_backtrace(false);
self::$errors []= sprintf("Assertion failed (%s !== %s): %s:%d (%s)\n",
print_r($a, true), print_r($b, true),
$bt[0]["file"], $bt[0]["line"], $bt[1]["function"]);
}
protected function assertPatternMatch($str_test, $str_regex) {
if (preg_match($str_regex, $str_test))
return;
$bt = debug_backtrace(false);
self::$errors []= sprintf("Assertion failed ('%s' doesnt match '%s'): %s:%d (%s)\n",
$str_test, $str_regex, $bt[0]["file"], $bt[0]["line"], $bt[1]["function"]);
}
protected function markTestSkipped($msg='') {
$bt = debug_backtrace(false);
self::$warnings []= sprintf("Skipped test: %s:%d (%s) %s\n",
$bt[0]["file"], $bt[0]["line"], $bt[1]["function"], $msg);
throw new TestSkippedException($msg);
}
private static function getMaxTestLen($arr_methods, $str_limit) {
$i_result = 0;
foreach ($arr_methods as $obj_method) {
$str_name = strtolower($obj_method->name);
if (substr($str_name, 0, 4) != 'test')
continue;
if ($str_limit && !strstr($str_name, $str_limit))
continue;
if (strlen($str_name) > $i_result) {
$i_result = strlen($str_name);
}
}
return $i_result;
}
private static function findFile($path, $file) {
$files = glob($path . '/*', GLOB_NOSORT);
foreach ($files as $test) {
$test = basename($test);
if (strcasecmp($file, $test) == 0)
return $path . '/' . $test;
}
return NULL;
}
/* Small helper method that tries to load a custom test case class */
public static function loadTestClass($class) {
$filename = "{$class}.php";
if (($sp = getenv('PHPREDIS_TEST_SEARCH_PATH'))) {
$fullname = self::findFile($sp, $filename);
} else {
$fullname = self::findFile(__DIR__, $filename);
}
if ( ! $fullname)
die("Fatal: Couldn't find $filename\n");
require_once($fullname);
if ( ! class_exists($class))
die("Fatal: Loaded '$filename' but didn't find class '$class'\n");
/* Loaded the file and found the class, return it */
return $class;
}
/* Flag colorization */
public static function flagColorization($boo_override) {
self::$_boo_colorize = $boo_override && function_exists('posix_isatty') &&
posix_isatty(STDOUT);
}
public static function run($className, $str_limit = NULL, $str_host = NULL, $i_port = NULL, $auth = NULL) {
/* Lowercase our limit arg if we're passed one */
$str_limit = $str_limit ? strtolower($str_limit) : $str_limit;
$rc = new ReflectionClass($className);
$methods = $rc->GetMethods(ReflectionMethod::IS_PUBLIC);
$i_max_len = self::getMaxTestLen($methods, $str_limit);
foreach($methods as $m) {
$name = $m->name;
if(substr($name, 0, 4) !== 'test')
continue;
/* If we're trying to limit to a specific test and can't match the
* substring, skip */
if ($str_limit && strstr(strtolower($name), $str_limit)===FALSE) {
continue;
}
$str_out_name = str_pad($name, $i_max_len + 1);
echo self::make_bold($str_out_name);
$count = count($className::$errors);
$rt = new $className($str_host, $i_port, $auth);
try {
$rt->setUp();
$rt->$name();
if ($count === count($className::$errors)) {
$str_msg = self::make_success('PASSED');
} else {
$str_msg = self::make_fail('FAILED');
}
//echo ($count === count($className::$errors)) ? "." : "F";
} catch (Exception $e) {
/* We may have simply skipped the test */
if ($e instanceof TestSkippedException) {
$str_msg = self::make_warning('SKIPPED');
} else {
$className::$errors[] = "Uncaught exception '".$e->getMessage()."' ($name)\n";
$str_msg = self::make_fail('FAILED');
}
}
echo "[" . $str_msg . "]\n";
}
echo "\n";
echo implode('', $className::$warnings) . "\n";
if(empty($className::$errors)) {
echo "All tests passed. \o/\n";
return 0;
}
echo implode('', $className::$errors);
return 1;
}
}
?>