diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -408,6 +408,7 @@ 'ArcanistXMLLinterTestCase' => 'lint/linter/__tests__/ArcanistXMLLinterTestCase.php', 'ArcanistXUnitTestResultParser' => 'unit/parser/ArcanistXUnitTestResultParser.php', 'CSharpToolsTestEngine' => 'unit/engine/CSharpToolsTestEngine.php', + 'MidjeUnitTestEngine' => 'unit/engine/MidjeUnitTestEngine.php', 'NoseTestEngine' => 'unit/engine/NoseTestEngine.php', 'PhpunitTestEngine' => 'unit/engine/PhpunitTestEngine.php', 'PhpunitTestEngineTestCase' => 'unit/engine/__tests__/PhpunitTestEngineTestCase.php', @@ -822,6 +823,7 @@ 'ArcanistXMLLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistXUnitTestResultParser' => 'Phobject', 'CSharpToolsTestEngine' => 'XUnitTestEngine', + 'MidjeUnitTestEngine' => 'ArcanistUnitTestEngine', 'NoseTestEngine' => 'ArcanistUnitTestEngine', 'PhpunitTestEngine' => 'ArcanistUnitTestEngine', 'PhpunitTestEngineTestCase' => 'PhutilTestCase', diff --git a/src/unit/engine/MidjeUnitTestEngine.php b/src/unit/engine/MidjeUnitTestEngine.php new file mode 100644 --- /dev/null +++ b/src/unit/engine/MidjeUnitTestEngine.php @@ -0,0 +1,210 @@ +getRunAllTests()) { + $run_tests = $this->getAllTests(); + } else { + $run_tests = $this->getTestsForPaths(); + } + + if (!$run_tests) { + throw new ArcanistNoEffectException(pht('No tests to run.')); + } + + $midje_config = new TempFile(); + Filesystem::writeFile($midje_config, +<<getError() > 125) { + throw $e; + } + } + + $results = array(); + foreach ($run_tests as $test) { + $xunit_report = 'target/surefire-reports/TEST-'.$test.'.xml'; + $cover_report = ''; + + $results[] = $this->parseTestResults( + $test, + $xunit_report, + $cover_report); + } + + return array_mergev($results); + } + + private function parseTestResults($test, $xunit_report, $cover_report) { + $xunit_results = Filesystem::readFile($xunit_report); + return id(new ArcanistXUnitTestResultParser()) + ->parseTestResults($xunit_results); + } + + private $clojureExtensions = array('clj', 'cljs', 'cljc'); + + private function isSupportedFileType($path) { + $extension = idx(pathinfo($path), 'extension'); + foreach ($this->clojureExtensions as $clojure_extension) { + if ($extension == $clojure_extension) { + return true; + } + } + + return false; + } + + /** + * For a given test file, figure out the namespace it contains. + * + * @param $path string Filename with extension + * @return string Namespace + */ + private function testPathToNamespace($path) { + $path_stub = $this->stripTestPathPrefix( + $this->stripFileExtension($path)); + $namespace = str_replace('_', '-', $path_stub); + $namespace = str_replace('/', '.', $namespace); + return $namespace; + } + + // FIXME This list should actually be retrieved from `:test-paths` in + // "project.clj" + private $projectTestPaths = array('test'); + + private $testPrefix = 'test_'; + private $testSuffix = ''; + + /** + * Remove test path prefix from a file name. + * + * Tries to remove all known `:test-paths` until one matches. + */ + private function stripTestPathPrefix($path) { + foreach ($this->projectTestPaths as $project_test_path) { + $prefix = $project_test_path.'/'; + $prefix_len = strlen($prefix); + if (substr($path, 0, $prefix_len) == $prefix) { + return substr($path, $prefix_len); + } + } + } + + /** + * Small convenience wrapper around `pathinfo()`. + */ + private function stripFileExtension($path) { + $path_info = pathinfo($path); + return $path_info['dirname'].'/'.$path_info['filename']; + } + + /** + * For a given path, figure out the corresponding test case's namespace. + * + * @param $path string Path of the file to be tested. + * @return string or null Namespace of the test case, if any. + */ + private function pathToTest($path) { + $path_info = pathinfo($path); + $base_test_path = $path_info['dirname'].'/'. + $this->testPrefix.$path_info['filename'].$this->testSuffix. + '.'.$path_info['extension']; + + while ($base_test_path) { + foreach ($this->projectTestPaths as $project_test_path) { + $test_path = $project_test_path.'/'.$base_test_path; + if (is_readable($test_path)) { + return $this->testPathToNamespace($test_path); + } + } + + // FIXME This could be simplified and sped up, if we had `:source-paths` + // from "project.clj". + $next_pos = strpos($base_test_path, DIRECTORY_SEPARATOR); + if (!$next_pos) { + return; + } + $base_test_path = substr($base_test_path, $next_pos + 1); + } + + return; + } + + /** + * Retrieve all test cases. + * + * @return list The names of the test case namespaces to be executed. + */ + private function getAllTests() { + $test_paths = array(); + foreach ($this->projectTestPaths as $project_test_path) { + foreach ($this->clojureExtensions as $extension) { + $test_paths = array_merge($test_paths, + glob($project_test_path.'/**/*.'.$extension)); + } + } + + $run_tests = array(); + foreach ($test_paths as $test_path) { + $test = $this->testPathToNamespace($test_path); + if ($test) { + $run_tests[] = $test; + } + } + + return $run_tests; + } + + /** + * Retrieve all relevant test cases. + * + * For every affected file, it looks for a corresponding test case namespace + * with a `-test` suffix. + * + * @return list The names of the test case namespaces to be executed. + */ + private function getTestsForPaths() { + $run_tests = array(); + foreach ($this->getPaths() as $path) { + if (!$this->isSupportedFileType($path)) { + continue; + } + + $test = $this->pathToTest($path); + if ($test) { + $run_tests[] = $test; + } + } + + return $run_tests; + } + +}