From 792a25d57b73ac69f73713b5a2c54ee76c2ff590 Mon Sep 17 00:00:00 2001 From: David Grudl Date: Tue, 3 Aug 2010 14:42:32 +0200 Subject: [PATCH] added tests --- tests/DibiFluent.cloning.phpt | 72 ++++++ tests/NetteTest/Assert.php | 138 +++++++++++ tests/NetteTest/RunTests.php | 43 ++++ tests/NetteTest/TestCase.php | 400 ++++++++++++++++++++++++++++++++ tests/NetteTest/TestHelpers.php | 312 +++++++++++++++++++++++++ tests/NetteTest/TestRunner.php | 230 ++++++++++++++++++ tests/config.ini | 67 ++++++ tests/data/sample.mdb | Bin 0 -> 286720 bytes tests/data/sample.mysql | 149 ++++++++++++ tests/data/sample.sdb | Bin 0 -> 7168 bytes tests/data/sample.sdb3 | Bin 0 -> 8192 bytes tests/initialize.php | 26 +++ 12 files changed, 1437 insertions(+) create mode 100644 tests/DibiFluent.cloning.phpt create mode 100644 tests/NetteTest/Assert.php create mode 100644 tests/NetteTest/RunTests.php create mode 100644 tests/NetteTest/TestCase.php create mode 100644 tests/NetteTest/TestHelpers.php create mode 100644 tests/NetteTest/TestRunner.php create mode 100644 tests/config.ini create mode 100644 tests/data/sample.mdb create mode 100644 tests/data/sample.mysql create mode 100644 tests/data/sample.sdb create mode 100644 tests/data/sample.sdb3 create mode 100644 tests/initialize.php diff --git a/tests/DibiFluent.cloning.phpt b/tests/DibiFluent.cloning.phpt new file mode 100644 index 00000000..95dcb4bf --- /dev/null +++ b/tests/DibiFluent.cloning.phpt @@ -0,0 +1,72 @@ +select('*')->from('table')->where('x=1'); +$dolly = clone $fluent; +$dolly->where('y=1'); +$dolly->clause('FOO'); + +$fluent->test(); +$dolly->test(); + + + +$fluent = dibi::select('id')->from('table')->where('id = %i',1); +$dolly = clone $fluent; +$dolly->where('cd = %i',5); + +$fluent->test(); +$dolly->test(); + + + +$fluent = dibi::select("*")->from("table"); +$dolly = clone $fluent; +$dolly->removeClause("select")->select("count(*)"); + +$fluent->test(); +$dolly->test(); + + + +__halt_compiler() ?> + +------EXPECT------ +SELECT * +FROM [table] +WHERE x=1 + +SELECT * +FROM [table] +WHERE x=1 AND y=1 FOO + +SELECT [id] +FROM [table] +WHERE id = 1 + +SELECT [id] +FROM [table] +WHERE id = 1 AND cd = 5 + +SELECT * +FROM [table] + +SELECT count(*) +FROM [table] diff --git a/tests/NetteTest/Assert.php b/tests/NetteTest/Assert.php new file mode 100644 index 00000000..af0ec064 --- /dev/null +++ b/tests/NetteTest/Assert.php @@ -0,0 +1,138 @@ +getCode() ? '#' . $var->getCode() . ' ' : '') . $var->getMessage(); + + } elseif (is_object($var)) { + $arr = (array) $var; + return "object(" . get_class($var) . ") (" . count($arr) . ")"; + + } elseif (is_resource($var)) { + return "resource(" . get_resource_type($var) . ")"; + + } else { + return "unknown type"; + } + } + + + + /** + * Returns message and file and line from call stack. + * @param string + * @return void + */ + private static function note($message) + { + echo $message; + $trace = debug_backtrace(); + if (isset($trace[1]['file'], $trace[1]['line'])) { + echo ' in file ' . $trace[1]['file'] . ' on line ' . $trace[1]['line']; + } + echo "\n\n"; + } + +} diff --git a/tests/NetteTest/RunTests.php b/tests/NetteTest/RunTests.php new file mode 100644 index 00000000..d2d641ed --- /dev/null +++ b/tests/NetteTest/RunTests.php @@ -0,0 +1,43 @@ + +Nette Test Framework (v0.3) +--------------------------- + +Usage: + php RunTests.php [options] [file or directory] + +Options: + -p Specify PHP-CGI executable to run. + -c Look for php.ini in directory or use as php.ini. + -d key=val Define INI entry 'key' with value 'val'. + -l Specify path to shared library files (LD_LIBRARY_PATH) + -e Load php environment + -s Show information about skipped tests + +parseConfigFile(); + $manager->parseArguments(); + $res = $manager->run(); + die($res ? 0 : 1); + +} catch (Exception $e) { + echo 'Error: ', $e->getMessage(), "\n"; + die(2); +} diff --git a/tests/NetteTest/TestCase.php b/tests/NetteTest/TestCase.php new file mode 100644 index 00000000..e84b5498 --- /dev/null +++ b/tests/NetteTest/TestCase.php @@ -0,0 +1,400 @@ +file = (string) $testFile; + $this->sections = self::parseSections($this->file); + } + + + + /** + * Runs single test. + * @return void + */ + public function run() + { + // pre-skip? + $options = $this->sections['options']; + if (isset($options['skip'])) { + $message = $options['skip'] ? $options['skip'] : 'No message.'; + throw new TestCaseException($message, TestCaseException::SKIPPED); + + } elseif (isset($options['phpversion'])) { + $operator = '>='; + if (preg_match('#^(<=|le|<|lt|==|=|eq|!=|<>|ne|>=|ge|>|gt)#', $options['phpversion'], $matches)) { + $options['phpversion'] = trim(substr($options['phpversion'], strlen($matches[1]))); + $operator = $matches[1]; + } + if (version_compare($options['phpversion'], $this->phpVersion, $operator)) { + throw new TestCaseException("Requires PHP $operator $options[phpversion].", TestCaseException::SKIPPED); + } + } + + $this->execute(); + $output = $this->output; + $headers = array_change_key_case(self::parseLines($this->headers, ':'), CASE_LOWER); + $tests = 0; + + // post-skip? + if (isset($headers['x-nette-test-skip'])) { + throw new TestCaseException($headers['x-nette-test-skip'], TestCaseException::SKIPPED); + } + + // compare output + $expectedOutput = $this->getExpectedOutput(); + if ($expectedOutput !== NULL) { + $tests++; + $binary = (bool) preg_match('#[\x00-\x08\x0B\x0C\x0E-\x1F]#', $expectedOutput); + if ($binary) { + if ($expectedOutput !== $output) { + throw new TestCaseException("Binary output doesn't match."); + } + } else { + $output = self::normalize($output, isset($options['keeptrailingspaces'])); + $expectedOutput = self::normalize($expectedOutput, isset($options['keeptrailingspaces'])); + if (!$this->compare($output, $expectedOutput)) { + throw new TestCaseException("Output doesn't match."); + } + } + } + + // compare headers + $expectedHeaders = $this->getExpectedHeaders(); + if ($expectedHeaders !== NULL) { + $tests++; + $expectedHeaders = self::normalize($expectedHeaders, FALSE); + $expectedHeaders = array_change_key_case(self::parseLines($expectedHeaders, ':'), CASE_LOWER); + foreach ($expectedHeaders as $name => $header) { + if (!isset($headers[$name])) { + throw new TestCaseException("Missing header '$name'."); + + } elseif (!$this->compare($headers[$name], $header)) { + throw new TestCaseException("Header '$name' doesn't match."); + } + } + } + + if (!$tests) { // expecting no output + if (trim($output) !== '') { + throw new TestCaseException("Empty output doesn't match."); + } + } + } + + + + /** + * Sets PHP command line. + * @param string + * @param string + * @param string + * @return TestCase provides a fluent interface + */ + public function setPhp($binary, $args, $environment) + { + if (isset(self::$cachedPhp[$binary])) { + $this->phpVersion = self::$cachedPhp[$binary]; + + } else { + exec($environment . escapeshellarg($binary) . ' -v', $output, $res); + if ($res !== 0 && $res !== 255) { + throw new Exception("Unable to execute '$binary -v'."); + } + + if (!preg_match('#^PHP (\S+).*cli#i', $output[0], $matches)) { + throw new Exception("Unable to detect PHP version (output: $output[0])."); + } + + $this->phpVersion = self::$cachedPhp[$binary] = $matches[1]; + } + + $this->cmdLine = $environment . escapeshellarg($binary) . $args; + return $this; + } + + + + /** + * Execute test. + * @return array + */ + private function execute() + { + $this->headers = $this->output = NULL; + + $tempFile = tempnam('', 'tmp'); + if (!$tempFile) { + throw new Exception("Unable to create temporary file."); + } + + $command = $this->cmdLine; + if (isset($this->sections['options']['phpini'])) { + foreach (explode(';', $this->sections['options']['phpini']) as $item) { + $command .= " -d " . escapeshellarg(trim($item)); + } + } + $command .= ' ' . escapeshellarg($this->file) . ' > ' . escapeshellarg($tempFile); + + chdir(dirname($this->file)); + exec($command, $foo, $res); + if ($res === 255) { + // exit_status 255 => parse or fatal error + + } elseif ($res !== 0) { + throw new Exception("Unable to execute '$command'."); + + } + + $this->output = file_get_contents($tempFile); + unlink($tempFile); + } + + + + /** + * Returns test file section. + * @return string + */ + public function getSection($name) + { + return isset($this->sections[$name]) ? $this->sections[$name] : NULL; + } + + + + /** + * Returns test name. + * @return string + */ + public function getName() + { + return $this->sections['options']['name']; + } + + + + /** + * Returns test output. + * @return string + */ + public function getOutput() + { + return $this->output; + } + + + + /** + * Returns output headers. + * @return string + */ + public function getHeaders() + { + return $this->headers; + } + + + + /** + * Returns expected output. + * @return string + */ + public function getExpectedOutput() + { + if (isset($this->sections['expect'])) { + return $this->sections['expect']; + + } elseif (is_file($expFile = str_replace('.phpt', '', $this->file) . '.expect')) { + return file_get_contents($expFile); + + } else { + return NULL; + } + } + + + + /** + * Returns expected headers. + * @return string + */ + public function getExpectedHeaders() + { + return $this->getSection('expectheaders'); + } + + + + /********************* helpers ****************d*g**/ + + + + /** + * Splits file into sections. + * @param string file + * @return array + */ + public static function parseSections($testFile) + { + $content = file_get_contents($testFile); + $sections = array( + 'options' => array(), + ); + + // phpDoc + $phpDoc = preg_match('#^/\*\*(.*?)\*/#ms', $content, $matches) ? trim($matches[1]) : ''; + preg_match_all('#^\s*\*\s*@(\S+)(.*)#mi', $phpDoc, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $sections['options'][strtolower($match[1])] = isset($match[2]) ? trim($match[2]) : TRUE; + } + $sections['options']['name'] = preg_match('#^\s*\*\s*TEST:(.*)#mi', $phpDoc, $matches) ? trim($matches[1]) : $testFile; + + // file parts + $tmp = preg_split('#^-{3,}([^\s-]+)-{1,}(?:\r?\n|$)#m', $content, -1, PREG_SPLIT_DELIM_CAPTURE); + $i = 1; + while (isset($tmp[$i])) { + $sections[strtolower($tmp[$i])] = $tmp[$i+1]; + $i += 2; + } + return $sections; + } + + + + /** + * Splits HTTP headers into array. + * @param string + * @param string + * @return array + */ + public static function parseLines($raw, $separator) + { + $headers = array(); + foreach (explode("\r\n", $raw) as $header) { + $a = strpos($header, $separator); + if ($a !== FALSE) { + $headers[trim(substr($header, 0, $a))] = (string) trim(substr($header, $a + 1)); + } + } + return $headers; + } + + + + /** + * Compares results. + * @param string + * @param string + * @return bool + */ + public static function compare($left, $right) + { + $right = strtr($right, array( + '%a%' => '[^\r\n]+', // one or more of anything except the end of line characters + '%a?%'=> '[^\r\n]*', // zero or more of anything except the end of line characters + '%A%' => '.+', // one or more of anything including the end of line characters + '%A?%'=> '.*', // zero or more of anything including the end of line characters + '%s%' => '[\t ]+', // one or more white space characters except the end of line characters + '%s?%'=> '[\t ]*', // zero or more white space characters except the end of line characters + '%S%' => '\S+', // one or more of characters except the white space + '%S?%'=> '\S*', // zero or more of characters except the white space + '%c%' => '[^\r\n]', // a single character of any sort (except the end of line) + '%d%' => '[0-9]+', // one or more digits + '%d?%'=> '[0-9]*', // zero or more digits + '%i%' => '[+-]?[0-9]+', // signed integer value + '%f%' => '[+-]?\.?\d+\.?\d*(?:[Ee][+-]?\d+)?', // floating point number + '%h%' => '[0-9a-fA-F]+',// one or more HEX digits + '%ns%'=> '(?:[_0-9a-zA-Z\\\\]+\\\\|N)?',// PHP namespace + '%[^' => '[^', // reg-exp + '%[' => '[', // reg-exp + ']%' => ']+', // reg-exp + + '.' => '\.', '\\' => '\\\\', '+' => '\+', '*' => '\*', '?' => '\?', '[' => '\[', '^' => '\^', ']' => '\]', '$' => '\$', '(' => '\(', ')' => '\)', // preg quote + '{' => '\{', '}' => '\}', '=' => '\=', '!' => '\!', '>' => '\>', '<' => '\<', '|' => '\|', ':' => '\:', '-' => '\-', "\x00" => '\000', '#' => '\#', // preg quote + )); + + return (bool) preg_match("#^$right$#s", $left); + } + + + + /** + * Normalizes whitespace + * @param string + * @param bool + * @return string + */ + public static function normalize($s, $keepTrailingSpaces) + { + $s = str_replace("\n", PHP_EOL, str_replace("\r\n", "\n", $s)); // normalize EOL + if (!$keepTrailingSpaces) { + $s = preg_replace("#[\t ]+(\r?\n)#", '$1', $s); // multiline right trim + $s = rtrim($s); // ending trim + } + return $s; + } + +} + + + +/** + * Single test exception. + * + * @author David Grudl + * @package Nette\Test + */ +class TestCaseException extends Exception +{ + const SKIPPED = 1; + +} diff --git a/tests/NetteTest/TestHelpers.php b/tests/NetteTest/TestHelpers.php new file mode 100644 index 00000000..eb6a0ee7 --- /dev/null +++ b/tests/NetteTest/TestHelpers.php @@ -0,0 +1,312 @@ +getBasename() === '.gitignore') { + // ignore + } elseif ($entry->isDir()) { + rmdir($entry); + } else { + unlink($entry); + } + } + } + + + + /** + * Returns current test section. + * @param string + * @param string + * @return mixed + */ + public static function getSection($file, $section) + { + if (!isset(self::$sections[$file])) { + self::$sections[$file] = TestCase::parseSections($file); + } + + $lowerSection = strtolower($section); + if (!isset(self::$sections[$file][$lowerSection])) { + throw new Exception("Missing section '$section' in file '$file'."); + } + + if (in_array($section, array('GET', 'POST', 'SERVER'), TRUE)) { + return TestCase::parseLines(self::$sections[$file][$lowerSection], '='); + } else { + return self::$sections[$file][$lowerSection]; + } + } + + + + /** + * Writes new message. + * @param string + * @return void + */ + public static function note($message = NULL) + { + echo $message ? "$message\n\n" : "===\n\n"; + } + + + + /** + * Dumps information about a variable in readable format. + * @param mixed variable to dump + * @param string + * @return mixed variable itself or dump + */ + public static function dump($var, $message = NULL) + { + if ($message) { + echo $message . (preg_match('#[.:?]$#', $message) ? ' ' : ': '); + } + + self::_dump($var, 0); + echo "\n"; + return $var; + } + + + + private static function _dump(& $var, $level = 0) + { + static $tableUtf, $tableBin, $reBinary = '#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u'; + if ($tableUtf === NULL) { + foreach (range("\x00", "\xFF") as $ch) { + if (ord($ch) < 32 && strpos("\r\n\t", $ch) === FALSE) $tableUtf[$ch] = $tableBin[$ch] = '\\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT); + elseif (ord($ch) < 127) $tableUtf[$ch] = $tableBin[$ch] = $ch; + else { $tableUtf[$ch] = $ch; $tableBin[$ch] = '\\x' . dechex(ord($ch)); } + } + $tableBin["\\"] = '\\\\'; + $tableBin["\r"] = '\\r'; + $tableBin["\n"] = '\\n'; + $tableBin["\t"] = '\\t'; + $tableUtf['\\x'] = $tableBin['\\x'] = '\\\\x'; + } + + if (is_bool($var)) { + echo ($var ? 'TRUE' : 'FALSE') . "\n"; + + } elseif ($var === NULL) { + echo "NULL\n"; + + } elseif (is_int($var)) { + echo "$var\n"; + + } elseif (is_float($var)) { + $var = (string) $var; + if (strpos($var, '.') === FALSE) $var .= '.0'; + echo "$var\n"; + + } elseif (is_string($var)) { + $s = strtr($var, preg_match($reBinary, $var) || preg_last_error() ? $tableBin : $tableUtf); + echo "\"$s\"\n"; + + } elseif (is_array($var)) { + echo "array("; + $space = str_repeat("\t", $level); + + static $marker; + if ($marker === NULL) $marker = uniqid("\x00", TRUE); + if (empty($var)) { + + } elseif (isset($var[$marker])) { + echo " *RECURSION* "; + + } elseif ($level < self::$maxDepth) { + echo "\n"; + $vector = range(0, count($var) - 1) === array_keys($var); + $var[$marker] = 0; + foreach ($var as $k => &$v) { + if ($k === $marker) continue; + if ($vector) { + echo "$space\t"; + } else { + $k = is_int($k) ? $k : '"' . strtr($k, preg_match($reBinary, $k) || preg_last_error() ? $tableBin : $tableUtf) . '"'; + echo "$space\t$k => "; + } + self::_dump($v, $level + 1); + } + unset($var[$marker]); + echo "$space"; + + } else { + echo " ... "; + } + echo ")\n"; + + } elseif ($var instanceof Exception) { + echo 'Exception ', get_class($var), ': ', ($var->getCode() ? '#' . $var->getCode() . ' ' : '') . $var->getMessage(), "\n"; + + } elseif (is_object($var)) { + $arr = (array) $var; + echo get_class($var) . "("; + $space = str_repeat("\t", $level); + + static $list = array(); + if (empty($arr)) { + + } elseif (in_array($var, $list, TRUE)) { + echo " *RECURSION* "; + + } elseif ($level < self::$maxDepth) { + echo "\n"; + $list[] = $var; + foreach ($arr as $k => &$v) { + $m = ''; + if ($k[0] === "\x00") { + $m = $k[1] === '*' ? ' protected' : ' private'; + $k = substr($k, strrpos($k, "\x00") + 1); + } + $k = strtr($k, preg_match($reBinary, $k) || preg_last_error() ? $tableBin : $tableUtf); + echo "$space\t\"$k\"$m => "; + echo self::_dump($v, $level + 1); + } + array_pop($list); + echo "$space"; + + } else { + echo " ... "; + } + echo ")\n"; + + } elseif (is_resource($var)) { + echo get_resource_type($var) . " resource\n"; + + } else { + echo "unknown type\n"; + } + } + + + + /** + * Custom exception handler. + * @param Exception + * @return void + */ + public static function exceptionHandler(Exception $exception) + { + echo 'Error: Uncaught '; + echo $exception; + } + + + + /** + * Coverage saving helper. + * @return void + */ + public static function prepareSaveCoverage() + { + register_shutdown_function(array(__CLASS__, 'saveCoverage')); + } + + + + /** + * Saves information about code coverage. + * @return void + */ + public static function saveCoverage() + { + $file = dirname(__FILE__) . '/coverage.tmp'; + $coverage = @unserialize(file_get_contents($file)); + $root = realpath(dirname(__FILE__) . '/../../Nette') . DIRECTORY_SEPARATOR; + + foreach (xdebug_get_code_coverage() as $filename => $lines) { + if (strncmp($root, $filename, strlen($root))) continue; + + foreach ($lines as $num => $val) { + if (empty($coverage[$filename][$num]) || $val > 0) { + $coverage[$filename][$num] = $val; // -1 => untested; -2 => dead code + } + } + } + + file_put_contents($file, serialize($coverage)); + } + + + + /** + * Skips this test. + * @return void + */ + public static function skip($message = 'No message.') + { + header('X-Nette-Test-Skip: '. $message); + exit; + } + +} diff --git a/tests/NetteTest/TestRunner.php b/tests/NetteTest/TestRunner.php new file mode 100644 index 00000000..ade71e09 --- /dev/null +++ b/tests/NetteTest/TestRunner.php @@ -0,0 +1,230 @@ +path)) { + $files = array($this->path); + } else { + $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->path)); + } + + foreach ($files as $entry) { + $entry = (string) $entry; + $info = pathinfo($entry); + if (!isset($info['extension']) || $info['extension'] !== 'phpt') { + continue; + } + + $count++; + $testCase = new TestCase($entry); + $testCase->setPhp($this->phpBinary, $this->phpArgs, $this->phpEnvironment); + + try { + $testCase->run(); + echo '.'; + $passed[] = array($testCase->getName(), $entry); + + } catch (TestCaseException $e) { + if ($e->getCode() === TestCaseException::SKIPPED) { + echo 's'; + $skipped[] = array($testCase->getName(), $entry, $e->getMessage()); + + } else { + echo 'F'; + $failed[] = array($testCase->getName(), $entry, $e->getMessage()); + + $this->log($entry, $testCase->getOutput(), self::OUTPUT); + $this->log($entry, $testCase->getExpectedOutput(), self::EXPECTED); + + if ($testCase->getExpectedHeaders() !== NULL) { + $this->log($entry, $testCase->getHeaders(), self::OUTPUT, self::HEADERS); + $this->log($entry, $testCase->getExpectedHeaders(), self::EXPECTED, self::HEADERS); + } + } + } + } + + $failedCount = count($failed); + $skippedCount = count($skipped); + + if ($this->displaySkipped && $skippedCount) { + echo "\n\nSkipped:\n"; + foreach ($skipped as $i => $item) { + list($name, $file, $message) = $item; + echo "\n", ($i + 1), ") $name\n $message\n $file\n"; + } + } + + if (!$count) { + echo "No tests found\n"; + + } elseif ($failedCount) { + echo "\n\nFailures:\n"; + foreach ($failed as $i => $item) { + list($name, $file, $message) = $item; + echo "\n", ($i + 1), ") $name\n $message\n $file\n"; + } + echo "\nFAILURES! ($count tests, $failedCount failures, $skippedCount skipped)\n"; + return FALSE; + + } else { + echo "\n\nOK ($count tests, $skippedCount skipped)\n"; + } + return TRUE; + } + + + + /** + * Returns output file for logging. + * @param string + * @param string + * @param string + * @param string + * @return void + */ + public function log($testFile, $content, $type, $section = '') + { + $file = dirname($testFile) . '/' . $type . '/' . basename($testFile, '.phpt') . ($section ? ".$section" : '') . '.raw'; + @mkdir(dirname($file)); // @ - directory may already exist + file_put_contents($file, $content); + } + + + + /** + * Parses configuration file. + * @return void + */ + public function parseConfigFile() + { + $configFile = dirname(__FILE__) . '/config.ini'; + if (file_exists($configFile)) { + $this->config = parse_ini_file($configFile, TRUE); + if ($this->config === FALSE) { + throw new Exception('Config file parsing failed.'); + } + foreach ($this->config as & $environment) { + $environment += array( + 'binary' => 'php-cgi', + 'args' => '', + 'environment' => '', + ); + // shorthand options + if (isset($environment['php.ini'])) { + $environment['args'] .= ' -c '. escapeshellarg($environment['php.ini']); + } + if (isset($environment['libraries'])) { + $environment['environment'] .= 'LD_LIBRARY_PATH='. escapeshellarg($environment['libraries']) .' '; + } + } + } + } + + + + /** + * Parses command line arguments. + * @return void + */ + public function parseArguments() + { + $this->phpBinary = 'php-cgi'; + $this->phpArgs = ''; + $this->phpEnvironment = ''; + $this->path = getcwd(); // current directory + + $args = new ArrayIterator(array_slice(isset($_SERVER['argv']) ? $_SERVER['argv'] : array(), 1)); + foreach ($args as $arg) { + if (!preg_match('#^[-/][a-z]$#', $arg)) { + if ($path = realpath($arg)) { + $this->path = $path; + } else { + throw new Exception("Invalid path '$arg'."); + } + + } else switch ($arg[1]) { + case 'p': + $args->next(); + $this->phpBinary = $args->current(); + break; + case 'c': + case 'd': + $args->next(); + $this->phpArgs .= " -$arg[1] " . escapeshellarg($args->current()); + break; + case 'l': + $args->next(); + $this->phpEnvironment .= 'LD_LIBRARY_PATH='. escapeshellarg($args->current()) . ' '; + break; + case 'e': + $args->next(); + $name = $args->current(); + if (!isset($this->config[$name])) { + throw new Exception("Unknown environment name '$name'."); + } + $this->phpBinary = $this->config[$name]['binary']; + $this->phpArgs = $this->config[$name]['args']; + $this->phpEnvironment = $this->config[$name]['environment']; + break; + case 's': + $this->displaySkipped = TRUE; + break; + default: + throw new Exception("Unknown option -$arg[1]."); + exit; + } + } + } + +} diff --git a/tests/config.ini b/tests/config.ini new file mode 100644 index 00000000..8245b567 --- /dev/null +++ b/tests/config.ini @@ -0,0 +1,67 @@ +[mysql] +driver = mysql +host = localhost +username = dibi +password = dibi +database = dibi_test +charset = utf8 + +[mysqli] +driver = mysqli +host = localhost +username = dibi +password = dibi +database = dibi_test +charset = utf8 + +[sqlite] +driver = sqlite +database = DIR "/data/sample.sdb" + +[sqlite3] +driver = sqlite3 +database = DIR "/data/sample.sdb3" + +[odbc] +driver = odbc +username = dibi +password = dibi +dsn = "Driver={Microsoft Access Driver (*.mdb)};Dbq=" DIR "/data/sample.mdb" + +[postgresql] +driver = postgre +host = localhost +port = 5432 +username = dibi +password = dibi +database = dibi_test +persistent = TRUE + +[sqlite-pdo] +driver = pdo +dsn = "sqlite2::" DIR "/data/sample.sdb" + +[mysql-pdo] +driver = pdo +dsn = "mysql:dbname=dibi_test;host=localhost" +username = dibi +password = dibi + +[mssql] +driver = mssql +host = localhost +username = dibi +password = dibi + +[mssql2005] +driver = mssql2005 +host = "(local)" +username = dibi +password = dibi +database = dibi_test + +[oracle] +driver = oracle +username = dibi +password = dibi +database = dibi_test diff --git a/tests/data/sample.mdb b/tests/data/sample.mdb new file mode 100644 index 0000000000000000000000000000000000000000..464a336ca5e34587eed8827a309646161caeb2ba GIT binary patch literal 286720 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 literal 0 HcmV?d00001 diff --git a/tests/initialize.php b/tests/initialize.php new file mode 100644 index 00000000..f75cd9ce --- /dev/null +++ b/tests/initialize.php @@ -0,0 +1,26 @@ +