4
* Facilitates implementation of test cases for @{class:ArcanistLinter}s.
6
abstract class ArcanistLinterTestCase extends ArcanistPhutilTestCase {
8
public function executeTestsInDirectory($root, ArcanistLinter $linter) {
9
$files = id(new FileFinder($root))
11
->withSuffix('lint-test')
15
foreach ($files as $file) {
16
$this->lintFile($root.$file, $linter);
22
pht('Expected to find some .lint-test tests in directory %s!', $root));
25
private function lintFile($file, ArcanistLinter $linter) {
26
$linter = clone $linter;
28
$contents = Filesystem::readFile($file);
29
$contents = preg_split('/^~{4,}\n/m', $contents);
30
if (count($contents) < 2) {
32
"Expected '~~~~~~~~~~' separating test case and results.");
35
list ($data, $expect, $xform, $config) = array_merge(
39
$basename = basename($file);
42
$config = phutil_json_decode($config);
46
PhutilTypeSpec::checkMap(
49
'hook' => 'optional bool',
50
'config' => 'optional wild',
51
'path' => 'optional string',
52
'arcconfig' => 'optional map<string, string>',
58
$exception_message = false;
59
$caught_exception = false;
62
$tmp = new TempFile($basename);
63
Filesystem::writeFile($tmp, $data);
64
$full_path = (string)$tmp;
66
$dir = dirname($full_path);
67
$path = basename($full_path);
69
$arcconfig = idx($config, 'arcconfig');
71
$config_file = json_encode($arcconfig);
74
$working_copy = ArcanistWorkingCopyIdentity::newFromRootAndConfigFile(
78
$configuration_manager = new ArcanistConfigurationManager();
79
$configuration_manager->setWorkingCopyIdentity($working_copy);
82
$engine = new UnitTestableArcanistLintEngine();
83
$engine->setWorkingCopy($working_copy);
84
$engine->setConfigurationManager($configuration_manager);
85
$engine->setPaths(array($path));
87
$engine->setCommitHookMode(idx($config, 'hook', false));
89
$path_name = idx($config, 'path', $path);
90
$linter->addPath($path_name);
91
$linter->addData($path_name, $data);
92
$config = idx($config, 'config', array());
93
foreach ($config as $key => $value) {
94
$linter->setLinterConfigurationValue($key, $value);
97
$engine->addLinter($linter);
98
$engine->addFileData($path_name, $data);
100
$results = $engine->run();
105
'Expect one result returned by linter.');
107
$result = reset($results);
108
$patcher = ArcanistLintPatcher::newFromArcanistLintResult($result);
109
$after_lint = $patcher->getModifiedFileContent();
110
} catch (ArcanistPhutilTestTerminatedException $ex) {
112
} catch (Exception $exception) {
113
$caught_exception = true;
114
if ($exception instanceof PhutilAggregateException) {
115
$caught_exception = false;
116
foreach ($exception->getExceptions() as $ex) {
117
if ($ex instanceof ArcanistUsageException) {
118
$this->assertSkipped($ex->getMessage());
120
$caught_exception = true;
123
} else if ($exception instanceof ArcanistUsageException) {
124
$this->assertSkipped($exception->getMessage());
126
$exception_message = $exception->getMessage()."\n\n".
127
$exception->getTraceAsString();
130
$this->assertEqual(false, $caught_exception, $exception_message);
131
$this->compareLint($basename, $expect, $result);
132
$this->compareTransform($xform, $after_lint);
135
private function compareLint($file, $expect, ArcanistLintResult $result) {
138
$message_map = array();
140
foreach ($result->getMessages() as $message) {
141
$sev = $message->getSeverity();
142
$line = $message->getLine();
143
$char = $message->getChar();
144
$code = $message->getCode();
145
$name = $message->getName();
146
$message_key = $sev.':'.$line.':'.$char;
147
$message_map[$message_key] = $message;
148
$seen[] = $message_key;
149
$raised[] = " {$sev} at line {$line}, char {$char}: {$code} {$name}";
151
$expect = trim($expect);
153
$expect = explode("\n", $expect);
157
foreach ($expect as $key => $expected) {
158
$expect[$key] = head(explode(' ', $expected));
161
$expect = array_fill_keys($expect, true);
162
$seen = array_fill_keys($seen, true);
165
$raised = array('No messages.');
167
$raised = "Actually raised:\n".implode("\n", $raised);
169
foreach (array_diff_key($expect, $seen) as $missing => $ignored) {
170
list($sev, $line, $char) = explode(':', $missing);
171
$this->assertFailure(
173
"expected lint to raise {$sev} on line {$line} at char {$char}, ".
174
"but no {$sev} was raised. {$raised}");
177
foreach (array_diff_key($seen, $expect) as $surprising => $ignored) {
178
$message = $message_map[$surprising];
179
$message_info = $message->getDescription();
181
list($sev, $line, $char) = explode(':', $surprising);
182
$this->assertFailure(
184
"lint raised {$sev} on line {$line} at char {$char}, ".
185
"but nothing was expected:\n\n{$message_info}\n\n{$raised}");
189
private function compareTransform($expected, $actual) {
190
if (!strlen($expected)) {
196
'File as patched by lint did not match the expected patched file.');