2011-01-10 00:22:25 +01:00
|
|
|
<?php
|
|
|
|
|
2011-02-19 20:36:08 +01:00
|
|
|
/**
|
|
|
|
* Very basic unit test engine which runs libphutil tests.
|
|
|
|
*/
|
2014-07-21 23:49:15 +02:00
|
|
|
final class PhutilUnitTestEngine extends ArcanistUnitTestEngine {
|
2011-01-10 00:22:25 +01:00
|
|
|
|
2012-12-17 21:56:41 +01:00
|
|
|
protected function supportsRunAllTests() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-01-10 00:22:25 +01:00
|
|
|
public function run() {
|
2012-12-17 21:56:41 +01:00
|
|
|
if ($this->getRunAllTests()) {
|
|
|
|
$run_tests = $this->getAllTests();
|
|
|
|
} else {
|
|
|
|
$run_tests = $this->getTestsForPaths();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$run_tests) {
|
2015-05-22 09:09:55 +02:00
|
|
|
throw new ArcanistNoEffectException(pht('No tests to run.'));
|
2012-12-17 21:56:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$enable_coverage = $this->getEnableCoverage();
|
2015-05-28 10:25:56 +02:00
|
|
|
|
2012-12-17 21:56:41 +01:00
|
|
|
if ($enable_coverage !== false) {
|
|
|
|
if (!function_exists('xdebug_start_code_coverage')) {
|
|
|
|
if ($enable_coverage === true) {
|
|
|
|
throw new ArcanistUsageException(
|
2015-05-13 10:05:15 +02:00
|
|
|
pht(
|
2015-05-28 10:25:56 +02:00
|
|
|
'You specified %s but %s is not available, so '.
|
2015-05-13 10:05:15 +02:00
|
|
|
'coverage can not be enabled for %s.',
|
|
|
|
'--coverage',
|
2015-05-28 10:25:56 +02:00
|
|
|
'XDebug',
|
2015-05-13 10:05:15 +02:00
|
|
|
__CLASS__));
|
2012-12-17 21:56:41 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
$enable_coverage = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-06 22:40:07 +01:00
|
|
|
$test_cases = array();
|
2015-05-28 10:25:56 +02:00
|
|
|
|
2012-12-17 21:56:41 +01:00
|
|
|
foreach ($run_tests as $test_class) {
|
2015-05-28 10:25:56 +02:00
|
|
|
$test_case = newv($test_class, array())
|
|
|
|
->setEnableCoverage($enable_coverage)
|
|
|
|
->setWorkingCopy($this->getWorkingCopy());
|
2015-05-20 01:36:22 +02:00
|
|
|
|
2012-12-17 21:56:41 +01:00
|
|
|
if ($this->getPaths()) {
|
|
|
|
$test_case->setPaths($this->getPaths());
|
|
|
|
}
|
2015-05-28 10:25:56 +02:00
|
|
|
|
2013-02-27 22:59:12 +01:00
|
|
|
if ($this->renderer) {
|
|
|
|
$test_case->setRenderer($this->renderer);
|
|
|
|
}
|
2015-05-28 10:25:56 +02:00
|
|
|
|
2013-03-06 22:40:07 +01:00
|
|
|
$test_cases[] = $test_case;
|
2012-12-17 21:56:41 +01:00
|
|
|
}
|
|
|
|
|
2013-03-06 22:40:07 +01:00
|
|
|
foreach ($test_cases as $test_case) {
|
|
|
|
$test_case->willRunTestCases($test_cases);
|
|
|
|
}
|
|
|
|
|
|
|
|
$results = array();
|
|
|
|
foreach ($test_cases as $test_case) {
|
|
|
|
$results[] = $test_case->run();
|
|
|
|
}
|
2012-12-17 21:56:41 +01:00
|
|
|
$results = array_mergev($results);
|
2013-03-06 22:40:07 +01:00
|
|
|
|
|
|
|
foreach ($test_cases as $test_case) {
|
|
|
|
$test_case->didRunTestCases($test_cases);
|
|
|
|
}
|
|
|
|
|
2012-12-17 21:56:41 +01:00
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
|
|
|
private function getAllTests() {
|
|
|
|
$project_root = $this->getWorkingCopy()->getProjectRoot();
|
|
|
|
|
|
|
|
$symbols = id(new PhutilSymbolLoader())
|
|
|
|
->setType('class')
|
2015-05-20 01:37:44 +02:00
|
|
|
->setAncestorClass('PhutilTestCase')
|
2012-12-17 21:56:41 +01:00
|
|
|
->setConcreteOnly(true)
|
|
|
|
->selectSymbolsWithoutLoading();
|
|
|
|
|
|
|
|
$in_working_copy = array();
|
2011-01-10 00:22:25 +01:00
|
|
|
|
2012-12-17 21:56:41 +01:00
|
|
|
$run_tests = array();
|
|
|
|
foreach ($symbols as $symbol) {
|
2014-01-02 15:24:15 +01:00
|
|
|
if (!preg_match('@(?:^|/)__tests__/@', $symbol['where'])) {
|
2012-12-17 21:56:41 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
$library = $symbol['library'];
|
|
|
|
|
|
|
|
if (!isset($in_working_copy[$library])) {
|
|
|
|
$library_root = phutil_get_library_root($library);
|
|
|
|
$in_working_copy[$library] = Filesystem::isDescendant(
|
|
|
|
$library_root,
|
|
|
|
$project_root);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($in_working_copy[$library]) {
|
|
|
|
$run_tests[] = $symbol['name'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $run_tests;
|
|
|
|
}
|
|
|
|
|
2015-05-28 10:25:56 +02:00
|
|
|
/**
|
|
|
|
* Retrieve all relevant test cases.
|
|
|
|
*
|
|
|
|
* Looks for any class that extends @{class:PhutilTestCase} inside a
|
|
|
|
* `__tests__` directory in any parent directory of every affected file.
|
|
|
|
*
|
|
|
|
* The idea is that "infrastructure/__tests__/" tests defines general tests
|
|
|
|
* for all of "infrastructure/", and those tests run for any change in
|
|
|
|
* "infrastructure/". However, "infrastructure/concrete/rebar/__tests__/"
|
|
|
|
* defines more specific tests that run only when "rebar/" (or some
|
|
|
|
* subdirectory) changes.
|
|
|
|
*
|
|
|
|
* @return list<string> The names of the test case classes to be executed.
|
|
|
|
*/
|
2012-12-17 21:56:41 +01:00
|
|
|
private function getTestsForPaths() {
|
2015-05-28 10:25:56 +02:00
|
|
|
$look_here = $this->getTestPaths();
|
|
|
|
$run_tests = array();
|
2011-01-13 00:45:17 +01:00
|
|
|
|
2015-05-28 10:25:56 +02:00
|
|
|
foreach ($look_here as $path_info) {
|
|
|
|
$library = $path_info['library'];
|
|
|
|
$path = $path_info['path'];
|
|
|
|
|
|
|
|
$symbols = id(new PhutilSymbolLoader())
|
|
|
|
->setType('class')
|
|
|
|
->setLibrary($library)
|
|
|
|
->setPathPrefix($path)
|
|
|
|
->setAncestorClass('PhutilTestCase')
|
|
|
|
->setConcreteOnly(true)
|
|
|
|
->selectAndLoadSymbols();
|
|
|
|
|
|
|
|
foreach ($symbols as $symbol) {
|
|
|
|
$run_tests[$symbol['name']] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return array_keys($run_tests);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the paths in which we should look for tests to execute.
|
|
|
|
*
|
|
|
|
* @return list<string> A list of paths in which to search for test cases.
|
|
|
|
*/
|
|
|
|
public function getTestPaths() {
|
|
|
|
$root = $this->getWorkingCopy()->getProjectRoot();
|
|
|
|
$paths = array();
|
2012-05-31 19:40:35 +02:00
|
|
|
|
2011-01-10 00:22:25 +01:00
|
|
|
foreach ($this->getPaths() as $path) {
|
|
|
|
$library_root = phutil_get_library_root_for_path($path);
|
2015-05-28 10:25:56 +02:00
|
|
|
|
2011-01-10 00:22:25 +01:00
|
|
|
if (!$library_root) {
|
|
|
|
continue;
|
|
|
|
}
|
2015-05-28 10:25:56 +02:00
|
|
|
|
2011-01-10 00:22:25 +01:00
|
|
|
$library_name = phutil_get_library_name_for_root($library_root);
|
|
|
|
|
2012-03-05 19:03:13 +01:00
|
|
|
if (!$library_name) {
|
|
|
|
throw new Exception(
|
2015-05-28 10:25:56 +02:00
|
|
|
pht(
|
|
|
|
"Attempting to run unit tests on a libphutil library which has ".
|
|
|
|
"not been loaded, at:\n\n".
|
|
|
|
" %s\n\n".
|
|
|
|
"This probably means one of two things:\n\n".
|
|
|
|
" - You may need to add this library to %s.\n".
|
|
|
|
" - You may be running tests on a copy of libphutil or ".
|
|
|
|
"arcanist using a different copy of libphutil or arcanist. ".
|
|
|
|
"This operation is not supported.\n",
|
2015-05-13 10:05:15 +02:00
|
|
|
$library_root,
|
2015-05-28 10:25:56 +02:00
|
|
|
'.arcconfig.'));
|
2012-03-05 19:03:13 +01:00
|
|
|
}
|
|
|
|
|
2015-05-28 10:25:56 +02:00
|
|
|
$path = Filesystem::resolvePath($path, $root);
|
|
|
|
$library_path = Filesystem::readablePath($path, $library_root);
|
2011-01-10 00:22:25 +01:00
|
|
|
|
2015-05-28 10:25:56 +02:00
|
|
|
if (!Filesystem::isDescendant($path, $library_root)) {
|
2012-04-26 01:13:04 +02:00
|
|
|
// We have encountered some kind of symlink maze -- for instance, $path
|
|
|
|
// is some symlink living outside the library that links into some file
|
|
|
|
// inside the library. Just ignore these cases, since the affected file
|
|
|
|
// does not actually lie within the library.
|
|
|
|
continue;
|
|
|
|
}
|
2011-11-18 20:27:55 +01:00
|
|
|
|
2015-05-28 10:25:56 +02:00
|
|
|
if (is_file($path) && preg_match('@(?:^|/)__tests__/@', $path)) {
|
|
|
|
$paths[$library_name.':'.$library_path] = array(
|
|
|
|
'library' => $library_name,
|
|
|
|
'path' => $library_path,
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
2012-05-31 19:40:35 +02:00
|
|
|
|
2015-05-28 10:25:56 +02:00
|
|
|
while (($library_path = dirname($library_path)) != '.') {
|
|
|
|
$paths[$library_name.':'.$library_path] = array(
|
|
|
|
'library' => $library_name,
|
|
|
|
'path' => $library_path.'/__tests__/',
|
|
|
|
);
|
2011-01-13 00:45:17 +01:00
|
|
|
}
|
2011-01-10 00:22:25 +01:00
|
|
|
}
|
|
|
|
|
2015-05-28 10:25:56 +02:00
|
|
|
return $paths;
|
2011-01-10 00:22:25 +01:00
|
|
|
}
|
|
|
|
|
2013-02-27 22:59:12 +01:00
|
|
|
public function shouldEchoTestResults() {
|
|
|
|
return !$this->renderer;
|
|
|
|
}
|
|
|
|
|
2011-01-10 00:22:25 +01:00
|
|
|
}
|