Page MenuHomePhabricator

D11692.diff
No OneTemporary

D11692.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -67,7 +67,7 @@
'rsrc/css/application/feed/feed.css' => 'b513b5f4',
'rsrc/css/application/files/global-drag-and-drop.css' => '697324ad',
'rsrc/css/application/flag/flag.css' => '5337623f',
- 'rsrc/css/application/harbormaster/harbormaster.css' => '49d64eb4',
+ 'rsrc/css/application/harbormaster/harbormaster.css' => '661ada8b',
'rsrc/css/application/herald/herald-test.css' => '778b008e',
'rsrc/css/application/herald/herald.css' => '826075fa',
'rsrc/css/application/home/home.css' => 'e34bf140',
@@ -529,7 +529,7 @@
'font-fontawesome' => '21b0ced7',
'font-source-sans-pro' => 'f5c0ffcb',
'global-drag-and-drop-css' => '697324ad',
- 'harbormaster-css' => '49d64eb4',
+ 'harbormaster-css' => '661ada8b',
'herald-css' => '826075fa',
'herald-rule-editor' => '6e2de6f2',
'herald-test-css' => '778b008e',
diff --git a/resources/sql/autopatches/20150206.harbormasteritem.1.sql b/resources/sql/autopatches/20150206.harbormasteritem.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20150206.harbormasteritem.1.sql
@@ -0,0 +1,12 @@
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_builditem (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ buildTargetPHID VARBINARY(64) NOT NULL,
+ name VARCHAR(255) NOT NULL COLLATE {$COLLATE_TEXT},
+ type VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
+ details LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (phid),
+ UNIQUE KEY `key_type` (buildTargetPHID, type)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -874,6 +874,7 @@
'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php',
'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php',
'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php',
+ 'HarbormasterTestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterTestBuildStepImplementation.php',
'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php',
'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php',
'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php',
@@ -3939,7 +3940,10 @@
'HarbormasterBuildEngine' => 'Phobject',
'HarbormasterBuildFailureException' => 'Exception',
'HarbormasterBuildGraph' => 'AbstractDirectedGraph',
- 'HarbormasterBuildItem' => 'HarbormasterDAO',
+ 'HarbormasterBuildItem' => array(
+ 'HarbormasterDAO',
+ 'PhabricatorPolicyInterface',
+ ),
'HarbormasterBuildItemPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildItemQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildLog' => array(
@@ -4039,6 +4043,7 @@
'HarbormasterStepDeleteController' => 'HarbormasterController',
'HarbormasterStepEditController' => 'HarbormasterController',
'HarbormasterTargetWorker' => 'HarbormasterWorker',
+ 'HarbormasterTestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation',
'HarbormasterUIEventListener' => 'PhabricatorEventListener',
'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
--- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
@@ -188,6 +188,7 @@
$targets[] = $target_box;
+ $targets[] = $this->buildUnitItems($build, $build_target);
$targets[] = $this->buildLog($build, $build_target);
}
@@ -236,6 +237,209 @@
return $list;
}
+ private function buildUnitItems(
+ HarbormasterBuild $build,
+ HarbormasterBuildTarget $build_target) {
+
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $items = id(new HarbormasterBuildItemQuery())
+ ->setViewer($viewer)
+ ->withTypes(array(HarbormasterBuildItem::TYPE_UNIT))
+ ->withBuildTargetPHIDs(array($build_target->getPHID()))
+ ->execute();
+
+ if (count($items) === 0) {
+ return null;
+ }
+
+ $rows = array();
+
+ $this->requireResource('differential-core-view-css');
+ $this->requireResource('differential-revision-history-css');
+
+ $show_limit = 10;
+ $hidden = array();
+ $totals = array();
+
+ $udata = mpull($items, 'getDetails');
+ if ($udata) {
+ $sort_map = array(
+ ArcanistUnitTestResult::RESULT_BROKEN => 0,
+ ArcanistUnitTestResult::RESULT_FAIL => 1,
+ ArcanistUnitTestResult::RESULT_UNSOUND => 2,
+ ArcanistUnitTestResult::RESULT_SKIP => 3,
+ ArcanistUnitTestResult::RESULT_POSTPONED => 4,
+ ArcanistUnitTestResult::RESULT_PASS => 5,
+ );
+
+ foreach ($udata as $key => $test) {
+ $udata[$key]['sort'] = idx($sort_map, idx($test, 'result'));
+ }
+ $udata = isort($udata, 'sort');
+ $engine = new PhabricatorMarkupEngine();
+ $engine->setViewer($this->getViewer());
+ $markup_objects = array();
+ foreach ($udata as $key => $test) {
+ $userdata = idx($test, 'userData');
+ if ($userdata) {
+ if ($userdata !== false) {
+ $userdata = str_replace("\000", '', $userdata);
+ }
+ $markup_object = id(new PhabricatorMarkupOneOff())
+ ->setContent($userdata)
+ ->setPreserveLinebreaks(true);
+ $engine->addObject($markup_object, 'default');
+ $markup_objects[$key] = $markup_object;
+ }
+ }
+ $engine->process();
+ foreach ($udata as $key => $test) {
+ $result = idx($test, 'result');
+
+ $default_hide = false;
+ switch ($result) {
+ case ArcanistUnitTestResult::RESULT_POSTPONED:
+ $default_hide = true;
+ break;
+ }
+
+ if ($show_limit && !$default_hide) {
+ --$show_limit;
+ $show = true;
+ } else {
+ $show = false;
+ if (empty($hidden[$result])) {
+ $hidden[$result] = 0;
+ }
+ $hidden[$result]++;
+ }
+
+ if (empty($totals[$result])) {
+ $totals[$result] = 0;
+ }
+ $totals[$result]++;
+ if (empty($totals['total'])) {
+ $totals['total'] = 0;
+ }
+ $totals['total']++;
+
+ $value = idx($test, 'name');
+
+ $namespace = idx($test, 'namespace');
+ if ($namespace) {
+ $value = $namespace.'::'.$value;
+ }
+
+ if (!empty($test['link'])) {
+ $value = phutil_tag(
+ 'a',
+ array(
+ 'href' => $test['link'],
+ 'target' => '_blank',
+ ),
+ $value);
+ }
+ $rows[] = array(
+ 'style' => $this->getUnitResultStyle($result),
+ 'name' => ucwords($result),
+ 'value' => $value,
+ 'show' => $show,
+ );
+
+ if (isset($markup_objects[$key])) {
+ $rows[] = array(
+ 'style' => 'details',
+ 'value' => $engine->getOutput($markup_objects[$key], 'default'),
+ 'show' => false,
+ );
+ if (empty($hidden['details'])) {
+ $hidden['details'] = 0;
+ }
+ $hidden['details']++;
+ }
+ }
+ }
+
+ $show_string = $this->renderUnitShowString($hidden);
+
+ $view = new DifferentialResultsTableView();
+ $view->setRows($rows);
+ $view->setShowMoreString($show_string);
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader(pht('Test Results'))
+ ->setSubheader($this->getUnitSummary($totals))
+ ->setUser($viewer);
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeader($header)
+ ->setForm($view);
+
+ return $box;
+ }
+
+ private function getUnitResultStyle($result) {
+ $map = array(
+ ArcanistUnitTestResult::RESULT_PASS => 'green',
+ ArcanistUnitTestResult::RESULT_FAIL => 'red',
+ ArcanistUnitTestResult::RESULT_SKIP => 'blue',
+ ArcanistUnitTestResult::RESULT_BROKEN => 'red',
+ ArcanistUnitTestResult::RESULT_UNSOUND => 'yellow',
+ ArcanistUnitTestResult::RESULT_POSTPONED => 'blue',
+ );
+ return idx($map, $result);
+ }
+
+ private function getUnitSummary(array $hidden) {
+ // Reorder hidden things by severity.
+ $hidden = array_select_keys(
+ $hidden,
+ array(
+ ArcanistUnitTestResult::RESULT_BROKEN,
+ ArcanistUnitTestResult::RESULT_FAIL,
+ ArcanistUnitTestResult::RESULT_UNSOUND,
+ ArcanistUnitTestResult::RESULT_SKIP,
+ ArcanistUnitTestResult::RESULT_POSTPONED,
+ ArcanistUnitTestResult::RESULT_PASS,
+ 'details',
+ 'total',
+ )) + $hidden;
+
+ $noun = array(
+ ArcanistUnitTestResult::RESULT_BROKEN => 'Broken',
+ ArcanistUnitTestResult::RESULT_FAIL => 'Failed',
+ ArcanistUnitTestResult::RESULT_UNSOUND => 'Unsound',
+ ArcanistUnitTestResult::RESULT_SKIP => 'Skipped',
+ ArcanistUnitTestResult::RESULT_POSTPONED => 'Postponed',
+ ArcanistUnitTestResult::RESULT_PASS => 'Passed',
+ );
+
+ $show = array();
+ foreach ($hidden as $key => $value) {
+ if ($key == 'details') {
+ $show[] = pht('%d Detail(s)', $value);
+ } else if ($key == 'total') {
+ $show[] = pht('%d Total', $value);
+ } else {
+ $show[] = $value.' '.idx($noun, $key);
+ }
+ }
+
+ return implode(', ', $show);
+ }
+
+ private function renderUnitShowString(array $hidden) {
+ if (!$hidden) {
+ return null;
+ }
+
+ $summary = $this->getUnitSummary($hidden);
+
+ return 'Show Full Unit Results ('.$summary.')';
+ }
+
private function buildLog(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target) {
diff --git a/src/applications/harbormaster/query/HarbormasterBuildItemQuery.php b/src/applications/harbormaster/query/HarbormasterBuildItemQuery.php
--- a/src/applications/harbormaster/query/HarbormasterBuildItemQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildItemQuery.php
@@ -5,6 +5,8 @@
private $ids;
private $phids;
+ private $buildTargetPHIDs;
+ private $types;
public function withIDs(array $ids) {
$this->ids = $ids;
@@ -16,6 +18,16 @@
return $this;
}
+ public function withBuildTargetPHIDs(array $phids) {
+ $this->buildTargetPHIDs = $phids;
+ return $this;
+ }
+
+ public function withTypes(array $types) {
+ $this->types = $types;
+ return $this;
+ }
+
protected function loadPage() {
$table = new HarbormasterBuildItem();
$conn_r = $table->establishConnection('r');
@@ -31,6 +43,31 @@
return $table->loadAllFromArray($data);
}
+ protected function willFilterPage(array $page) {
+ $build_targets = array();
+
+ $build_target_phids = array_filter(mpull($page, 'getBuildTargetPHID'));
+ if ($build_target_phids) {
+ $build_targets = id(new HarbormasterBuildTargetQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($build_target_phids)
+ ->setParentQuery($this)
+ ->execute();
+ $build_targets = mpull($build_targets, null, 'getPHID');
+ }
+
+ foreach ($page as $key => $build_log) {
+ $build_target_phid = $build_log->getBuildTargetPHID();
+ if (empty($build_targets[$build_target_phid])) {
+ unset($page[$key]);
+ continue;
+ }
+ $build_log->attachBuildTarget($build_targets[$build_target_phid]);
+ }
+
+ return $page;
+ }
+
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
@@ -48,6 +85,20 @@
$this->phids);
}
+ if ($this->buildTargetPHIDs) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'buildTargetPHID in (%Ls)',
+ $this->buildTargetPHIDs);
+ }
+
+ if ($this->types) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'type in (%Ls)',
+ $this->types);
+ }
+
$where[] = $this->buildPagingClause($conn_r);
return $this->formatWhereClause($where);
diff --git a/src/applications/harbormaster/step/HarbormasterTestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterTestBuildStepImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/step/HarbormasterTestBuildStepImplementation.php
@@ -0,0 +1,139 @@
+<?php
+
+final class HarbormasterTestBuildStepImplementation
+ extends HarbormasterBuildStepImplementation {
+
+ public function getName() {
+ return pht('Run Tests');
+ }
+
+ public function getGenericDescription() {
+ return pht('Run \'arc unit\' on Drydock host.');
+ }
+
+ public function getDescription() {
+ return pht(
+ 'Run \'arc unit\' on host %s.',
+ $this->formatSettingForDescription('hostartifact'));
+ }
+
+ public function execute(
+ HarbormasterBuild $build,
+ HarbormasterBuildTarget $build_target) {
+
+ $settings = $this->getSettings();
+ $variables = $build_target->getVariables();
+
+ $artifact = $build->loadArtifact($settings['hostartifact']);
+
+ $lease = $artifact->loadDrydockLease();
+
+ $interface = $lease->getInterface('command');
+
+ $future = $interface->getExecFuture(
+ 'cd %s && arc unit --output json-realtime --everything',
+ $settings['directory']);
+
+ $log_stderr = $build->createLog($build_target, 'remote', 'stderr');
+
+ $start_stderr = $log_stderr->start();
+
+ $build_update = 5;
+
+ $stdout_buffer = '';
+ $err = 0;
+
+ $futures = new FutureIterator(array($future));
+ foreach ($futures->setUpdateInterval(1) as $key => $future_iter) {
+ if ($future_iter !== null) {
+ list($err) = $future->resolve();
+ } else {
+ // Check to see if we should abort.
+ if ($build_update <= 0) {
+ $build->reload();
+ if ($this->shouldAbort($build, $build_target)) {
+ $future->resolveKill();
+ throw new HarbormasterBuildAbortedException();
+ } else {
+ $build_update = 5;
+ }
+ } else {
+ $build_update -= 1;
+ }
+ }
+
+ list($stdout, $stderr) = $future->read();
+ $stdout_buffer .= $stdout;
+ $log_stderr->append($stderr);
+ $future->discardBuffers();
+
+ $lines = phutil_split_lines($stdout_buffer);
+ if (strlen($stdout_buffer) > 0 &&
+ $stdout_buffer[strlen($stdout_buffer) - 1] !== "\n") {
+
+ $stdout_buffer = $lines[count($lines) - 1];
+ array_pop($lines);
+ } else {
+ $stdout_buffer = '';
+ }
+
+ foreach ($lines as $line) {
+ if (strlen($line) > strlen('##arc-unit## ') &&
+ substr($line, 0, strlen('##arc-unit## ')) === '##arc-unit## ') {
+ // We have output from 'arc unit'.
+ $data = phutil_json_decode(substr($line, strlen('##arc-unit## ')));
+
+ $name = idx($data, 'name');
+ if (idx($data, 'namespace') !== null) {
+ $name = idx($data, 'namespace').'::'.$name;
+ }
+
+ $item = HarbormasterBuildItem::initializeNewBuildItem(
+ $build_target,
+ HarbormasterBuildItem::TYPE_UNIT);
+ $item->setName($name);
+ $item->setDetails($data);
+ $item->save();
+ }
+ }
+
+ if ($future_iter !== null) {
+ break;
+ }
+ }
+
+ $log_stderr->finalize($start_stderr);
+
+ if ($err) {
+ throw new HarbormasterBuildFailureException();
+ }
+ }
+
+ public function getArtifactInputs() {
+ return array(
+ array(
+ 'name' => pht('Run on Host'),
+ 'key' => $this->getSetting('hostartifact'),
+ 'type' => HarbormasterBuildArtifact::TYPE_HOST,
+ ),
+ );
+ }
+
+ public function getFieldSpecifications() {
+ return array(
+ 'directory' => array(
+ 'name' => pht('Directory'),
+ 'type' => 'text',
+ 'required' => true,
+ 'caption' => pht(
+ 'The relative directory to run \'arc unit\' in.'),
+ ),
+ 'hostartifact' => array(
+ 'name' => pht('Host'),
+ 'type' => 'text',
+ 'required' => true,
+ ),
+ );
+ }
+
+}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildItem.php b/src/applications/harbormaster/storage/build/HarbormasterBuildItem.php
--- a/src/applications/harbormaster/storage/build/HarbormasterBuildItem.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildItem.php
@@ -1,19 +1,85 @@
<?php
-final class HarbormasterBuildItem extends HarbormasterDAO {
+final class HarbormasterBuildItem extends HarbormasterDAO
+ implements PhabricatorPolicyInterface {
protected $name;
+ protected $buildTargetPHID;
+ protected $type;
+ protected $details = array();
+
+ private $buildTarget = self::ATTACHABLE;
+
+ const TYPE_UNIT = 'unit';
+ const TYPE_LINT = 'lint';
+
+ public static function initializeNewBuildItem(
+ HarbormasterBuildTarget $build_target,
+ $type) {
+ return id(new HarbormasterBuildItem())
+ ->setBuildTargetPHID($build_target->getPHID())
+ ->setType($type);
+ }
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
- self::CONFIG_NO_TABLE => true,
+ self::CONFIG_SERIALIZATION => array(
+ 'details' => self::SERIALIZATION_JSON,
+ ),
+ self::CONFIG_COLUMN_SCHEMA => array(
+ 'type' => 'text32',
+ 'name' => 'text255',
+ ),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_type' => array(
+ 'columns' => array('buildTargetPHID', 'type'),
+ ),
+ ),
) + parent::getConfiguration();
}
+ public function attachBuildTarget(HarbormasterBuildTarget $build_target) {
+ $this->buildTarget = $build_target;
+ return $this;
+ }
+
+ public function getBuildTarget() {
+ return $this->assertAttached($this->buildTarget);
+ }
+
public function generatePHID() {
return PhabricatorPHID::generateNewPHID(
HarbormasterBuildItemPHIDType::TYPECONST);
}
+ public function getResult() {
+ return idx($this->getDetails(), 'result');
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ return $this->getBuildTarget()->getPolicy($capability);
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return $this->getBuildTarget()->hasAutomaticCapability(
+ $capability,
+ $viewer);
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return pht(
+ 'Users must be able to see a buildable to see its artifacts.');
+ }
+
}
diff --git a/webroot/rsrc/css/application/harbormaster/harbormaster.css b/webroot/rsrc/css/application/harbormaster/harbormaster.css
--- a/webroot/rsrc/css/application/harbormaster/harbormaster.css
+++ b/webroot/rsrc/css/application/harbormaster/harbormaster.css
@@ -25,3 +25,16 @@
padding: 12px;
color: {$darkgreytext};
}
+
+/*
+ * Doesn't appear to be any way of setting custom
+ * classes on PHUI boxes to limit the scope of these
+ * styles, so just assume that if harbormaster-css is
+ * included, then we're rendering on a Harbormaster
+ * build view page.
+ */
+
+div.phui-box table.differential-results-table {
+ width: 100%;
+ line-height: 20px;
+}

File Metadata

Mime Type
text/plain
Expires
Thu, May 9, 8:20 PM (3 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6276648
Default Alt Text
D11692.diff (19 KB)

Event Timeline