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 @@ -92,6 +92,7 @@ 'ArcanistDuplicateKeysInArrayXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDuplicateKeysInArrayXHPASTLinterRule.php', 'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDuplicateSwitchCaseXHPASTLinterRule.php', 'ArcanistDynamicDefineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php', + 'ArcanistElixirDogmaLinter' => 'lint/linter/ArcanistElixirDogmaLinter.php', 'ArcanistElseIfUsageXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php', 'ArcanistEmptyFileXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php', 'ArcanistEmptyStatementXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyStatementXHPASTLinterRule.php', @@ -289,6 +290,7 @@ 'ArcanistXMLLinterTestCase' => 'lint/linter/__tests__/ArcanistXMLLinterTestCase.php', 'ArcanistXUnitTestResultParser' => 'unit/parser/ArcanistXUnitTestResultParser.php', 'CSharpToolsTestEngine' => 'unit/engine/CSharpToolsTestEngine.php', + 'ExunitTestEngine' => 'unit/engine/ExunitTestEngine.php', 'NoseTestEngine' => 'unit/engine/NoseTestEngine.php', 'PhpunitTestEngine' => 'unit/engine/PhpunitTestEngine.php', 'PhpunitTestEngineTestCase' => 'unit/engine/__tests__/PhpunitTestEngineTestCase.php', @@ -387,6 +389,7 @@ 'ArcanistDuplicateKeysInArrayXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistDuplicateSwitchCaseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistDynamicDefineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', + 'ArcanistElixirDogmaLinter' => 'ArcanistExternalLinter', 'ArcanistElseIfUsageXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistEmptyFileXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistEmptyStatementXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', @@ -584,6 +587,7 @@ 'ArcanistXMLLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistXUnitTestResultParser' => 'Phobject', 'CSharpToolsTestEngine' => 'XUnitTestEngine', + 'ExunitTestEngine' => 'ArcanistUnitTestEngine', 'NoseTestEngine' => 'ArcanistUnitTestEngine', 'PhpunitTestEngine' => 'ArcanistUnitTestEngine', 'PhpunitTestEngineTestCase' => 'PhutilTestCase', diff --git a/src/lint/linter/ArcanistElixirDogmaLinter.php b/src/lint/linter/ArcanistElixirDogmaLinter.php new file mode 100644 --- /dev/null +++ b/src/lint/linter/ArcanistElixirDogmaLinter.php @@ -0,0 +1,118 @@ + array( + 'type' => 'optional string', + 'help' => pht( + 'Adjust the project root directory in which mix is executed.'. + 'This is useful in case the Elixir project\'s root'. + 'resides in a subdirectory of the repository.'), + ), + ); + + return $options + parent::getLinterConfigurationOptions(); + } + + public function setProjectRootDir($new_dir) { + $this->projectRootDir = $new_dir; + return $this; + } + + public function setLinterConfigurationValue($key, $value) { + switch ($key) { + case 'elixirdogma.project-root-dir': + $this->setProjectRootDir($value); + return; + } + + return parent::setLinterConfigurationValue($key, $value); + } + + public function getDefaultBinary() { + return 'mix'; + } + + protected function getMandatoryFlags() { + $flags = array(); + if ($this->projectRootDir) { + $flags[] = 'cmd'; + $flags[] = 'cd '.$this->projectRootDir; + $flags[] = '&&'; + $flags[] = 'mix'; + } + $flags[] = 'dogma --format=flycheck'; + return $flags; + } + + public function getInstallInstructions() { + return pht( + 'Install dogma by adding it as a dependency to your deps and + executing mix deps.get'); + } + + public function shouldExpectCommandErrors() { + return true; + } + + protected function canCustomizeLintSeverities() { + return false; + } + + protected function parseLinterOutput($path, $err, $stdout, $stderr) { + $lines = phutil_split_lines($stdout, false); + + $messages = array(); + foreach ($lines as $line) { + $matches = explode(':', $line, 5); + + if (count($matches) === 5) { + $message = new ArcanistLintMessage(); + $message->setPath($path); + $message->setLine($matches[1]); + $message->setChar($matches[2]); + $message->setCode($this->getLinterName()); + $message->setName($this->getLinterName()); + $message->setDescription(ucfirst(trim($matches[4]))); + $message->setSeverity($this->getSeverity(ucfirst(trim($matches[3])))); + + $messages[] = $message; + } + } + + return $messages; + } + + private function getSeverity($identifier) { + switch ($identifier) { + case 'W': + return ArcanistLintSeverity::SEVERITY_WARNING; + case 'E': + return ArcanistLintSeverity::SEVERITY_ERROR; + } + return ArcanistLintSeverity::SEVERITY_ADVICE; + } +} diff --git a/src/unit/engine/ExunitTestEngine.php b/src/unit/engine/ExunitTestEngine.php new file mode 100644 --- /dev/null +++ b/src/unit/engine/ExunitTestEngine.php @@ -0,0 +1,105 @@ +getWorkingCopy(); + $this->projectRoot = $working_copy->getProjectRoot(); + $projects = $this->findProjects($this->getPaths()); + $results = array(); + + foreach ($projects as $project => $paths) { + $junit_file = $project.$this->junitFilename; + $future = $this->buildTestFuture($project, $paths, $junit_file); + list($err, $stdout, $stderr) = $future->resolve(); + if (!Filesystem::pathExists($junit_file)) { + throw new CommandException( + pht('Command failed with error #%s!', $err), + $future->getCommand(), + $err, + $stdout, + $stderr); + } + $results = array_merge($results, $this->parseTestResults($junit_file)); + } + + return $results; + } + + private function buildTestFuture($project, $paths, $junit_file) { + $cmd_line = csprintf('cd %s && mix test %s --junit', + $project, implode(' ', $paths)); + + return new ExecFuture('%C', $cmd_line); + } + + public function parseTestResults($junit_file) { + $parser = new ArcanistXUnitTestResultParser(); + $results = $parser->parseTestResults( + Filesystem::readFile($junit_file)); + + return $results; + } + + public function shouldEchoTestResults() { + return !$this->renderer; + } + + private function findProjects($paths) { + $projects = array(); + foreach ($paths as $path) { + $path = realpath($path); + $orig_path = $path; + do { + foreach (glob($path.'/mix.exs') as $project_file) { + $project = dirname($project_file); + $projects_new = array($project => array($orig_path)); + $projects = array_merge_recursive($projects, $projects_new); + } + $path = dirname($path); + } while ($this->projectRoot != $path); + } + return $projects; + } + +}