public function testisDescendant() { $test_cases = array(array(__FILE__, dirname(__FILE__), true), array(dirname(__FILE__), dirname(dirname(__FILE__)), true), array(dirname(__FILE__), phutil_get_library_root_for_path(__FILE__), true), array(dirname(dirname(__FILE__)), dirname(__FILE__), false), array(dirname(__FILE__) . '/quack', dirname(__FILE__), false)); foreach ($test_cases as $test_case) { list($path, $root, $expected) = $test_case; $this->assertEqual($expected, Filesystem::isDescendant($path, $root), sprintf('Filesystem::isDescendant(%s, %s)', phutil_var_export($path), phutil_var_export($root))); } }
private function getTestsForPaths() { $project_root = $this->getWorkingCopy()->getProjectRoot(); $look_here = array(); foreach ($this->getPaths() as $path) { $library_root = phutil_get_library_root_for_path($path); if (!$library_root) { continue; } $library_name = phutil_get_library_name_for_root($library_root); if (!$library_name) { throw new Exception("Attempting to run unit tests on a libphutil library which has not " . "been loaded, at:\n\n" . " {$library_root}\n\n" . "This probably means one of two things:\n\n" . " - You may need to add this library to .arcconfig.\n" . " - You may be running tests on a copy of libphutil or arcanist\n" . " using a different copy of libphutil or arcanist. This\n" . " operation is not supported."); } $path = Filesystem::resolvePath($path, $project_root); if (!is_dir($path)) { $path = dirname($path); } if ($path == $library_root) { $look_here[$library_name . ':.'] = array('library' => $library_name, 'path' => ''); } else { if (!Filesystem::isDescendant($path, $library_root)) { // 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; } else { $library_path = Filesystem::readablePath($path, $library_root); do { $look_here[$library_name . ':' . $library_path] = array('library' => $library_name, 'path' => $library_path); $library_path = dirname($library_path); } while ($library_path != '.'); } } } // Look for any class that extends ArcanistPhutilTestCase 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. $run_tests = array(); foreach ($look_here as $path_info) { $library = $path_info['library']; $path = $path_info['path']; $symbols = id(new PhutilSymbolLoader())->setType('class')->setLibrary($library)->setPathPrefix(($path ? $path . '/' : '') . '__tests__/')->setAncestorClass('ArcanistPhutilTestCase')->setConcreteOnly(true)->selectAndLoadSymbols(); foreach ($symbols as $symbol) { $run_tests[$symbol['name']] = true; } } $run_tests = array_keys($run_tests); return $run_tests; }
public function run() { $bootloader = PhutilBootloader::getInstance(); $affected_modules = array(); foreach ($this->getPaths() as $path) { $library_root = phutil_get_library_root_for_path($path); if (!$library_root) { continue; } $library_name = phutil_get_library_name_for_root($library_root); if (!$library_name) { throw new Exception("Attempting to run unit tests on a libphutil library which has not " . "been loaded, at:\n\n" . " {$library_root}\n\n" . "This probably means one of two things:\n\n" . " - You may need to add this library to .arcconfig.\n" . " - You may be running tests on a copy of libphutil or arcanist\n" . " using a different copy of libphutil or arcanist. This\n" . " operation is not supported."); } $path = Filesystem::resolvePath($path); if (!is_dir($path)) { $path = dirname($path); } if ($path == $library_root) { continue; } $library_path = Filesystem::readablePath($path, $library_root); do { // Add the module and all parent modules as affected modules, which // means we'll look for __tests__ to run here and in any containing // module. $affected_modules[$library_name . ':' . $library_path] = array('name' => $library_name, 'root' => $library_root, 'path' => $library_path); $library_path = dirname($library_path); } while ($library_path != '.'); } $tests = array(); foreach ($affected_modules as $library_info) { $library_name = $library_info['name']; $library_root = $library_info['root']; $module = $library_info['path']; if (basename($module) == '__tests__') { // Okay, this is a __tests__ module. } else { $exists = $bootloader->moduleExists($library_name, $module . '/__tests__'); if ($exists) { // This is a module which has a __tests__ module in it. $module .= '/__tests__'; } else { // Look for a parent named __tests__. $rpos = strrpos($module, '/__tests__'); if ($rpos === false) { // No tests to run since there is no child or parent module named // __tests__. continue; } // Select the parent named __tests__. $module = substr($module, 0, $rpos + strlen('/__tests__')); } } $module_key = $library_name . ':' . $module; $tests[$module_key] = array('library' => $library_name, 'root' => $library_root, 'module' => $module); } if (!$tests) { throw new ArcanistNoEffectException("No tests to run."); } $run_tests = array(); foreach ($tests as $test) { $symbols = id(new PhutilSymbolLoader())->setType('class')->setLibrary($test['library'])->setModule($test['module'])->setAncestorClass('ArcanistPhutilTestCase')->selectAndLoadSymbols(); foreach ($symbols as $symbol) { $run_tests[$symbol['name']] = true; } } $run_tests = array_keys($run_tests); if (!$run_tests) { throw new ArcanistNoEffectException("No tests to run. You may need to rebuild the phutil library map."); } $enable_coverage = $this->getEnableCoverage(); if ($enable_coverage !== false) { if (!function_exists('xdebug_start_code_coverage')) { if ($enable_coverage === true) { throw new ArcanistUsageException("You specified --coverage but xdebug is not available, so " . "coverage can not be enabled for PhutilUnitTestEngine."); } } else { $enable_coverage = true; } } $results = array(); foreach ($run_tests as $test_class) { PhutilSymbolLoader::loadClass($test_class); $test_case = newv($test_class, array()); $test_case->setEnableCoverage($enable_coverage); $test_case->setProjectRoot($this->getWorkingCopy()->getProjectRoot()); $test_case->setPaths($this->getPaths()); $results[] = $test_case->run(); } if ($results) { $results = call_user_func_array('array_merge', $results); } return $results; }
/** * 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(); foreach ($this->getPaths() as $path) { $library_root = phutil_get_library_root_for_path($path); if (!$library_root) { continue; } $library_name = phutil_get_library_name_for_root($library_root); if (!$library_name) { throw new Exception(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", $library_root, '.arcconfig.')); } $path = Filesystem::resolvePath($path, $root); $library_path = Filesystem::readablePath($path, $library_root); if (!Filesystem::isDescendant($path, $library_root)) { // 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; } if (is_file($path) && preg_match('@(?:^|/)__tests__/@', $path)) { $paths[$library_name . ':' . $library_path] = array('library' => $library_name, 'path' => $library_path); continue; } foreach (Filesystem::walkToRoot($path, $library_root) as $subpath) { if ($subpath == $library_root) { $paths[$library_name . ':.'] = array('library' => $library_name, 'path' => '__tests__/'); } else { $library_subpath = Filesystem::readablePath($subpath, $library_root); $paths[$library_name . ':' . $library_subpath] = array('library' => $library_name, 'path' => $library_subpath . '/__tests__/'); } } } return $paths; }
public function run() { $bootloader = PhutilBootloader::getInstance(); $project_root = $this->getWorkingCopy()->getProjectRoot(); $look_here = array(); foreach ($this->getPaths() as $path) { $library_root = phutil_get_library_root_for_path($path); if (!$library_root) { continue; } $library_name = phutil_get_library_name_for_root($library_root); if (!$library_name) { throw new Exception("Attempting to run unit tests on a libphutil library which has not " . "been loaded, at:\n\n" . " {$library_root}\n\n" . "This probably means one of two things:\n\n" . " - You may need to add this library to .arcconfig.\n" . " - You may be running tests on a copy of libphutil or arcanist\n" . " using a different copy of libphutil or arcanist. This\n" . " operation is not supported."); } $path = Filesystem::resolvePath($path, $project_root); if (!is_dir($path)) { $path = dirname($path); } if ($path == $library_root) { continue; } if (!Filesystem::isDescendant($path, $library_root)) { // 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; } $library_path = Filesystem::readablePath($path, $library_root); do { $look_here[$library_name . ':' . $library_path] = array('library' => $library_name, 'path' => $library_path); $library_path = dirname($library_path); } while ($library_path != '.'); } // Look for any class that extends ArcanistPhutilTestCase 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. $run_tests = array(); foreach ($look_here as $path_info) { $library = $path_info['library']; $path = $path_info['path']; $symbols = id(new PhutilSymbolLoader())->setType('class')->setLibrary($library)->setPathPrefix($path . '/__tests__/')->setAncestorClass('ArcanistPhutilTestCase')->setConcreteOnly(true)->selectAndLoadSymbols(); foreach ($symbols as $symbol) { $run_tests[$symbol['name']] = true; } } $run_tests = array_keys($run_tests); if (!$run_tests) { throw new ArcanistNoEffectException("No tests to run."); } $enable_coverage = $this->getEnableCoverage(); if ($enable_coverage !== false) { if (!function_exists('xdebug_start_code_coverage')) { if ($enable_coverage === true) { throw new ArcanistUsageException("You specified --coverage but xdebug is not available, so " . "coverage can not be enabled for PhutilUnitTestEngine."); } } else { $enable_coverage = true; } } $results = array(); foreach ($run_tests as $test_class) { $test_case = newv($test_class, array()); $test_case->setEnableCoverage($enable_coverage); $test_case->setProjectRoot($project_root); $test_case->setPaths($this->getPaths()); $results[] = $test_case->run(); } if ($results) { $results = call_user_func_array('array_merge', $results); } return $results; }
public function willLintPaths(array $paths) { if ($paths) { if (!xhpast_is_available()) { throw new Exception(xhpast_get_build_instructions()); } } $modules = array(); $moduleinfo = array(); $project_root = $this->getEngine()->getWorkingCopy()->getProjectRoot(); foreach ($paths as $path) { $absolute_path = $project_root . '/' . $path; $library_root = phutil_get_library_root_for_path($absolute_path); if (!$library_root) { continue; } if ($this->isPhutilLibraryMetadata($path)) { continue; } $library_name = phutil_get_library_name_for_root($library_root); if (!is_dir($path)) { $path = dirname($path); } $path = Filesystem::resolvePath($path, $project_root); if ($path == $library_root) { continue; } $module_name = Filesystem::readablePath($path, $library_root); $module_key = $library_name . ':' . $module_name; if (empty($modules[$module_key])) { $modules[$module_key] = $module_key; $this->setModuleInfo($module_key, array('library' => $library_name, 'root' => $library_root, 'module' => $module_name)); } } if (!$modules) { return; } $modules = array_keys($modules); $arc_root = phutil_get_library_root('arcanist'); $bin = dirname($arc_root) . '/scripts/phutil_analyzer.php'; $futures = array(); foreach ($modules as $mkey => $key) { $disk_path = $this->getModulePathOnDisk($key); if (Filesystem::pathExists($disk_path)) { $futures[$key] = new ExecFuture('%s %s', $bin, $disk_path); } else { // This can occur in git when you add a module in HEAD and then remove // it in unstaged changes in the working copy. Just ignore it. unset($modules[$mkey]); } } $requirements = array(); foreach (Futures($futures)->limit(16) as $key => $future) { $requirements[$key] = $future->resolveJSON(); } $dependencies = array(); $futures = array(); foreach ($requirements as $key => $requirement) { foreach ($requirement['messages'] as $message) { list($where, $text, $code, $description) = $message; if ($where) { $where = array($where); } $this->raiseLintInModule($key, $code, $description, $where, $text); } foreach ($requirement['requires']['module'] as $req_module => $where) { if (isset($requirements[$req_module])) { $dependencies[$req_module] = $requirements[$req_module]; } else { list($library_name, $module_name) = explode(':', $req_module); $library_root = phutil_get_library_root($library_name); $this->setModuleInfo($req_module, array('library' => $library_name, 'root' => $library_root, 'module' => $module_name)); $disk_path = $this->getModulePathOnDisk($req_module); if (Filesystem::pathExists($disk_path)) { $futures[$req_module] = new ExecFuture('%s %s', $bin, $disk_path); } else { $dependencies[$req_module] = array(); } } } } foreach (Futures($futures)->limit(16) as $key => $future) { $dependencies[$key] = $future->resolveJSON(); } foreach ($requirements as $key => $spec) { $deps = array_intersect_key($dependencies, $spec['requires']['module']); $this->lintModule($key, $spec, $deps); } }
/** * Get the root directory for the library currently being tested. */ protected function getLibraryRoot() { $caller = id(new ReflectionClass($this))->getFileName(); return phutil_get_library_root_for_path($caller); }
function phutil_get_current_library_name() { $caller = head(debug_backtrace(false)); $root = phutil_get_library_root_for_path($caller['file']); return phutil_get_library_name_for_root($root); }