Page MenuHomePhabricator

D14430.id35031.diff
No OneTemporary

D14430.id35031.diff

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 @@
+<?php
+
+final class ArcanistElixirDogmaLinter extends ArcanistExternalLinter {
+
+ private $projectRootDir = null;
+
+ public function getInfoName() {
+ return 'ElixirDogma';
+ }
+
+ public function getInfoURI() {
+ return 'https://github.com/lpil/dogma';
+ }
+
+ public function getInfoDescription() {
+ return pht('A code style linter for Elixir, powered by shame.');
+ }
+
+ public function getLinterName() {
+ return 'ELIXIRDOGMA';
+ }
+
+ public function getLinterConfigurationName() {
+ return 'elixirdogma';
+ }
+ public function getLinterConfigurationOptions() {
+ $options = array(
+ 'elixirdogma.project-root-dir' => 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 @@
+<?php
+
+/**
+ * Very basic `mix test` unit test engine wrapper.
+ *
+ * It requires the use of the junit_formatter to produce the xml report.
+ * Refer to https://hex.pm/packages/junit_formatter
+ *
+ * Cover reporting is not supported yet.
+ *
+ * When using mix configurations you likely want to execute the
+ * unit tests with the correct environment, e.g. `MIX_ENV=test arc unit`
+ *
+ * Since 'mix test' requires to be run with a Mix project directory, the engine
+ * scans all included paths backwards until it either finds a Mix project
+ * directory or the arc project root directory. Therefore, a useful `.arcunit`
+ * entry might look like this:
+ *
+ * {
+ * "engines": {
+ * "exunit": {
+ * "type": "exunit",
+ * "include": [
+ * "(^somemixproject/.+_test\\.ex[s]?$)"
+ * ]
+ * }
+ * }
+ * }
+ *
+ */
+final class ExunitTestEngine extends ArcanistUnitTestEngine {
+
+ private $projectRoot;
+ // this is the standard report location when using mix test
+ private $junitFilename = '/_build/test/test-junit-report.xml';
+
+ public function getEngineConfigurationName() {
+ return 'exunit';
+ }
+
+ protected function supportsRunAllTests() {
+ return true;
+ }
+
+ public function run() {
+ $working_copy = $this->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;
+ }
+
+}

File Metadata

Mime Type
text/plain
Expires
Thu, May 30, 8:31 AM (3 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6297440
Default Alt Text
D14430.id35031.diff (8 KB)

Event Timeline