@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Render unit results as Harbormaster unit messages

Summary:
Ref T8095. Same as D13377, but for unit results.

This is a bit rough and there's some duplication between this and unit results. I'll likely merge them later, but I think some of it is superficial since these iterations are still a little crude.

Test Plan: {F523499}

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T8095

Differential Revision: https://secure.phabricator.com/D13378

+189 -167
+2
src/__phutil_library_map__.php
··· 919 919 'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php', 920 920 'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php', 921 921 'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php', 922 + 'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php', 922 923 'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php', 923 924 'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php', 924 925 'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php', ··· 4380 4381 'HarbormasterTargetWorker' => 'HarbormasterWorker', 4381 4382 'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation', 4382 4383 'HarbormasterUIEventListener' => 'PhabricatorEventListener', 4384 + 'HarbormasterUnitPropertyView' => 'AphrontView', 4383 4385 'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4384 4386 'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation', 4385 4387 'HarbormasterWorker' => 'PhabricatorWorker',
+15 -5
src/applications/differential/customfield/DifferentialLintField.php
··· 56 56 57 57 // TODO: Look for Harbormaster messages here. 58 58 59 - 60 59 if (!$lint) { 61 60 // No Harbormaster messages, so look for legacy messages and make them 62 61 // look like modern messages. ··· 71 70 72 71 $target = new HarbormasterBuildTarget(); 73 72 foreach ($legacy_lint as $message) { 74 - $modern = HarbormasterBuildLintMessage::newFromDictionary( 75 - $target, 76 - $this->getModernLintMessageDictionary($message)); 77 - $lint[] = $modern; 73 + try { 74 + $modern = HarbormasterBuildLintMessage::newFromDictionary( 75 + $target, 76 + $this->getModernLintMessageDictionary($message)); 77 + $lint[] = $modern; 78 + } catch (Exception $ex) { 79 + // Ignore any poorly formatted messages. 80 + } 78 81 } 79 82 } 80 83 } ··· 160 163 } 161 164 162 165 private function getModernLintMessageDictionary(array $map) { 166 + // Strip out `null` values to satisfy stricter typechecks. 167 + foreach ($map as $key => $value) { 168 + if ($value === null) { 169 + unset($map[$key]); 170 + } 171 + } 172 + 163 173 // TODO: We might need to remap some stuff here? 164 174 return $map; 165 175 }
+81 -161
src/applications/differential/customfield/DifferentialUnitField.php
··· 48 48 $diff->attachProperty($key, idx($properties, $key)); 49 49 } 50 50 51 - $ustar = DifferentialRevisionUpdateHistoryView::renderDiffUnitStar($diff); 52 - $umsg = DifferentialRevisionUpdateHistoryView::getDiffUnitMessage($diff); 51 + $status = $this->renderUnitStatus($diff); 53 52 54 - $rows = array(); 53 + $unit = array(); 55 54 56 - $rows[] = array( 57 - 'style' => 'star', 58 - 'name' => $ustar, 59 - 'value' => $umsg, 60 - 'show' => true, 61 - ); 55 + // TODO: Look for Harbormaster results here. 62 56 63 - $excuse = $diff->getProperty('arc:unit-excuse'); 64 - if ($excuse) { 65 - $rows[] = array( 66 - 'style' => 'excuse', 67 - 'name' => pht('Excuse'), 68 - 'value' => phutil_escape_html_newlines($excuse), 69 - 'show' => true, 70 - ); 71 - } 57 + if (!$unit) { 58 + $legacy_unit = $diff->getProperty('arc:unit'); 59 + if ($legacy_unit) { 60 + // Show the top 100 legacy unit messages. 61 + $legacy_unit = array_slice($legacy_unit, 0, 100); 72 62 73 - $show_limit = 10; 74 - $hidden = array(); 75 - 76 - $udata = $diff->getProperty('arc:unit'); 77 - if ($udata) { 78 - $sort_map = array( 79 - ArcanistUnitTestResult::RESULT_BROKEN => 0, 80 - ArcanistUnitTestResult::RESULT_FAIL => 1, 81 - ArcanistUnitTestResult::RESULT_UNSOUND => 2, 82 - ArcanistUnitTestResult::RESULT_SKIP => 3, 83 - ArcanistUnitTestResult::RESULT_POSTPONED => 4, 84 - ArcanistUnitTestResult::RESULT_PASS => 5, 85 - ); 86 - 87 - foreach ($udata as $key => $test) { 88 - $udata[$key]['sort'] = idx($sort_map, idx($test, 'result')); 89 - } 90 - $udata = isort($udata, 'sort'); 91 - $engine = new PhabricatorMarkupEngine(); 92 - $engine->setViewer($this->getViewer()); 93 - $markup_objects = array(); 94 - foreach ($udata as $key => $test) { 95 - $userdata = idx($test, 'userdata'); 96 - if ($userdata) { 97 - if ($userdata !== false) { 98 - $userdata = str_replace("\000", '', $userdata); 99 - } 100 - $markup_object = id(new PhabricatorMarkupOneOff()) 101 - ->setContent($userdata) 102 - ->setPreserveLinebreaks(true); 103 - $engine->addObject($markup_object, 'default'); 104 - $markup_objects[$key] = $markup_object; 105 - } 106 - } 107 - $engine->process(); 108 - foreach ($udata as $key => $test) { 109 - $result = idx($test, 'result'); 110 - 111 - $default_hide = false; 112 - switch ($result) { 113 - case ArcanistUnitTestResult::RESULT_POSTPONED: 114 - case ArcanistUnitTestResult::RESULT_PASS: 115 - $default_hide = true; 116 - break; 117 - } 118 - 119 - if ($show_limit && !$default_hide) { 120 - --$show_limit; 121 - $show = true; 122 - } else { 123 - $show = false; 124 - if (empty($hidden[$result])) { 125 - $hidden[$result] = 0; 126 - } 127 - $hidden[$result]++; 128 - } 129 - 130 - $value = idx($test, 'name'); 131 - 132 - $namespace = idx($test, 'namespace'); 133 - if ($namespace) { 134 - $value = $namespace.'::'.$value; 135 - } 136 - 137 - if (!empty($test['link'])) { 138 - $value = phutil_tag( 139 - 'a', 140 - array( 141 - 'href' => $test['link'], 142 - 'target' => '_blank', 143 - ), 144 - $value); 145 - } 146 - $rows[] = array( 147 - 'style' => $this->getResultStyle($result), 148 - 'name' => ucwords($result), 149 - 'value' => $value, 150 - 'show' => $show, 151 - ); 152 - 153 - if (isset($markup_objects[$key])) { 154 - $rows[] = array( 155 - 'style' => 'details', 156 - 'value' => $engine->getOutput($markup_objects[$key], 'default'), 157 - 'show' => false, 158 - ); 159 - if (empty($hidden['details'])) { 160 - $hidden['details'] = 0; 63 + $target = new HarbormasterBuildTarget(); 64 + foreach ($legacy_unit as $message) { 65 + try { 66 + $modern = HarbormasterBuildUnitMessage::newFromDictionary( 67 + $target, 68 + $this->getModernUnitMessageDictionary($message)); 69 + $unit[] = $modern; 70 + } catch (Exception $ex) { 71 + // Just ignore it if legacy messages aren't formatted like 72 + // we expect. 161 73 } 162 - $hidden['details']++; 163 74 } 164 75 } 165 76 } 166 77 167 - $show_string = $this->renderShowString($hidden); 78 + if ($unit) { 79 + $path_map = mpull($diff->loadChangesets(), 'getID', 'getFilename'); 80 + foreach ($path_map as $path => $id) { 81 + $href = '#C'.$id.'NL'; 168 82 169 - $view = new DifferentialResultsTableView(); 170 - $view->setRows($rows); 171 - $view->setShowMoreString($show_string); 83 + // TODO: When the diff is not the right-hand-size diff, we should 84 + // ideally adjust this URI to be absolute. 172 85 173 - return $view->render(); 174 - } 86 + $path_map[$path] = $href; 87 + } 175 88 176 - private function getResultStyle($result) { 177 - $map = array( 178 - ArcanistUnitTestResult::RESULT_PASS => 'green', 179 - ArcanistUnitTestResult::RESULT_FAIL => 'red', 180 - ArcanistUnitTestResult::RESULT_SKIP => 'blue', 181 - ArcanistUnitTestResult::RESULT_BROKEN => 'red', 182 - ArcanistUnitTestResult::RESULT_UNSOUND => 'yellow', 183 - ArcanistUnitTestResult::RESULT_POSTPONED => 'blue', 184 - ); 185 - return idx($map, $result); 186 - } 187 - 188 - private function renderShowString(array $hidden) { 189 - if (!$hidden) { 190 - return null; 89 + $view = id(new HarbormasterUnitPropertyView()) 90 + ->setPathURIMap($path_map) 91 + ->setUnitMessages($unit); 92 + } else { 93 + $view = null; 191 94 } 192 95 193 - // Reorder hidden things by severity. 194 - $hidden = array_select_keys( 195 - $hidden, 196 - array( 197 - ArcanistUnitTestResult::RESULT_BROKEN, 198 - ArcanistUnitTestResult::RESULT_FAIL, 199 - ArcanistUnitTestResult::RESULT_UNSOUND, 200 - ArcanistUnitTestResult::RESULT_SKIP, 201 - ArcanistUnitTestResult::RESULT_POSTPONED, 202 - ArcanistUnitTestResult::RESULT_PASS, 203 - 'details', 204 - )) + $hidden; 205 - 206 - $noun = array( 207 - ArcanistUnitTestResult::RESULT_BROKEN => pht('Broken'), 208 - ArcanistUnitTestResult::RESULT_FAIL => pht('Failed'), 209 - ArcanistUnitTestResult::RESULT_UNSOUND => pht('Unsound'), 210 - ArcanistUnitTestResult::RESULT_SKIP => pht('Skipped'), 211 - ArcanistUnitTestResult::RESULT_POSTPONED => pht('Postponed'), 212 - ArcanistUnitTestResult::RESULT_PASS => pht('Passed'), 96 + return array( 97 + $status, 98 + $view, 213 99 ); 214 - 215 - $show = array(); 216 - foreach ($hidden as $key => $value) { 217 - if ($key == 'details') { 218 - $show[] = pht('%d Detail(s)', $value); 219 - } else { 220 - $show[] = $value.' '.idx($noun, $key); 221 - } 222 - } 223 - 224 - return pht( 225 - 'Show Full Unit Results (%s)', 226 - implode(', ', $show)); 227 100 } 228 101 229 102 public function getWarningsForDetailView() { ··· 245 118 } 246 119 247 120 return $warnings; 121 + } 122 + 123 + 124 + private function renderUnitStatus(DifferentialDiff $diff) { 125 + $colors = array( 126 + DifferentialUnitStatus::UNIT_NONE => 'grey', 127 + DifferentialUnitStatus::UNIT_OKAY => 'green', 128 + DifferentialUnitStatus::UNIT_WARN => 'yellow', 129 + DifferentialUnitStatus::UNIT_FAIL => 'red', 130 + DifferentialUnitStatus::UNIT_SKIP => 'blue', 131 + DifferentialUnitStatus::UNIT_AUTO_SKIP => 'blue', 132 + DifferentialUnitStatus::UNIT_POSTPONED => 'blue', 133 + ); 134 + $icon_color = idx($colors, $diff->getUnitStatus(), 'grey'); 135 + 136 + $message = DifferentialRevisionUpdateHistoryView::getDiffUnitMessage($diff); 137 + 138 + $excuse = $diff->getProperty('arc:unit-excuse'); 139 + if (strlen($excuse)) { 140 + $excuse = array( 141 + phutil_tag('strong', array(), pht('Excuse:')), 142 + ' ', 143 + phutil_escape_html_newlines($excuse), 144 + ); 145 + } 146 + 147 + $status = id(new PHUIStatusListView()) 148 + ->addItem( 149 + id(new PHUIStatusItemView()) 150 + ->setIcon(PHUIStatusItemView::ICON_STAR, $icon_color) 151 + ->setTarget($message) 152 + ->setNote($excuse)); 153 + 154 + return $status; 155 + } 156 + 157 + private function getModernUnitMessageDictionary(array $map) { 158 + // Strip out `null` values to satisfy stricter typechecks. 159 + foreach ($map as $key => $value) { 160 + if ($value === null) { 161 + unset($map[$key]); 162 + } 163 + } 164 + 165 + // TODO: Remap more stuff here? 166 + 167 + return $map; 248 168 } 249 169 250 170
+1 -1
src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php
··· 15 15 16 16 public static function initializeNewUnitMessage( 17 17 HarbormasterBuildTarget $build_target) { 18 - return id(new HarbormasterBuildLintMessage()) 18 + return id(new HarbormasterBuildUnitMessage()) 19 19 ->setBuildTargetPHID($build_target->getPHID()); 20 20 } 21 21
+90
src/applications/harbormaster/view/HarbormasterUnitPropertyView.php
··· 1 + <?php 2 + 3 + final class HarbormasterUnitPropertyView extends AphrontView { 4 + 5 + private $pathURIMap; 6 + private $unitMessages = array(); 7 + 8 + public function setPathURIMap(array $map) { 9 + $this->pathURIMap = $map; 10 + return $this; 11 + } 12 + 13 + public function setUnitMessages(array $messages) { 14 + assert_instances_of($messages, 'HarbormasterBuildUnitMessage'); 15 + $this->unitMessages = $messages; 16 + return $this; 17 + } 18 + 19 + public function render() { 20 + 21 + $rows = array(); 22 + $any_duration = false; 23 + foreach ($this->unitMessages as $message) { 24 + $result = $this->renderResult($message->getResult()); 25 + 26 + $duration = $message->getDuration(); 27 + if ($duration !== null) { 28 + $any_duration = true; 29 + $duration = pht('%s ms', new PhutilNumber((int)(1000 * $duration))); 30 + } 31 + 32 + $name = $message->getName(); 33 + 34 + $namespace = $message->getNamespace(); 35 + if (strlen($namespace)) { 36 + $name = $namespace.'::'.$name; 37 + } 38 + 39 + $engine = $message->getEngine(); 40 + if (strlen($engine)) { 41 + $name = $engine.' > '.$name; 42 + } 43 + 44 + $rows[] = array( 45 + $result, 46 + $duration, 47 + $name, 48 + ); 49 + } 50 + 51 + 52 + $table = id(new AphrontTableView($rows)) 53 + ->setHeaders( 54 + array( 55 + pht('Result'), 56 + pht('Time'), 57 + pht('Test'), 58 + )) 59 + ->setColumnClasses( 60 + array( 61 + null, 62 + null, 63 + 'pri wide', 64 + )) 65 + ->setColumnVisibility( 66 + array( 67 + true, 68 + $any_duration, 69 + )); 70 + 71 + return $table; 72 + } 73 + 74 + private function renderResult($result) { 75 + $names = array( 76 + ArcanistUnitTestResult::RESULT_BROKEN => pht('Broken'), 77 + ArcanistUnitTestResult::RESULT_FAIL => pht('Failed'), 78 + ArcanistUnitTestResult::RESULT_UNSOUND => pht('Unsound'), 79 + ArcanistUnitTestResult::RESULT_SKIP => pht('Skipped'), 80 + ArcanistUnitTestResult::RESULT_POSTPONED => pht('Postponed'), 81 + ArcanistUnitTestResult::RESULT_PASS => pht('Passed'), 82 + ); 83 + $result = idx($names, $result, $result); 84 + 85 + // TODO: Add some color. 86 + 87 + return $result; 88 + } 89 + 90 + }