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));
 }