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 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); }
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>'; }
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')); }
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; }
public function run() { $working_copy = $this->getWorkingCopy(); $engine_class = $this->getArgument('engine', $working_copy->getConfigFromAnySource('unit.engine')); if (!$engine_class) { throw new ArcanistNoEngineException("No unit test engine is configured for this project. Edit .arcconfig " . "to specify a unit test engine."); } $paths = $this->getArgument('paths'); $rev = $this->getArgument('rev'); $everything = $this->getArgument('everything'); if ($everything && $paths) { throw new ArcanistUsageException("You can not specify paths with --everything. The --everything " . "flag runs every test."); } $paths = $this->selectPathsForWorkflow($paths, $rev); if (!class_exists($engine_class) || !is_subclass_of($engine_class, 'ArcanistBaseUnitTestEngine')) { throw new ArcanistUsageException("Configured unit test engine '{$engine_class}' is not a subclass of " . "'ArcanistBaseUnitTestEngine'."); } $this->engine = newv($engine_class, array()); $this->engine->setWorkingCopy($working_copy); 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->testResults = $results; $console = PhutilConsole::getConsole(); $json_output = $this->getArgument('json'); if ($json_output) { $console->disableOut(); } $unresolved = array(); $coverage = array(); $postponed_count = 0; foreach ($results as $result) { $result_code = $result->getResult(); if ($result_code == ArcanistUnitTestResult::RESULT_POSTPONED) { $postponed_count++; $unresolved[] = $result; } else { 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 ($postponed_count) { $console->writeOut('%s', $renderer->renderPostponedResult($postponed_count)); } 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__COVERAGE REPORT__\n"); 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)) { $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; } else { if ($result_code == ArcanistUnitTestResult::RESULT_POSTPONED && $overall_result != self::RESULT_UNSOUND) { $overall_result = self::RESULT_POSTPONED; } } } } if ($json_output) { $console->enableOut(); $data = array_values(mpull($results, 'toDictionary')); if ($this->getArgument('ugly')) { $console->writeOut('%s', json_encode($data)); } else { $json = new PhutilJSON(); $console->writeOut('%s', $json->encodeFormatted($data)); } } return $overall_result; }
public function run() { $working_copy = $this->getWorkingCopy(); $engine_class = $this->getArgument('engine', $working_copy->getConfig('unit_engine')); if (!$engine_class) { throw new ArcanistNoEngineException("No unit test engine is configured for this project. Edit .arcconfig " . "to specify a unit test engine."); } $paths = $this->getArgument('paths'); $rev = $this->getArgument('rev'); $paths = $this->selectPathsForWorkflow($paths, $rev); PhutilSymbolLoader::loadClass($engine_class); if (!is_subclass_of($engine_class, 'ArcanistBaseUnitTestEngine')) { throw new ArcanistUsageException("Configured unit test engine '{$engine_class}' is not a subclass of " . "'ArcanistBaseUnitTestEngine'."); } $this->engine = newv($engine_class, array()); $this->engine->setWorkingCopy($working_copy); $this->engine->setPaths($paths); $this->engine->setArguments($this->getPassthruArgumentsAsMap('unit')); $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->testResults = $results; $status_codes = array(ArcanistUnitTestResult::RESULT_PASS => phutil_console_format('<bg:green>** PASS **</bg>'), ArcanistUnitTestResult::RESULT_FAIL => phutil_console_format('<bg:red>** FAIL **</bg>'), ArcanistUnitTestResult::RESULT_SKIP => phutil_console_format('<bg:yellow>** SKIP **</bg>'), ArcanistUnitTestResult::RESULT_BROKEN => phutil_console_format('<bg:red>** BROKEN **</bg>'), ArcanistUnitTestResult::RESULT_UNSOUND => phutil_console_format('<bg:yellow>** UNSOUND **</bg>'), ArcanistUnitTestResult::RESULT_POSTPONED => phutil_console_format('<bg:yellow>** POSTPONED **</bg>')); $unresolved = array(); $coverage = array(); $postponed_count = 0; foreach ($results as $result) { $result_code = $result->getResult(); if ($result_code == ArcanistUnitTestResult::RESULT_POSTPONED) { $postponed_count++; $unresolved[] = $result; } else { if ($this->engine->shouldEchoTestResults()) { echo ' ' . $status_codes[$result_code]; if ($result_code == ArcanistUnitTestResult::RESULT_PASS) { echo ' ' . self::formatTestDuration($result->getDuration()); } echo ' ' . $result->getName() . "\n"; } if ($result_code != ArcanistUnitTestResult::RESULT_PASS) { if ($this->engine->shouldEchoTestResults()) { echo $result->getUserData() . "\n"; } $unresolved[] = $result; } } if ($result->getCoverage()) { foreach ($result->getCoverage() as $file => $report) { $coverage[$file][] = $report; } } } if ($postponed_count) { echo sprintf("%s %d %s\n", $status_codes[ArcanistUnitTestResult::RESULT_POSTPONED], $postponed_count, $postponed_count > 1 ? 'tests' : 'test'); } 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; } echo "\n"; echo phutil_console_format('__COVERAGE REPORT__'); echo "\n"; asort($file_coverage); foreach ($file_coverage as $file => $coverage) { echo phutil_console_format(" **%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)) { echo $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; } else { if ($result_code == ArcanistUnitTestResult::RESULT_POSTPONED && $overall_result != self::RESULT_UNSOUND) { $overall_result = self::RESULT_POSTPONED; } } } } return $overall_result; }
public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $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; $load_ids = array($id); if ($vs && $vs != -1) { $load_ids[] = $vs; } $changesets = id(new DifferentialChangesetQuery())->setViewer($viewer)->withIDs($load_ids)->needHunks(true)->execute(); $changesets = mpull($changesets, null, 'getID'); $changeset = idx($changesets, $id); if (!$changeset) { return new Aphront404Response(); } $vs_changeset = null; if ($vs && $vs != -1) { $vs_changeset = idx($changesets, $vs); if (!$vs_changeset) { return new Aphront404Response(); } } $view = $request->getStr('view'); if ($view) { $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_changeset) { return $this->buildRawFileResponse($vs_changeset, $is_new = true); } return $this->buildRawFileResponse($changeset, $is_new = false); default: return new Aphront400Response(); } } $old = array(); $new = array(); 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(); $old[] = $changeset; $new[] = $changeset; } 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; $old[] = $changeset; $new[] = $changeset; } else { $right = $changeset; $left = $vs_changeset; $right_source = $right->getID(); $right_new = true; $left_source = $left->getID(); $left_new = true; $render_cache_key = null; $new[] = $left; $new[] = $right; } } 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 = id(new DifferentialChangesetParser())->setCoverage($coverage)->setChangeset($changeset)->setRenderingReference($rendering_reference)->setRenderCacheKey($render_cache_key)->setRightSideCommentMapping($right_source, $right_new)->setLeftSideCommentMapping($left_source, $left_new); $parser->readParametersFromRequest($request); if ($left && $right) { $parser->setOriginals($left, $right); } $diff = $changeset->getDiff(); $revision_id = $diff->getRevisionID(); $can_mark = false; $object_owner_phid = null; $revision = null; if ($revision_id) { $revision = id(new DifferentialRevisionQuery())->setViewer($viewer)->withIDs(array($revision_id))->executeOne(); if ($revision) { $can_mark = $revision->getAuthorPHID() == $viewer->getPHID(); $object_owner_phid = $revision->getAuthorPHID(); } } // Load both left-side and right-side inline comments. if ($revision) { $query = id(new DifferentialInlineCommentQuery())->setViewer($viewer)->needHidden(true)->withRevisionPHIDs(array($revision->getPHID())); $inlines = $query->execute(); $inlines = $query->adjustInlinesForChangesets($inlines, $old, $new, $revision); } else { $inlines = array(); } 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 = $this->loadViewerHandles($phids); $parser->setHandles($handles); $engine = new PhabricatorMarkupEngine(); $engine->setViewer($viewer); foreach ($inlines as $inline) { $engine->addObject($inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); } $engine->process(); $parser->setUser($viewer)->setMarkupEngine($engine)->setShowEditAndReplyLinks(true)->setCanMarkDone($can_mark)->setObjectOwnerPHID($object_owner_phid)->setRange($range_s, $range_e)->setMask($mask); if ($request->isAjax()) { $mcov = $parser->renderModifiedCoverage(); $coverage = array('differential-mcoverage-' . md5($changeset->getFilename()) => $mcov); return id(new PhabricatorChangesetResponse())->setRenderedChangeset($parser->renderChangeset())->setCoverage($coverage)->setUndoTemplates($parser->getRenderer()->renderUndoTemplates()); } $detail = id(new DifferentialChangesetListView())->setUser($this->getViewer())->setChangesets(array($changeset))->setVisibleChangesets(array($changeset))->setRenderingReferences(array($rendering_reference))->setRenderURI('/differential/changeset/')->setDiff($diff)->setTitle(pht('Standalone View'))->setParser($parser); if ($revision_id) { $detail->setInlineCommentControllerURI('/differential/comment/inline/edit/' . $revision_id . '/'); } $crumbs = $this->buildApplicationCrumbs(); if ($revision_id) { $crumbs->addTextCrumb('D' . $revision_id, '/D' . $revision_id); } $diff_id = $diff->getID(); if ($diff_id) { $crumbs->addTextCrumb(pht('Diff %d', $diff_id), $this->getApplicationURI('diff/' . $diff_id)); } $crumbs->addTextCrumb($changeset->getDisplayFilename()); return $this->buildApplicationPage(array($crumbs, $detail), array('title' => pht('Changeset View'), 'device' => false)); }
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; $load_ids = array($id); if ($vs && $vs != -1) { $load_ids[] = $vs; } $changesets = id(new DifferentialChangesetQuery())->setViewer($request->getUser())->withIDs($load_ids)->needHunks(true)->execute(); $changesets = mpull($changesets, null, 'getID'); $changeset = idx($changesets, $id); if (!$changeset) { return new Aphront404Response(); } $vs_changeset = null; if ($vs && $vs != -1) { $vs_changeset = idx($changesets, $vs); if (!$vs_changeset) { return new Aphront404Response(); } } $view = $request->getStr('view'); if ($view) { $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_changeset) { return $this->buildRawFileResponse($vs_changeset, $is_new = true); } return $this->buildRawFileResponse($changeset, $is_new = false); default: return new Aphront400Response(); } } 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_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')); $parser->setCharacterEncoding($request->getStr('encoding')); $parser->setHighlightAs($request->getStr('highlight')); if ($request->getStr('renderer') == '1up') { $parser->setRenderer(new DifferentialChangesetOneUpRenderer()); } 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 = $this->loadViewerHandles($phids); $parser->setHandles($handles); $engine = new PhabricatorMarkupEngine(); $engine->setViewer($request->getUser()); foreach ($inlines as $inline) { $engine->addObject($inline, PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY); } $engine->process(); $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()); // TODO: [HTML] Clean up DifferentialChangesetParser output, but it's // undergoing like six kinds of refactoring anyway. $output = phutil_safe_html($output); $detail = new DifferentialChangesetDetailView(); $detail->setChangeset($changeset); $detail->appendChild($output); $detail->setVsChangesetID($left_source); $panel = new DifferentialPrimaryPaneView(); $panel->appendChild(phutil_tag('div', array('class' => 'differential-review-stage', 'id' => 'differential-review-stage'), $detail->render())); $crumbs = $this->buildApplicationCrumbs(); $revision_id = $changeset->getDiff()->getRevisionID(); if ($revision_id) { $crumbs->addTextCrumb('D' . $revision_id, '/D' . $revision_id); } $diff_id = $changeset->getDiff()->getID(); if ($diff_id) { $crumbs->addTextCrumb(pht('Diff %d', $diff_id), $this->getApplicationURI('diff/' . $diff_id)); } $crumbs->addTextCrumb($changeset->getDisplayFilename()); $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Standalone View'))->appendChild($panel); return $this->buildApplicationPage(array($crumbs, $box), array('title' => pht('Changeset View'), 'device' => false)); }