diff --git a/src/lint/ArcanistLintMessage.php b/src/lint/ArcanistLintMessage.php index 29f9ab92..bd60addc 100644 --- a/src/lint/ArcanistLintMessage.php +++ b/src/lint/ArcanistLintMessage.php @@ -1,276 +1,291 @@ setPath($dict['path']); $message->setLine($dict['line']); $message->setChar($dict['char']); $message->setCode($dict['code']); $message->setSeverity($dict['severity']); $message->setName($dict['name']); $message->setDescription($dict['description']); if (isset($dict['original'])) { $message->setOriginalText($dict['original']); } if (isset($dict['replacement'])) { $message->setReplacementText($dict['replacement']); } $message->setGranularity(idx($dict, 'granularity')); $message->setOtherLocations(idx($dict, 'locations', array())); if (isset($dict['bypassChangedLineFiltering'])) { $message->bypassChangedLineFiltering($dict['bypassChangedLineFiltering']); } return $message; } public function toDictionary() { return array( 'path' => $this->getPath(), 'line' => $this->getLine(), 'char' => $this->getChar(), 'code' => $this->getCode(), 'severity' => $this->getSeverity(), 'name' => $this->getName(), 'description' => $this->getDescription(), 'original' => $this->getOriginalText(), 'replacement' => $this->getReplacementText(), 'granularity' => $this->getGranularity(), 'locations' => $this->getOtherLocations(), 'bypassChangedLineFiltering' => $this->shouldBypassChangedLineFiltering(), ); } public function setPath($path) { $this->path = $path; return $this; } public function getPath() { return $this->path; } public function setLine($line) { $this->line = $this->validateInteger($line, 'setLine'); return $this; } public function getLine() { return $this->line; } public function setChar($char) { $this->char = $this->validateInteger($char, 'setChar'); return $this; } public function getChar() { return $this->char; } public function setCode($code) { $code = (string)$code; $maximum_bytes = 128; $actual_bytes = strlen($code); if ($actual_bytes > $maximum_bytes) { throw new Exception( pht( 'Parameter ("%s") passed to "%s" when constructing a lint message '. 'must be a scalar with a maximum string length of %s bytes, but is '. '%s bytes in length.', $code, 'setCode()', new PhutilNumber($maximum_bytes), new PhutilNumber($actual_bytes))); } $this->code = $code; return $this; } public function getCode() { return $this->code; } public function setSeverity($severity) { $this->severity = $severity; return $this; } public function getSeverity() { return $this->severity; } public function setName($name) { + $maximum_bytes = 255; + $actual_bytes = strlen($name); + + if ($actual_bytes > $maximum_bytes) { + throw new Exception( + pht( + 'Parameter ("%s") passed to "%s" when constructing a lint message '. + 'must be a string with a maximum length of %s bytes, but is %s '. + 'bytes in length.', + $name, + 'setName()', + new PhutilNumber($maximum_bytes), + new PhutilNumber($actual_bytes))); + } + $this->name = $name; return $this; } public function getName() { return $this->name; } public function setDescription($description) { $this->description = $description; return $this; } public function getDescription() { return $this->description; } public function setOriginalText($original) { $this->originalText = $original; return $this; } public function getOriginalText() { return $this->originalText; } public function setReplacementText($replacement) { $this->replacementText = $replacement; return $this; } public function getReplacementText() { return $this->replacementText; } /** * @param dict Keys 'path', 'line', 'char', 'original'. */ public function setOtherLocations(array $locations) { assert_instances_of($locations, 'array'); $this->otherLocations = $locations; return $this; } public function getOtherLocations() { return $this->otherLocations; } public function isError() { return $this->getSeverity() == ArcanistLintSeverity::SEVERITY_ERROR; } public function isWarning() { return $this->getSeverity() == ArcanistLintSeverity::SEVERITY_WARNING; } public function isAutofix() { return $this->getSeverity() == ArcanistLintSeverity::SEVERITY_AUTOFIX; } public function hasFileContext() { return ($this->getLine() !== null); } public function setObsolete($obsolete) { $this->obsolete = $obsolete; return $this; } public function getObsolete() { return $this->obsolete; } public function isPatchable() { return ($this->getReplacementText() !== null) && ($this->getReplacementText() !== $this->getOriginalText()); } public function didApplyPatch() { if ($this->appliedToDisk) { return $this; } $this->appliedToDisk = true; foreach ($this->dependentMessages as $message) { $message->didApplyPatch(); } return $this; } public function isPatchApplied() { return $this->appliedToDisk; } public function setGranularity($granularity) { $this->granularity = $granularity; return $this; } public function getGranularity() { return $this->granularity; } public function setDependentMessages(array $messages) { assert_instances_of($messages, __CLASS__); $this->dependentMessages = $messages; return $this; } public function setBypassChangedLineFiltering($bypass_changed_lines) { $this->bypassChangedLineFiltering = $bypass_changed_lines; return $this; } public function shouldBypassChangedLineFiltering() { return $this->bypassChangedLineFiltering; } /** * Validate an integer-like value, returning a strict integer. * * Further on, the pipeline is strict about types. We want to be a little * less strict in linters themselves, since they often parse command line * output or XML and will end up with string representations of numbers. * * @param mixed Integer or digit string. * @return int Integer. */ private function validateInteger($value, $caller) { if ($value === null) { // This just means that we don't have any information. return null; } // Strings like "234" are fine, coerce them to integers. if (is_string($value) && preg_match('/^\d+\z/', $value)) { $value = (int)$value; } if (!is_int($value)) { throw new Exception( pht( 'Parameter passed to "%s" must be an integer.', $caller.'()')); } return $value; } } diff --git a/src/unit/ArcanistUnitTestResult.php b/src/unit/ArcanistUnitTestResult.php index a9061bd0..ec25c20f 100644 --- a/src/unit/ArcanistUnitTestResult.php +++ b/src/unit/ArcanistUnitTestResult.php @@ -1,227 +1,241 @@ namespace = $namespace; return $this; } public function getNamespace() { return $this->namespace; } public function setName($name) { + $maximum_bytes = 255; + $actual_bytes = strlen($name); + + if ($actual_bytes > $maximum_bytes) { + throw new Exception( + pht( + 'Parameter ("%s") passed to "%s" when constructing a unit test '. + 'message must be a string with a maximum length of %s bytes, but '. + 'is %s bytes in length.', + $name, + 'setName()', + new PhutilNumber($maximum_bytes), + new PhutilNumber($actual_bytes))); + } $this->name = $name; return $this; } public function getName() { return $this->name; } public function setLink($link) { $this->link = $link; return $this; } public function getLink() { return $this->link; } public function setResult($result) { $this->result = $result; return $this; } public function getResult() { return $this->result; } /** * Set the number of seconds spent executing this test. * * Reporting this information can help users identify slow tests and reduce * the total cost of running a test suite. * * Callers should pass an integer or a float. For example, pass `3` for * 3 seconds, or `0.125` for 125 milliseconds. * * @param int|float Duration, in seconds. * @return this */ public function setDuration($duration) { if (!is_int($duration) && !is_float($duration)) { throw new Exception( pht( 'Parameter passed to setDuration() must be an integer or a float.')); } $this->duration = $duration; return $this; } public function getDuration() { return $this->duration; } public function setUserData($user_data) { $this->userData = $user_data; return $this; } public function getUserData() { return $this->userData; } /** * "extra data" allows an implementation to store additional key/value * metadata along with the result of the test run. */ public function setExtraData(array $extra_data = null) { $this->extraData = $extra_data; return $this; } public function getExtraData() { return $this->extraData; } public function setCoverage($coverage) { $this->coverage = $coverage; return $this; } public function getCoverage() { return $this->coverage; } /** * Merge several coverage reports into a comprehensive coverage report. * * @param list List of coverage report strings. * @return string Cumulative coverage report. */ public static function mergeCoverage(array $coverage) { if (empty($coverage)) { return null; } $base = reset($coverage); foreach ($coverage as $more_coverage) { $base_len = strlen($base); $more_len = strlen($more_coverage); $len = min($base_len, $more_len); for ($ii = 0; $ii < $len; $ii++) { if ($more_coverage[$ii] == 'C') { $base[$ii] = 'C'; } } // If a secondary report has more data, copy all of it over. if ($more_len > $base_len) { $base .= substr($more_coverage, $base_len); } } return $base; } public function toDictionary() { return array( 'namespace' => $this->getNamespace(), 'name' => $this->getName(), 'link' => $this->getLink(), 'result' => $this->getResult(), 'duration' => $this->getDuration(), 'extra' => $this->getExtraData(), 'userData' => $this->getUserData(), 'coverage' => $this->getCoverage(), ); } public static function getAllResultCodes() { return array( self::RESULT_PASS, self::RESULT_FAIL, self::RESULT_SKIP, self::RESULT_BROKEN, self::RESULT_UNSOUND, ); } public static function getResultCodeName($result_code) { $spec = self::getResultCodeSpec($result_code); if (!$spec) { return null; } return idx($spec, 'name'); } public static function getResultCodeDescription($result_code) { $spec = self::getResultCodeSpec($result_code); if (!$spec) { return null; } return idx($spec, 'description'); } private static function getResultCodeSpec($result_code) { $specs = self::getResultCodeSpecs(); return idx($specs, $result_code); } private static function getResultCodeSpecs() { return array( self::RESULT_PASS => array( 'name' => pht('Pass'), 'description' => pht( 'The test passed.'), ), self::RESULT_FAIL => array( 'name' => pht('Fail'), 'description' => pht( 'The test failed.'), ), self::RESULT_SKIP => array( 'name' => pht('Skip'), 'description' => pht( 'The test was not executed.'), ), self::RESULT_BROKEN => array( 'name' => pht('Broken'), 'description' => pht( 'The test failed in an abnormal or severe way. For example, the '. 'harness crashed instead of reporting a failure.'), ), self::RESULT_UNSOUND => array( 'name' => pht('Unsound'), 'description' => pht( 'The test failed, but this change is probably not what broke it. '. 'For example, it might have already been failing.'), ), ); } }