private function parseOutput($output) { $results = array(); $json = json_decode($output); foreach ($json as $feature) { if (property_exists($feature, 'elements')) { foreach ($feature->elements as $element) { $passed = true; $result = new ArcanistUnitTestResult(); $result->setName($feature->description); foreach ($element->steps as $step) { switch ($step->result->status) { case 'passed': break; case 'failed': $passed = false; $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); $result->setUserData($step->result->error_message); break; } } if ($passed) { $result->setResult(ArcanistUnitTestResult::RESULT_PASS); } $results[] = $result; } } } return $results; }
private function parseOutput($output) { $results = array(); $lines = explode(PHP_EOL, $output); foreach ($lines as $index => $line) { preg_match('/^(not ok|ok)\\s+\\d+\\s+-?(.*)/', $line, $matches); if (count($matches) < 3) { continue; } $result = new ArcanistUnitTestResult(); $result->setName(trim($matches[2])); switch (trim($matches[1])) { case 'ok': $result->setResult(ArcanistUnitTestResult::RESULT_PASS); break; case 'not ok': $exception_message = trim($lines[$index + 1]); $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); $result->setUserData($exception_message); break; default: continue; } $results[] = $result; } return $results; }
private function runTests() { $root = $this->getWorkingCopy()->getProjectRoot(); $script = $this->getConfiguredScript(); $path = $this->getConfiguredTestResultPath(); foreach (glob($root . DIRECTORY_SEPARATOR . $path . "/*.xml") as $filename) { // Remove existing files so we cannot report old results $this->unlink($filename); } // Provide changed paths to process putenv("ARCANIST_DIFF_PATHS=" . implode(PATH_SEPARATOR, $this->getPaths())); $future = new ExecFuture('%C %s', $script, $path); $future->setCWD($root); $err = null; try { $future->resolvex(); } catch (CommandException $exc) { $err = $exc; } $results = $this->parseTestResults($root . DIRECTORY_SEPARATOR . $path); if ($err) { $result = new ArcanistUnitTestResult(); $result->setName('Unit Test Script'); $result->setResult(ArcanistUnitTestResult::RESULT_BROKEN); $result->setUserData("ERROR: Command failed with code {$err->getError()}\nCOMMAND: `{$err->getCommand()}`"); $results[] = $result; } return $results; }
private function checkNonEmptyTestPlan() { $result = new ArcanistUnitTestResult(); $result->setName("Test Plan"); $lines = join(' ', $this->getMessage()); $testPlanExists = preg_match('/\\sTest Plan:/', $lines); if (!$testPlanExists) { $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); print 'Test Plan not found!'; return array($result); } $platforms = $this->getPlatformTestsFromLines($this->getMessage()); if ($platforms) { print "Found tests for the following platforms:\n"; foreach ($platforms as $platform => $tests) { print "{$platform}:\n "; print join("\n ", $tests); print "\n"; } $result->setResult(ArcanistUnitTestResult::RESULT_PASS); print 'Test Plan found!'; return array($result); } $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); print "No tests found to run on CI! Check your repo's README for instructions\n"; return array($result); }
private function runCommand($command) { exec($command, $output, $return_code); $result = new ArcanistUnitTestResult(); $result->setName($command); $result->setResult($return_code == 0 ? ArcanistUnitTestResult::RESULT_PASS : ArcanistUnitTestResult::RESULT_FAIL); return array($result); }
public function run() { // For a call to `arc call-conduit differential.updateunitresults` to // succeed we need at least one entry here. $result = new ArcanistUnitTestResult(); $result->setName("dummy_placeholder_entry"); $result->setResult(ArcanistUnitTestResult::RESULT_PASS); return array($result); }
public function run() { $working_copy = $this->getWorkingCopy(); $this->project_root = $working_copy->getProjectRoot(); // We only want to report results for tests that actually ran, so // we'll compare the test result files' timestamps to the start time // of the test run. This will probably break if multiple test runs // are happening in parallel, but if that's happening then we can't // count on the results files being intact anyway. $start_time = time(); $maven_top_dirs = $this->findTopLevelMavenDirectories(); // We'll figure out if any of the modified files we're testing are in // Maven directories. We won't want to run a bunch of Java tests for // changes to CSS files or whatever. $modified_paths = $this->getModifiedPaths(); $maven_failed = false; foreach ($maven_top_dirs as $dir) { $dir_with_trailing_slash = $dir . '/'; foreach ($modified_paths as $path) { if ($dir_with_trailing_slash === substr($path, 0, strlen($dir_with_trailing_slash))) { $future = new ExecFuture('mvn test'); $future->setCWD($dir); list($status, $stdout, $stderr) = $future->resolve(); if ($status) { // Maven exits with a nonzero status if there were test failures // or if there was a compilation error. $maven_failed = true; break 2; } break; } } } $testResults = $this->parseTestResultsSince($start_time); if ($maven_failed) { // If there wasn't a test failure, then synthesize one to represent // the failure of the test run as a whole, since it probably means the // code failed to compile. $found_failure = false; foreach ($testResults as $testResult) { if ($testResult->getResult() === ArcanistUnitTestResult::RESULT_FAIL || $testResult->getResult() === ArcanistUnitTestResult::RESULT_BROKEN) { $found_failure = true; break; } } if (!$found_failure) { $testResult = new ArcanistUnitTestResult(); $testResult->setResult(ArcanistUnitTestResult::RESULT_BROKEN); $testResult->setName('mvn test'); $testResults[] = $testResult; } } return $testResults; }
public function run() { // Here we create a new unit test "jenkins_async_test" and promise we'll // update the results later. // Jenkins updates the results using `arc call-conduit // differential.updateunitresults` call. If you change the name here, also // make sure to change the name in Jenkins script that updates the test // result -- they have to be the same. $result = new ArcanistUnitTestResult(); $result->setName("jenkins_async_test"); $result->setResult(ArcanistUnitTestResult::RESULT_POSTPONED); return array($result); }
public function run() { $working_copy = $this->getWorkingCopy(); $this->projectRoot = $working_copy->getProjectRoot(); $future = new ExecFuture('npm run coverage'); $future->setCWD($this->projectRoot); list($err, $stdout, $stderr) = $future->resolve(); $result = new ArcanistUnitTestResult(); $result->setName("Node test engine"); $result->setUserData($stdout); if ($err) { $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); } else { $result->setResult(ArcanistUnitTestResult::RESULT_PASS); } return array($result); }
private function parseOutput($output) { $results = array(); $json = json_decode($output, true); foreach ($json['examples'] as $example) { $result = new ArcanistUnitTestResult(); $result->setName($example['full_description']); if (array_key_exists('run_time', $example)) { $result->setDuration($example['run_time']); } switch ($example['status']) { case 'passed': $result->setResult(ArcanistUnitTestResult::RESULT_PASS); break; case 'failed': $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); $result->setUserData($example['exception']['message']); break; case 'pending': $result->setResult(ArcanistUnitTestResult::RESULT_SKIP); break; } $results[] = $result; } return $results; }
/** * Runs the test suite. */ public function run() { $results = array(); $command = '(mkdir -p build && cd build && cmake ..)'; $command .= '&& make -C build all'; $command .= '&& make -C build test'; // Execute the test command & time it. $timeStart = microtime(true); $future = new ExecFuture($command); do { $future->read(); sleep(0.5); } while (!$future->isReady()); list($error, $stdout, $stderr) = $future->resolve(); $timeEnd = microtime(true); // Create a unit test result structure. $result = new ArcanistUnitTestResult(); $result->setNamespace('DerpVision'); $result->setName('Core'); $result->setDuration($timeEnd - $timeStart); if ($error == 0) { $result->setResult(ArcanistUnitTestResult::RESULT_PASS); } else { $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); $result->setUserData($stdout . $stderr); } $results[] = $result; return $results; }
public function run() { // If we are running asynchronously, mark all tests as postponed // and return those results. Otherwise, run the tests and collect // the actual results. if ($this->getEnableAsyncTests()) { $results = array(); $result = new ArcanistUnitTestResult(); $result->setName("jcommon_build"); $result->setResult(ArcanistUnitTestResult::RESULT_POSTPONED); $results[] = $result; return $results; } else { $server = new FacebookBuildServer(); $server->startProjectBuilds(false); return array(); } }
public function runJs() { // First, check to see if karma is on $PATH: list($err, $stdout, $_) = exec_manual("which karma"); if ($err != 0) { $result = new ArcanistUnitTestResult(); $result->setName("Karma not found. Skipping js tests..."); $result->setResult(ArcanistUnitTestResult::RESULT_SKIP); $result->setDuration(0); return array($result); } // Karma IS on the path. $old_dir = getcwd(); $project_root = $this->getWorkingCopy()->getProjectRoot(); chdir($project_root . '/client/js'); exec_manual("karma start karma-conf-oneshot.js"); chdir($old_dir); // Read from the text-results.xml file. $xml = file_get_contents($project_root . '/client/test-results.xml'); $doc = new SimpleXMLElement($xml); // Destroy the test-results.xml file. unlink($project_root . '/client/test-results.xml'); // Extract all the test cases. $results = array(); foreach ($doc->testsuite as $suite) { $suite_name = $suite['name']; foreach ($suite->testcase as $case) { $case_name = $case['name']; $time = $case['time']; $fixture_name = substr($case['classname'], strlen($suite_name) + 1); // Did we fail? $failure = (string) $case->failure; // Convert each to a ArcanistUnitTestResult $result = new ArcanistUnitTestResult(); $result->setName($fixture_name . ' ' . $case_name); $result->setResult($failure ? ArcanistUnitTestResult::RESULT_FAIL : ArcanistUnitTestResult::RESULT_PASS); $result->setUserData($failure); $result->setDuration($time); $results[] = $result; } } return $results; }
public function testCoverageMerges() { $cases = array(array('coverage' => array(), 'expect' => null), array('coverage' => array('UUUNCNC'), 'expect' => 'UUUNCNC'), array('coverage' => array('UUCUUU', 'UUUUCU'), 'expect' => 'UUCUCU'), array('coverage' => array('UUCCCU', 'UUUCCCNNNC'), 'expect' => 'UUCCCCNNNC')); foreach ($cases as $case) { $input = $case['coverage']; $expect = $case['expect']; $actual = ArcanistUnitTestResult::mergeCoverage($input); $this->assertEqual($expect, $actual); } }
private function checkNonEmptyTestPlan() { $result = new ArcanistUnitTestResult(); $result->setName("Test Plan"); $lines = join(' ', $this->getMessage()); $testPlanExists = preg_match('/\\sTest Plan:/', $lines); if (!$testPlanExists) { $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); print 'Test Plan not found!'; return array($result); } $testPlanEmpty = preg_match('/\\sTest Plan:\\s*?Reviewers:/', $lines); if ($testPlanEmpty) { $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); print 'Test Plan cannot be empty!'; return array($result); } $result->setResult(ArcanistUnitTestResult::RESULT_PASS); print 'Test Plan found!'; return array($result); }
private function parseTestResults($test, $err, $stdout, $stderr) { $result = new ArcanistUnitTestResult(); $result->setName($test); $result->setUserData($stdout . $stderr); $result->setResult($err == 0 ? ArcanistUnitTestResult::RESULT_PASS : ArcanistUnitTestResult::RESULT_FAIL); if (preg_match("/# ELAPSED: (\\d+)ms/", $stderr, $M)) { $result->setDuration($M[1] / 1000); } return $result; }
public function renderUnitResult(ArcanistUnitTestResult $result) { $result_code = $result->getResult(); $duration = ''; if ($result_code == ArcanistUnitTestResult::RESULT_PASS) { $duration = ' ' . $this->formatTestDuration($result->getDuration()); } $return = sprintf(" %s %s\n", $this->getFormattedResult($result->getResult()) . $duration, $result->getName()); if ($result_code != ArcanistUnitTestResult::RESULT_PASS) { $return .= $result->getUserData() . "\n"; } return $return; }
/** * Parse test results from phpunit json report * * @param string $path Path to test * @param string $test_results String containing phpunit json report * * @return array */ public function parseTestResults($path, $test_results) { $report = $this->getJsonReport($test_results); // coverage is for all testcases in the executed $path $coverage = array(); if ($this->enableCoverage !== false) { $coverage = $this->readCoverage(); } $results = array(); foreach ($report as $event) { if ('test' != $event->event) { continue; } $status = ArcanistUnitTestResult::RESULT_PASS; $user_data = ''; if ('fail' == $event->status) { $status = ArcanistUnitTestResult::RESULT_FAIL; $user_data .= $event->message . "\n"; foreach ($event->trace as $trace) { $user_data .= sprintf("\n%s:%s", $trace->file, $trace->line); } } else { if ('error' == $event->status) { if (strpos($event->message, 'Skipped Test') !== false) { $status = ArcanistUnitTestResult::RESULT_SKIP; $user_data .= $event->message; } else { if (strpos($event->message, 'Incomplete Test') !== false) { $status = ArcanistUnitTestResult::RESULT_SKIP; $user_data .= $event->message; } else { $status = ArcanistUnitTestResult::RESULT_BROKEN; $user_data .= $event->message; foreach ($event->trace as $trace) { $user_data .= sprintf("\n%s:%s", $trace->file, $trace->line); } } } } } $name = preg_replace('/ \\(.*\\)/', '', $event->test); $result = new ArcanistUnitTestResult(); $result->setName($name); $result->setResult($status); $result->setDuration($event->time); $result->setCoverage($coverage); $result->setUserData($user_data); $results[] = $result; } return $results; }
private function checkNonEmptyRevertPlan() { $result = new ArcanistUnitTestResult(); $lines = implode(' ', $this->getMessage()); $revert_plan_exists = preg_match('/\\sRevert Plan:/', $lines); if (!$revert_plan_exists) { $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); $result->setName('Revert Plan not found! (See http://t.uber.com/revert for more info)'); return array($result); } $revert_plan_empty = preg_match('/\\sRevert Plan:\\s*?$/', $lines); if ($revert_plan_empty) { $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); $result->setName('Revert Plan cannot be empty! (See http://t.uber.com/revert for more info)'); return array($result); } $result->setResult(ArcanistUnitTestResult::RESULT_PASS); $result->setName('Revert Plan found!'); return array($result); }
public function run() { $results = array(); $build_start = microtime(true); $config_manager = $this->getConfigurationManager(); if ($this->getEnableCoverage() !== false) { $command = $config_manager->getConfigFromAnySource('unit.engine.tap.cover'); } else { $command = $config_manager->getConfigFromAnySource('unit.engine.tap.command'); } $timeout = $config_manager->getConfigFromAnySource('unit.engine.tap.timeout'); if (!$timeout) { $timeout = 15; } $future = new ExecFuture('%C', $command); $future->setTimeout($timeout); $result = new ArcanistUnitTestResult(); $result->setName($command ? $command : 'unknown'); try { list($stdout, $stderr) = $future->resolvex(); $result->setResult(ArcanistUnitTestResult::RESULT_PASS); if ($this->getEnableCoverage() !== false) { $coverage = $this->readCoverage('coverage/cobertura-coverage.xml'); $result->setCoverage($coverage); } } catch (CommandException $exc) { $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); if ($future->getWasKilledByTimeout()) { print "Process stdout:\n" . $exc->getStdout() . "\nProcess stderr:\n" . $exc->getStderr() . "\nExceeded timeout of {$timeout} secs.\nMake unit tests faster."; } else { $result->setUserdata($exc->getStdout() . $exc->getStderr()); } } $result->setDuration(microtime(true) - $build_start); $results[] = $result; return $results; }
public function processRequest() { $request = $this->getRequest(); $author_phid = $request->getUser()->getPHID(); $rendering_reference = $request->getStr('ref'); $parts = explode('/', $rendering_reference); if (count($parts) == 2) { list($id, $vs) = $parts; } else { $id = $parts[0]; $vs = 0; } $id = (int) $id; $vs = (int) $vs; $changeset = id(new DifferentialChangeset())->load($id); if (!$changeset) { return new Aphront404Response(); } $view = $request->getStr('view'); if ($view) { $changeset->attachHunks($changeset->loadHunks()); $phid = idx($changeset->getMetadata(), "{$view}:binary-phid"); if ($phid) { return id(new AphrontRedirectResponse())->setURI("/file/info/{$phid}/"); } switch ($view) { case 'new': return $this->buildRawFileResponse($changeset, $is_new = true); case 'old': if ($vs && $vs != -1) { $vs_changeset = id(new DifferentialChangeset())->load($vs); if ($vs_changeset) { $vs_changeset->attachHunks($vs_changeset->loadHunks()); return $this->buildRawFileResponse($vs_changeset, $is_new = true); } } return $this->buildRawFileResponse($changeset, $is_new = false); default: return new Aphront400Response(); } } if ($vs && $vs != -1) { $vs_changeset = id(new DifferentialChangeset())->load($vs); if (!$vs_changeset) { return new Aphront404Response(); } } if (!$vs) { $right = $changeset; $left = null; $right_source = $right->getID(); $right_new = true; $left_source = $right->getID(); $left_new = false; $render_cache_key = $right->getID(); } else { if ($vs == -1) { $right = null; $left = $changeset; $right_source = $left->getID(); $right_new = false; $left_source = $left->getID(); $left_new = true; $render_cache_key = null; } else { $right = $changeset; $left = $vs_changeset; $right_source = $right->getID(); $right_new = true; $left_source = $left->getID(); $left_new = true; $render_cache_key = null; } } if ($left) { $left->attachHunks($left->loadHunks()); } if ($right) { $right->attachHunks($right->loadHunks()); } if ($left) { $left_data = $left->makeNewFile(); if ($right) { $right_data = $right->makeNewFile(); } else { $right_data = $left->makeOldFile(); } $engine = new PhabricatorDifferenceEngine(); $synthetic = $engine->generateChangesetFromFileContent($left_data, $right_data); $choice = clone nonempty($left, $right); $choice->attachHunks($synthetic->getHunks()); $changeset = $choice; } $coverage = null; if ($right && $right->getDiffID()) { $unit = id(new DifferentialDiffProperty())->loadOneWhere('diffID = %d AND name = %s', $right->getDiffID(), 'arc:unit'); if ($unit) { $coverage = array(); foreach ($unit->getData() as $result) { $result_coverage = idx($result, 'coverage'); if (!$result_coverage) { continue; } $file_coverage = idx($result_coverage, $right->getFileName()); if (!$file_coverage) { continue; } $coverage[] = $file_coverage; } $coverage = ArcanistUnitTestResult::mergeCoverage($coverage); } } $spec = $request->getStr('range'); list($range_s, $range_e, $mask) = DifferentialChangesetParser::parseRangeSpecification($spec); $parser = new DifferentialChangesetParser(); $parser->setCoverage($coverage); $parser->setChangeset($changeset); $parser->setRenderingReference($rendering_reference); $parser->setRenderCacheKey($render_cache_key); $parser->setRightSideCommentMapping($right_source, $right_new); $parser->setLeftSideCommentMapping($left_source, $left_new); $parser->setWhitespaceMode($request->getStr('whitespace')); if ($left && $right) { $parser->setOriginals($left, $right); } // Load both left-side and right-side inline comments. $inlines = $this->loadInlineComments(array($left_source, $right_source), $author_phid); if ($left_new) { $inlines = array_merge($inlines, $this->buildLintInlineComments($left)); } if ($right_new) { $inlines = array_merge($inlines, $this->buildLintInlineComments($right)); } $phids = array(); foreach ($inlines as $inline) { $parser->parseInlineComment($inline); if ($inline->getAuthorPHID()) { $phids[$inline->getAuthorPHID()] = true; } } $phids = array_keys($phids); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $parser->setHandles($handles); $engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine(); $parser->setMarkupEngine($engine); if ($request->isAjax()) { // TODO: This is sort of lazy, the effect is just to not render "Edit" // and "Reply" links on the "standalone view". $parser->setUser($request->getUser()); } $output = $parser->render($range_s, $range_e, $mask); $mcov = $parser->renderModifiedCoverage(); if ($request->isAjax()) { $coverage = array('differential-mcoverage-' . md5($changeset->getFilename()) => $mcov); return id(new PhabricatorChangesetResponse())->setRenderedChangeset($output)->setCoverage($coverage); } Javelin::initBehavior('differential-show-more', array('uri' => '/differential/changeset/', 'whitespace' => $request->getStr('whitespace'))); Javelin::initBehavior('differential-comment-jump', array()); $detail = new DifferentialChangesetDetailView(); $detail->setChangeset($changeset); $detail->appendChild($output); $detail->setVsChangesetID($left_source); $output = id(new DifferentialPrimaryPaneView())->setLineWidthFromChangesets(array($changeset))->appendChild('<div class="differential-review-stage" ' . 'id="differential-review-stage">' . $detail->render() . '</div>'); return $this->buildStandardPageResponse(array($output), array('title' => 'Changeset View')); }
private function loadCoverage(DifferentialChangeset $changeset) { $target_phids = $changeset->getDiff()->getBuildTargetPHIDs(); if (!$target_phids) { return null; } $unit = id(new HarbormasterBuildUnitMessage())->loadAllWhere('buildTargetPHID IN (%Ls)', $target_phids); if (!$unit) { return null; } $coverage = array(); foreach ($unit as $message) { $test_coverage = $message->getProperty('coverage'); if ($test_coverage === null) { continue; } $coverage_data = idx($test_coverage, $changeset->getFileName()); if (!strlen($coverage_data)) { continue; } $coverage[] = $coverage_data; } if (!$coverage) { return null; } return ArcanistUnitTestResult::mergeCoverage($coverage); }
public function loadCoverageMap(PhabricatorUser $viewer) { $target_phids = $this->getBuildTargetPHIDs(); if (!$target_phids) { return array(); } $unit = id(new HarbormasterBuildUnitMessage())->loadAllWhere('buildTargetPHID IN (%Ls)', $target_phids); $map = array(); foreach ($unit as $message) { $coverage = $message->getProperty('coverage', array()); foreach ($coverage as $path => $coverage_data) { $map[$path][] = $coverage_data; } } foreach ($map as $path => $coverage_items) { $map[$path] = ArcanistUnitTestResult::mergeCoverage($coverage_items); } return $map; }
public function render() { $this->requireResource('differential-core-view-css'); $this->requireResource('differential-table-of-contents-css'); $rows = array(); $coverage = array(); if ($this->unitTestData) { $coverage_by_file = array(); foreach ($this->unitTestData as $result) { $test_coverage = idx($result, 'coverage'); if (!$test_coverage) { continue; } foreach ($test_coverage as $file => $results) { $coverage_by_file[$file][] = $results; } } foreach ($coverage_by_file as $file => $coverages) { $coverage[$file] = ArcanistUnitTestResult::mergeCoverage($coverages); } } $changesets = $this->changesets; $paths = array(); foreach ($changesets as $id => $changeset) { $type = $changeset->getChangeType(); $ftype = $changeset->getFileType(); $ref = idx($this->references, $id); $display_file = $changeset->getDisplayFilename(); $meta = null; if (DifferentialChangeType::isOldLocationChangeType($type)) { $away = $changeset->getAwayPaths(); if (count($away) > 1) { $meta = array(); if ($type == DifferentialChangeType::TYPE_MULTICOPY) { $meta[] = pht('Deleted after being copied to multiple locations:'); } else { $meta[] = pht('Copied to multiple locations:'); } foreach ($away as $path) { $meta[] = $path; } $meta = phutil_implode_html(phutil_tag('br'), $meta); } else { if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) { $display_file = $this->renderRename($display_file, reset($away), "→"); } else { $meta = pht('Copied to %s', reset($away)); } } } else { if ($type == DifferentialChangeType::TYPE_MOVE_HERE) { $old_file = $changeset->getOldFile(); $display_file = $this->renderRename($display_file, $old_file, "←"); } else { if ($type == DifferentialChangeType::TYPE_COPY_HERE) { $meta = pht('Copied from %s', $changeset->getOldFile()); } } } $link = $this->renderChangesetLink($changeset, $ref, $display_file); $line_count = $changeset->getAffectedLineCount(); if ($line_count == 0) { $lines = ''; } else { $lines = ' ' . pht('(%d line(s))', $line_count); } $char = DifferentialChangeType::getSummaryCharacterForChangeType($type); $chartitle = DifferentialChangeType::getFullNameForChangeType($type); $desc = DifferentialChangeType::getShortNameForFileType($ftype); if ($desc) { $desc = '(' . $desc . ')'; } $pchar = $changeset->getOldProperties() === $changeset->getNewProperties() ? '' : phutil_tag('span', array('title' => pht('Properties Changed')), 'M'); $fname = $changeset->getFilename(); $cov = $this->renderCoverage($coverage, $fname); if ($cov === null) { $mcov = $cov = phutil_tag('em', array(), '-'); } else { $mcov = phutil_tag('div', array('id' => 'differential-mcoverage-' . md5($fname), 'class' => 'differential-mcoverage-loading'), isset($this->visibleChangesets[$id]) ? pht('Loading...') : pht('?')); } if ($meta) { $meta = phutil_tag('div', array('class' => 'differential-toc-meta'), $meta); } if ($this->diff && $this->repository) { $paths[] = $changeset->getAbsoluteRepositoryPath($this->repository, $this->diff); } $rows[] = array($char, $pchar, $desc, array($link, $lines, $meta), $cov, $mcov); } $editor_link = null; if ($paths && $this->user) { $editor_link = $this->user->loadEditorLink($paths, 1, $this->repository->getCallsign()); if ($editor_link) { $editor_link = phutil_tag('a', array('href' => $editor_link, 'class' => 'button differential-toc-edit-all'), pht('Open All in Editor')); } } $reveal_link = javelin_tag('a', array('sigil' => 'differential-reveal-all', 'mustcapture' => true, 'class' => 'button differential-toc-reveal-all'), pht('Show All Context')); $buttons = phutil_tag('div', array('class' => 'differential-toc-buttons grouped'), array($editor_link, $reveal_link)); $table = id(new AphrontTableView($rows)); $table->setHeaders(array('', '', '', pht('Path'), pht('Coverage (All)'), pht('Coverage (Touched)'))); $table->setColumnClasses(array('differential-toc-char center', 'differential-toc-prop center', 'differential-toc-ftype center', 'differential-toc-file wide', 'differential-toc-cov', 'differential-toc-cov')); $table->setDeviceVisibility(array(true, true, true, true, false, false)); $anchor = id(new PhabricatorAnchorView())->setAnchorName('toc')->setNavigationMarker(true); return id(new PHUIObjectBoxView())->setHeaderText(pht('Table of Contents'))->appendChild($anchor)->appendChild($table)->appendChild($buttons); }
function generateValgrindTestResults() { $this->terminateProcess(); if ($this->coverage) { return $this->generateCoverageResults(); } if (!$this->valgrind) { return array(); } $definite_leaks = array(); $possible_leaks = array(); $errors = array(); $descriptors = array(); // valgrind seems to use an interesting definition of valid XML. // Tolerate having multiple documents in one file. // Confluence of weird bugs; hhvm has very low preg_match limits // so we have to grovel around to make sure that we read this // stuff in properly :-/ $documents = array(); $in_doc = false; $doc = null; foreach (file($this->vg_log . '.xml') as $line) { if ($in_doc) { $doc[] = $line; if (preg_match(',</valgrindoutput>,', $line)) { $documents[] = implode("\n", $doc); $doc = null; } } else { if (preg_match(',<valgrindoutput>,', $line)) { $doc = array($line); $in_doc = true; } } } libxml_use_internal_errors(true); foreach ($documents as $data) { libxml_clear_errors(); $vg = @simplexml_load_string($data); if (is_object($vg)) { foreach ($vg->error as $err) { $render = $this->renderVGResult($err); switch ($err->kind) { case 'Leak_DefinitelyLost': $definite_leaks[] = $render; break; case 'Leak_PossiblyLost': $possible_leaks[] = $render; break; default: $errors[] = $render; } } // These look like fd leak records, but they're not documented // as such. These go away if we turn off track-fds foreach ($vg->stack as $stack) { // Suppressing this for now: posix_spawn seems to confuse // some valgrind's, particularly the version we run on travis, // as it records open descriptors from the exec'ing child // $descriptors[] = $this->renderVGStack($stack); } } else { $why = 'failed to parse xml'; $lines = explode("\n", $data); foreach (libxml_get_errors() as $err) { $slice = array_slice($lines, $err->line - 3, 6); $slice = implode("\n", $slice); $why .= sprintf("\n%s (line %d col %d) %s", $err->message, $err->line, $err->column, $slice); } printf("parsing valgrind output: %s\n", $why); } } $results = array(); $res = new ArcanistUnitTestResult(); $res->setName('valgrind possible leaks'); $res->setUserData(implode("\n\n", $possible_leaks)); $res->setResult(count($possible_leaks) ? ArcanistUnitTestResult::RESULT_SKIP : ArcanistUnitTestResult::RESULT_PASS); $results[] = $res; $res = new ArcanistUnitTestResult(); $res->setName('descriptor leaks'); $res->setUserData(implode("\n\n", $descriptors)); $res->setResult(count($descriptors) ? ArcanistUnitTestResult::RESULT_FAIL : ArcanistUnitTestResult::RESULT_PASS); $results[] = $res; $res = new ArcanistUnitTestResult(); $res->setName('valgrind leaks'); $res->setUserData(implode("\n\n", $definite_leaks)); $leak_res = count($definite_leaks) ? ArcanistUnitTestResult::RESULT_FAIL : ArcanistUnitTestResult::RESULT_PASS; if ($leak_res == ArcanistUnitTestResult::RESULT_FAIL && getenv('TRAVIS') == 'true') { // Travis has false positives at this time, downgrade $leak_res = ArcanistUnitTestResult::RESULT_SKIP; } $res->setResult($leak_res); $results[] = $res; $res = new ArcanistUnitTestResult(); $res->setName('valgrind errors'); $res->setUserData(implode("\n\n", $errors)); $res->setResult(count($errors) ? ArcanistUnitTestResult::RESULT_FAIL : ArcanistUnitTestResult::RESULT_PASS); $results[] = $res; return $results; }
public function render() { require_celerity_resource('differential-core-view-css'); require_celerity_resource('differential-table-of-contents-css'); $rows = array(); $coverage = array(); if ($this->unitTestData) { $coverage_by_file = array(); foreach ($this->unitTestData as $result) { $test_coverage = idx($result, 'coverage'); if (!$test_coverage) { continue; } foreach ($test_coverage as $file => $results) { $coverage_by_file[$file][] = $results; } } foreach ($coverage_by_file as $file => $coverages) { $coverage[$file] = ArcanistUnitTestResult::mergeCoverage($coverages); } } $changesets = $this->changesets; $paths = array(); foreach ($changesets as $id => $changeset) { $type = $changeset->getChangeType(); $ftype = $changeset->getFileType(); $ref = idx($this->references, $id); $link = $this->renderChangesetLink($changeset, $ref); if (DifferentialChangeType::isOldLocationChangeType($type)) { $away = $changeset->getAwayPaths(); if (count($away) > 1) { $meta = array(); if ($type == DifferentialChangeType::TYPE_MULTICOPY) { $meta[] = 'Deleted after being copied to multiple locations:'; } else { $meta[] = 'Copied to multiple locations:'; } foreach ($away as $path) { $meta[] = phutil_escape_html($path); } $meta = implode('<br />', $meta); } else { if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) { $meta = 'Moved to ' . phutil_escape_html(reset($away)); } else { $meta = 'Copied to ' . phutil_escape_html(reset($away)); } } } else { if ($type == DifferentialChangeType::TYPE_MOVE_HERE) { $meta = 'Moved from ' . phutil_escape_html($changeset->getOldFile()); } else { if ($type == DifferentialChangeType::TYPE_COPY_HERE) { $meta = 'Copied from ' . phutil_escape_html($changeset->getOldFile()); } else { $meta = null; } } } $line_count = $changeset->getAffectedLineCount(); if ($line_count == 0) { $lines = null; } else { $lines = ' ' . pht('(%d line(s))', $line_count); } $char = DifferentialChangeType::getSummaryCharacterForChangeType($type); $chartitle = DifferentialChangeType::getFullNameForChangeType($type); $desc = DifferentialChangeType::getShortNameForFileType($ftype); if ($desc) { $desc = '(' . $desc . ')'; } $pchar = $changeset->getOldProperties() === $changeset->getNewProperties() ? null : '<span title="Properties Changed">M</span>'; $fname = $changeset->getFilename(); $cov = $this->renderCoverage($coverage, $fname); if ($cov === null) { $mcov = $cov = '<em>-</em>'; } else { $mcov = phutil_render_tag('div', array('id' => 'differential-mcoverage-' . md5($fname), 'class' => 'differential-mcoverage-loading'), isset($this->visibleChangesets[$id]) ? 'Loading...' : '?'); } $rows[] = '<tr>' . phutil_render_tag('td', array('class' => 'differential-toc-char', 'title' => $chartitle), $char) . '<td class="differential-toc-prop">' . $pchar . '</td>' . '<td class="differential-toc-ftype">' . $desc . '</td>' . '<td class="differential-toc-file">' . $link . $lines . '</td>' . '<td class="differential-toc-cov">' . $cov . '</td>' . '<td class="differential-toc-mcov">' . $mcov . '</td>' . '</tr>'; if ($meta) { $rows[] = '<tr>' . '<td colspan="3"></td>' . '<td class="differential-toc-meta">' . $meta . '</td>' . '</tr>'; } if ($this->diff && $this->repository) { $paths[] = $changeset->getAbsoluteRepositoryPath($this->repository, $this->diff); } } $editor_link = null; if ($paths && $this->user) { $editor_link = $this->user->loadEditorLink(implode(' ', $paths), 1, $this->repository->getCallsign()); if ($editor_link) { $editor_link = phutil_render_tag('a', array('href' => $editor_link, 'class' => 'button differential-toc-edit-all'), 'Open All in Editor'); } } $reveal_link = javelin_render_tag('a', array('sigil' => 'differential-reveal-all', 'mustcapture' => true, 'class' => 'button differential-toc-reveal-all'), 'Show All Context'); return '<div id="differential-review-toc" ' . 'class="differential-toc differential-panel">' . $editor_link . $reveal_link . '<h1>Table of Contents</h1>' . '<table>' . '<tr>' . '<th></th>' . '<th></th>' . '<th></th>' . '<th>Path</th>' . '<th class="differential-toc-cov">Coverage (All)</th>' . '<th class="differential-toc-mcov">Coverage (Touched)</th>' . '</tr>' . implode("\n", $rows) . '</table>' . '</div>'; }
/** * Parse test results from provided input and return an array * of @{class:ArcanistUnitTestResult}. * * @param string $test_results String containing test results * * @return array ArcanistUnitTestResult */ public function parseTestResults($test_results) { if (!strlen($test_results)) { throw new Exception(pht('%s argument to %s must not be empty', 'test_results', 'parseTestResults()')); } // xunit xsd: https://gist.github.com/959290 $xunit_dom = new DOMDocument(); $load_success = @$xunit_dom->loadXML($test_results); if (!$load_success) { $input_start = id(new PhutilUTF8StringTruncator())->setMaximumGlyphs(150)->truncateString($test_results); throw new Exception(sprintf("%s\n\n%s", pht('Failed to load XUnit report; Input starts with:'), $input_start)); } $results = array(); $testcases = $xunit_dom->getElementsByTagName('testcase'); foreach ($testcases as $testcase) { $classname = $testcase->getAttribute('classname'); $name = $testcase->getAttribute('name'); $time = $testcase->getAttribute('time'); $status = ArcanistUnitTestResult::RESULT_PASS; $user_data = ''; // A skipped test is a test which was ignored using framework // mechanisms (e.g. @skip decorator) $skipped = $testcase->getElementsByTagName('skipped'); if ($skipped->length > 0) { $status = ArcanistUnitTestResult::RESULT_SKIP; $messages = array(); for ($ii = 0; $ii < $skipped->length; $ii++) { $messages[] = trim($skipped->item($ii)->nodeValue, " \n"); } $user_data .= implode("\n", $messages); } // Failure is a test which the code has explicitly failed by using // the mechanisms for that purpose. e.g., via an assertEquals $failures = $testcase->getElementsByTagName('failure'); if ($failures->length > 0) { $status = ArcanistUnitTestResult::RESULT_FAIL; $messages = array(); for ($ii = 0; $ii < $failures->length; $ii++) { $messages[] = trim($failures->item($ii)->nodeValue, " \n"); } $user_data .= implode("\n", $messages) . "\n"; } // An errored test is one that had an unanticipated problem. e.g., an // unchecked throwable, or a problem with an implementation of the test. $errors = $testcase->getElementsByTagName('error'); if ($errors->length > 0) { $status = ArcanistUnitTestResult::RESULT_BROKEN; $messages = array(); for ($ii = 0; $ii < $errors->length; $ii++) { $messages[] = trim($errors->item($ii)->nodeValue, " \n"); } $user_data .= implode("\n", $messages) . "\n"; } $result = new ArcanistUnitTestResult(); $result->setName($classname . '.' . $name); $result->setResult($status); $result->setDuration((double) $time); $result->setUserData($user_data); $results[] = $result; } return $results; }
/** * Parses the test results from xUnit. * * @param string The name of the xUnit results file. * @param string The name of the coverage file if one was provided by * `buildTestFuture`. This is passed through to * `parseCoverageResult`. * @return array Test results. */ private function parseTestResult($xunit_tmp, $coverage) { $xunit_dom = new DOMDocument(); $xunit_dom->loadXML(Filesystem::readFile($xunit_tmp)); $results = array(); $tests = $xunit_dom->getElementsByTagName('test'); foreach ($tests as $test) { $name = $test->getAttribute('name'); $time = $test->getAttribute('time'); $status = ArcanistUnitTestResult::RESULT_UNSOUND; switch ($test->getAttribute('result')) { case 'Pass': $status = ArcanistUnitTestResult::RESULT_PASS; break; case 'Fail': $status = ArcanistUnitTestResult::RESULT_FAIL; break; case 'Skip': $status = ArcanistUnitTestResult::RESULT_SKIP; break; } $userdata = ''; $reason = $test->getElementsByTagName('reason'); $failure = $test->getElementsByTagName('failure'); if ($reason->length > 0 || $failure->length > 0) { $node = $reason->length > 0 ? $reason : $failure; $message = $node->item(0)->getElementsByTagName('message'); if ($message->length > 0) { $userdata = $message->item(0)->nodeValue; } $stacktrace = $node->item(0)->getElementsByTagName('stack-trace'); if ($stacktrace->length > 0) { $userdata .= "\n" . $stacktrace->item(0)->nodeValue; } } $result = new ArcanistUnitTestResult(); $result->setName($name); $result->setResult($status); $result->setDuration($time); $result->setUserData($userdata); if ($coverage != null) { $result->setCoverage($this->parseCoverageResult($coverage)); } $results[] = $result; } return $results; }
/** * Parse test results from Go test report * (e.g. `go test -v`) * * @param string $path Path to test * @param string $test_results String containing Go test output * * @return array */ public function parseTestResults($path, $test_results) { $test_results = explode("\n", $test_results); $results = array(); // We'll get our full test case name at the end and add it back in $test_case_name = ''; // Temp store for test case results (in case we run multiple test cases) $test_case_results = array(); foreach ($test_results as $i => $line) { if (strncmp($line, '--- PASS', 8) === 0) { // We have a passing test $meta = array(); preg_match('/^--- PASS: (?P<test_name>.+) \\((?P<time>.+) seconds\\).*/', $line, $meta); $result = new ArcanistUnitTestResult(); // For now set name without test case, we'll add it later $result->setName($meta['test_name']); $result->setResult(ArcanistUnitTestResult::RESULT_PASS); $result->setDuration($meta['time']); $test_case_results[] = $result; continue; } if (strncmp($line, '--- FAIL', 8) === 0) { // We have a failing test $reason = trim($test_results[$i + 1]); $meta = array(); preg_match('/^--- FAIL: (?P<test_name>.+) \\((?P<time>.+) seconds\\).*/', $line, $meta); $result = new ArcanistUnitTestResult(); $result->setName($meta['test_name']); $result->setResult(ArcanistUnitTestResult::RESULT_FAIL); $result->setDuration($meta['time']); $result->setUserData($reason . "\n"); $test_case_results[] = $result; continue; } if (strncmp($line, 'ok', 2) === 0) { $meta = array(); preg_match('/^ok[\\s\\t]+(?P<test_name>\\w.*)[\\s\\t]+(?P<time>.*)s.*/', $line, $meta); $test_case_name = str_replace('/', '::', $meta['test_name']); // Our test case passed // check to make sure we were in verbose (-v) mode if (empty($test_case_results)) { // We weren't in verbose mode // create one successful result for the whole test case $test_name = 'Go::TestCase::' . $test_case_name; $result = new ArcanistUnitTestResult(); $result->setName($test_name); $result->setResult(ArcanistUnitTestResult::RESULT_PASS); $result->setDuration($meta['time']); $results[] = $result; } else { $test_case_results = $this->fixNames($test_case_results, $test_case_name); $results = array_merge($results, $test_case_results); $test_case_results = array(); } continue; } if (strncmp($line, "FAIL\t", 5) === 0) { $meta = array(); preg_match('/^FAIL[\\s\\t]+(?P<test_name>\\w.*)[\\s\\t]+.*/', $line, $meta); $test_case_name = str_replace('/', '::', $meta['test_name']); $test_case_results = $this->fixNames($test_case_results, $test_case_name); $results = array_merge($results, $test_case_results); $test_case_results = array(); continue; } } return $results; }
public function run() { $working_copy = $this->getWorkingCopy(); $paths = $this->getArgument('paths'); $rev = $this->getArgument('rev'); $everything = $this->getArgument('everything'); if ($everything && $paths) { throw new ArcanistUsageException(pht('You can not specify paths with %s. The %s flag runs every test.', '--everything', '--everything')); } if ($everything) { $paths = iterator_to_array($this->getRepositoryApi()->getAllFiles()); } else { $paths = $this->selectPathsForWorkflow($paths, $rev); } $this->engine = $this->newUnitTestEngine($this->getArgument('engine')); if ($everything) { $this->engine->setRunAllTests(true); } else { $this->engine->setPaths($paths); } $this->engine->setArguments($this->getPassthruArgumentsAsMap('unit')); $renderer = new ArcanistUnitConsoleRenderer(); $this->engine->setRenderer($renderer); $enable_coverage = null; // Means "default". if ($this->getArgument('coverage') || $this->getArgument('detailed-coverage')) { $enable_coverage = true; } else { if ($this->getArgument('no-coverage')) { $enable_coverage = false; } } $this->engine->setEnableCoverage($enable_coverage); // Enable possible async tests only for 'arc diff' not 'arc unit' if ($this->getParentWorkflow()) { $this->engine->setEnableAsyncTests(true); } else { $this->engine->setEnableAsyncTests(false); } $results = $this->engine->run(); $this->validateUnitEngineResults($this->engine, $results); $this->testResults = $results; $console = PhutilConsole::getConsole(); $output_format = $this->getOutputFormat(); if ($output_format !== 'full') { $console->disableOut(); } $unresolved = array(); $coverage = array(); foreach ($results as $result) { $result_code = $result->getResult(); if ($this->engine->shouldEchoTestResults()) { $console->writeOut('%s', $renderer->renderUnitResult($result)); } if ($result_code != ArcanistUnitTestResult::RESULT_PASS) { $unresolved[] = $result; } if ($result->getCoverage()) { foreach ($result->getCoverage() as $file => $report) { $coverage[$file][] = $report; } } } if ($coverage) { $file_coverage = array_fill_keys($paths, 0); $file_reports = array(); foreach ($coverage as $file => $reports) { $report = ArcanistUnitTestResult::mergeCoverage($reports); $cov = substr_count($report, 'C'); $uncov = substr_count($report, 'U'); if ($cov + $uncov) { $coverage = $cov / ($cov + $uncov); } else { $coverage = 0; } $file_coverage[$file] = $coverage; $file_reports[$file] = $report; } $console->writeOut("\n__%s__\n", pht('COVERAGE REPORT')); asort($file_coverage); foreach ($file_coverage as $file => $coverage) { $console->writeOut(" **%s%%** %s\n", sprintf('% 3d', (int) (100 * $coverage)), $file); $full_path = $working_copy->getProjectRoot() . '/' . $file; if ($this->getArgument('detailed-coverage') && Filesystem::pathExists($full_path) && is_file($full_path) && array_key_exists($file, $file_reports)) { $console->writeOut('%s', $this->renderDetailedCoverageReport(Filesystem::readFile($full_path), $file_reports[$file])); } } } $this->unresolvedTests = $unresolved; $overall_result = self::RESULT_OKAY; foreach ($results as $result) { $result_code = $result->getResult(); if ($result_code == ArcanistUnitTestResult::RESULT_FAIL || $result_code == ArcanistUnitTestResult::RESULT_BROKEN) { $overall_result = self::RESULT_FAIL; break; } else { if ($result_code == ArcanistUnitTestResult::RESULT_UNSOUND) { $overall_result = self::RESULT_UNSOUND; } } } if ($output_format !== 'full') { $console->enableOut(); } $data = array_values(mpull($results, 'toDictionary')); switch ($output_format) { case 'ugly': $console->writeOut('%s', json_encode($data)); break; case 'json': $json = new PhutilJSON(); $console->writeOut('%s', $json->encodeFormatted($data)); break; case 'full': // already printed break; case 'none': // do nothing break; } return $overall_result; }