private function lintFile($file, ArcanistLinter $linter) { $linter = clone $linter; $contents = Filesystem::readFile($file); $contents = preg_split('/^~{4,}\\n/m', $contents); if (count($contents) < 2) { throw new Exception(pht("Expected '%s' separating test case and results.", '~~~~~~~~~~')); } list($data, $expect, $xform, $config) = array_merge($contents, array(null, null)); $basename = basename($file); if ($config) { $config = phutil_json_decode($config); } else { $config = array(); } PhutilTypeSpec::checkMap($config, array('config' => 'optional map<string, wild>', 'path' => 'optional string', 'mode' => 'optional string', 'stopped' => 'optional bool')); $exception = null; $after_lint = null; $messages = null; $exception_message = false; $caught_exception = false; try { $tmp = new TempFile($basename); Filesystem::writeFile($tmp, $data); $full_path = (string) $tmp; $mode = idx($config, 'mode'); if ($mode) { Filesystem::changePermissions($tmp, octdec($mode)); } $dir = dirname($full_path); $path = basename($full_path); $working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile($dir, null, pht('Unit Test')); $configuration_manager = new ArcanistConfigurationManager(); $configuration_manager->setWorkingCopyIdentity($working_copy); $engine = new ArcanistUnitTestableLintEngine(); $engine->setWorkingCopy($working_copy); $engine->setConfigurationManager($configuration_manager); $path_name = idx($config, 'path', $path); $engine->setPaths(array($path_name)); $linter->addPath($path_name); $linter->addData($path_name, $data); foreach (idx($config, 'config', array()) as $key => $value) { $linter->setLinterConfigurationValue($key, $value); } $engine->addLinter($linter); $engine->addFileData($path_name, $data); $results = $engine->run(); $this->assertEqual(1, count($results), pht('Expect one result returned by linter.')); $assert_stopped = idx($config, 'stopped'); if ($assert_stopped !== null) { $this->assertEqual($assert_stopped, $linter->didStopAllLinters(), $assert_stopped ? pht('Expect linter to be stopped.') : pht('Expect linter to not be stopped.')); } $result = reset($results); $patcher = ArcanistLintPatcher::newFromArcanistLintResult($result); $after_lint = $patcher->getModifiedFileContent(); } catch (PhutilTestTerminatedException $ex) { throw $ex; } catch (Exception $exception) { $caught_exception = true; if ($exception instanceof PhutilAggregateException) { $caught_exception = false; foreach ($exception->getExceptions() as $ex) { if ($ex instanceof ArcanistUsageException || $ex instanceof ArcanistMissingLinterException) { $this->assertSkipped($ex->getMessage()); } else { $caught_exception = true; } } } else { if ($exception instanceof ArcanistUsageException || $exception instanceof ArcanistMissingLinterException) { $this->assertSkipped($exception->getMessage()); } } $exception_message = $exception->getMessage() . "\n\n" . $exception->getTraceAsString(); } $this->assertEqual(false, $caught_exception, $exception_message); $this->compareLint($basename, $expect, $result); $this->compareTransform($xform, $after_lint); }
public function run() { $svnargs = $this->getArgument('svnargs'); $repository = $svnargs[0]; $transaction = $svnargs[1]; list($commit_message) = execx('svnlook log --transaction %s %s', $transaction, $repository); if (strpos($commit_message, '@bypass-lint') !== false) { return 0; } // TODO: Do stuff with commit message. list($changed) = execx('svnlook changed --transaction %s %s', $transaction, $repository); $paths = array(); $changed = explode("\n", trim($changed)); foreach ($changed as $line) { $matches = null; preg_match('/^..\\s*(.*)$/', $line, $matches); $paths[$matches[1]] = strlen($matches[1]); } $resolved = array(); $failed = array(); $missing = array(); $found = array(); asort($paths); foreach ($paths as $path => $length) { foreach ($resolved as $rpath => $root) { if (!strncmp($path, $rpath, strlen($rpath))) { $resolved[$path] = $root; continue 2; } } $config = $path; if (basename($config) == '.arcconfig') { $resolved[$config] = $config; continue; } $config = rtrim($config, '/'); $last_config = $config; do { if (!empty($missing[$config])) { break; } else { if (!empty($found[$config])) { $resolved[$path] = $found[$config]; break; } } list($err) = exec_manual('svnlook cat --transaction %s %s %s', $transaction, $repository, $config ? $config . '/.arcconfig' : '.arcconfig'); if ($err) { $missing[$path] = true; } else { $resolved[$path] = $config ? $config . '/.arcconfig' : '.arcconfig'; $found[$config] = $resolved[$path]; break; } $config = dirname($config); if ($config == '.') { $config = ''; } if ($config == $last_config) { break; } $last_config = $config; } while (true); if (empty($resolved[$path])) { $failed[] = $path; } } if ($failed && $resolved) { $failed_paths = ' ' . implode("\n ", $failed); $resolved_paths = ' ' . implode("\n ", array_keys($resolved)); throw new ArcanistUsageException("This commit includes a mixture of files in Arcanist projects and " . "outside of Arcanist projects. A commit which affects an Arcanist " . "project must affect only that project.\n\n" . "Files in projects:\n\n" . $resolved_paths . "\n\n" . "Files not in projects:\n\n" . $failed_paths); } if (!$resolved) { // None of the affected paths are beneath a .arcconfig file. return 0; } $groups = array(); foreach ($resolved as $path => $project) { $groups[$project][] = $path; } if (count($groups) > 1) { $message = array(); foreach ($groups as $project => $group) { $message[] = "Files underneath '{$project}':\n\n"; $message[] = " " . implode("\n ", $group) . "\n\n"; } $message = implode('', $message); throw new ArcanistUsageException("This commit includes a mixture of files from different Arcanist " . "projects. A commit which affects an Arcanist project must affect " . "only that project.\n\n" . $message); } $config_file = key($groups); $project_root = dirname($config_file); $paths = reset($groups); list($config) = execx('svnlook cat --transaction %s %s %s', $transaction, $repository, $config_file); $working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile($project_root, $config, $config_file . " (svnlook: {$transaction} {$repository})"); $repository_api = new ArcanistSubversionHookAPI($project_root, $transaction, $repository); $lint_engine = $working_copy->getConfig('lint_engine'); if (!$lint_engine) { return 0; } $engine = newv($lint_engine, array()); $engine->setWorkingCopy($working_copy); $engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ERROR); $engine->setPaths($paths); $engine->setCommitHookMode(true); $engine->setHookAPI($repository_api); try { $results = $engine->run(); } catch (ArcanistNoEffectException $no_effect) { // Nothing to do, bail out. return 0; } $failures = array(); foreach ($results as $result) { if (!$result->getMessages()) { continue; } $failures[] = $result; } if ($failures) { $at = "@"; $msg = phutil_console_format("\n**LINT ERRORS**\n\n" . "This changeset has lint errors. You must fix all lint errors before " . "you can commit.\n\n" . "You can add '{$at}bypass-lint' to your commit message to disable " . "lint checks for this commit, or '{$at}nolint' to the file with " . "errors to disable lint for that file.\n\n"); echo phutil_console_wrap($msg); $renderer = new ArcanistLintConsoleRenderer(); foreach ($failures as $result) { echo $renderer->renderLintResult($result); } return 1; } return 0; }
private function liberateBuildLintEngine($path, array $changed) { $lint_map = array(); foreach ($changed as $module) { $module_path = $path . '/' . $module; $files = Filesystem::listDirectory($module_path); $lint_map[$module] = $files; } $working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile($path, json_encode(array('project_id' => '__arcliberate__')), 'arc liberate'); $engine = new ArcanistLiberateLintEngine(); $engine->setWorkingCopy($working_copy); $lint_paths = array(); foreach ($lint_map as $module => $files) { foreach ($files as $file) { $lint_paths[] = $module . '/' . $file; } } if (!$lint_paths) { return null; } $engine->setPaths($lint_paths); $engine->setMinimumSeverity(ArcanistLintSeverity::SEVERITY_ERROR); return $engine; }