<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[213788] trunk/Websites/perf.webkit.org</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/213788">213788</a></dd>
<dt>Author</dt> <dd>dewei_zhu@apple.com</dd>
<dt>Date</dt> <dd>2017-03-13 02:10:29 -0700 (Mon, 13 Mar 2017)</dd>
</dl>
<h3>Log Message</h3>
<pre>Add the ability to report a commit with sub-commits.
https://bugs.webkit.org/show_bug.cgi?id=168962
Reviewed by Ryosuke Niwa.
Introduce 'commit_ownerships' which records ownership between commits.
On existing production server, run ```
CREATE TABLE commit_ownerships (
commit_owner integer NOT NULL REFERENCES commits ON DELETE CASCADE,
commit_ownee integer NOT NULL REFERENCES commits ON DELETE CASCADE,
PRIMARY KEY (commit_owner, commit_ownee)
);
ALTER TABLE repositories RENAME repository_parent TO repository_owner;
ALTER TABLE repositories DROP repository_name_must_be_unique;
CREATE UNIQUE INDEX repository_name_owner_unique_index ON repositories (repository_owner, repository_name) WHERE repository_owner IS NOT NULL;
CREATE UNIQUE INDEX repository_name_unique_index ON repositories (repository_name) WHERE repository_owner IS NULL;
``` to update database.
Add unit-tests to cover this change.
* init-database.sql:
* public/api/report-commits.php:
* public/include/commit-log-fetcher.php:
* public/include/db.php:
* public/include/manifest-generator.php:
* public/include/report-processor.php:
* public/v3/models/repository.js:
(Repository):
(Repository.prototype.owner):
* server-tests/admin-reprocess-report-tests.js:
(addBuilderForReport.simpleReportWithRevisions.0.then):
(then):
* server-tests/api-manifest.js:
(then):
* server-tests/api-report-commits-tests.js:
(addSlaveForReport.sameRepositoryNameInSubCommitAndMajorCommit.then):
(then):
(addSlaveForReport.systemVersionCommitWithSubcommits.then):
(addSlaveForReport.multipleSystemVersionCommitsWithSubcommits.then):
(addSlaveForReport.systemVersionCommitWithEmptySubcommits.then):
(addSlaveForReport.systemVersionCommitAndSubcommitWithTimestamp.then):
* tools/js/database.js:</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkWebsitesperfwebkitorgChangeLog">trunk/Websites/perf.webkit.org/ChangeLog</a></li>
<li><a href="#trunkWebsitesperfwebkitorginitdatabasesql">trunk/Websites/perf.webkit.org/init-database.sql</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicapireportcommitsphp">trunk/Websites/perf.webkit.org/public/api/report-commits.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicincludecommitlogfetcherphp">trunk/Websites/perf.webkit.org/public/include/commit-log-fetcher.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicincludedbphp">trunk/Websites/perf.webkit.org/public/include/db.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicincludemanifestgeneratorphp">trunk/Websites/perf.webkit.org/public/include/manifest-generator.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicincludereportprocessorphp">trunk/Websites/perf.webkit.org/public/include/report-processor.php</a></li>
<li><a href="#trunkWebsitesperfwebkitorgpublicv3modelsrepositoryjs">trunk/Websites/perf.webkit.org/public/v3/models/repository.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgservertestsadminreprocessreporttestsjs">trunk/Websites/perf.webkit.org/server-tests/admin-reprocess-report-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgservertestsapimanifestjs">trunk/Websites/perf.webkit.org/server-tests/api-manifest.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgservertestsapireportcommitstestsjs">trunk/Websites/perf.webkit.org/server-tests/api-report-commits-tests.js</a></li>
<li><a href="#trunkWebsitesperfwebkitorgtoolsjsdatabasejs">trunk/Websites/perf.webkit.org/tools/js/database.js</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkWebsitesperfwebkitorgChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/ChangeLog (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/ChangeLog        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/ChangeLog        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -1,3 +1,47 @@
</span><ins>+2017-03-13 Dewei Zhu <dewei_zhu@apple.com>
+
+ Add the ability to report a commit with sub-commits.
+ https://bugs.webkit.org/show_bug.cgi?id=168962
+
+ Reviewed by Ryosuke Niwa.
+
+ Introduce 'commit_ownerships' which records ownership between commits.
+ On existing production server, run ```
+ CREATE TABLE commit_ownerships (
+ commit_owner integer NOT NULL REFERENCES commits ON DELETE CASCADE,
+ commit_ownee integer NOT NULL REFERENCES commits ON DELETE CASCADE,
+ PRIMARY KEY (commit_owner, commit_ownee)
+ );
+ ALTER TABLE repositories RENAME repository_parent TO repository_owner;
+ ALTER TABLE repositories DROP repository_name_must_be_unique;
+ CREATE UNIQUE INDEX repository_name_owner_unique_index ON repositories (repository_owner, repository_name) WHERE repository_owner IS NOT NULL;
+ CREATE UNIQUE INDEX repository_name_unique_index ON repositories (repository_name) WHERE repository_owner IS NULL;
+ ``` to update database.
+ Add unit-tests to cover this change.
+
+ * init-database.sql:
+ * public/api/report-commits.php:
+ * public/include/commit-log-fetcher.php:
+ * public/include/db.php:
+ * public/include/manifest-generator.php:
+ * public/include/report-processor.php:
+ * public/v3/models/repository.js:
+ (Repository):
+ (Repository.prototype.owner):
+ * server-tests/admin-reprocess-report-tests.js:
+ (addBuilderForReport.simpleReportWithRevisions.0.then):
+ (then):
+ * server-tests/api-manifest.js:
+ (then):
+ * server-tests/api-report-commits-tests.js:
+ (addSlaveForReport.sameRepositoryNameInSubCommitAndMajorCommit.then):
+ (then):
+ (addSlaveForReport.systemVersionCommitWithSubcommits.then):
+ (addSlaveForReport.multipleSystemVersionCommitsWithSubcommits.then):
+ (addSlaveForReport.systemVersionCommitWithEmptySubcommits.then):
+ (addSlaveForReport.systemVersionCommitAndSubcommitWithTimestamp.then):
+ * tools/js/database.js:
+
</ins><span class="cx"> 2017-03-07 Ryosuke Niwa <rniwa@webkit.org>
</span><span class="cx">
</span><span class="cx"> Update ReadMe.md to use directory format for backing up & restoring the database
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorginitdatabasesql"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/init-database.sql (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/init-database.sql        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/init-database.sql        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -7,6 +7,7 @@
</span><span class="cx"> DROP TABLE IF EXISTS committers CASCADE;
</span><span class="cx"> DROP TABLE IF EXISTS commits CASCADE;
</span><span class="cx"> DROP TABLE IF EXISTS build_commits CASCADE;
</span><ins>+DROP TABLE IF EXISTS commit_ownerships CASCADE;
</ins><span class="cx"> DROP TABLE IF EXISTS build_slaves CASCADE;
</span><span class="cx"> DROP TABLE IF EXISTS builders CASCADE;
</span><span class="cx"> DROP TABLE IF EXISTS repositories CASCADE;
</span><span class="lines">@@ -38,12 +39,16 @@
</span><span class="cx">
</span><span class="cx"> CREATE TABLE repositories (
</span><span class="cx"> repository_id serial PRIMARY KEY,
</span><del>- repository_parent integer REFERENCES repositories ON DELETE CASCADE,
</del><ins>+ repository_owner integer REFERENCES repositories ON DELETE CASCADE,
</ins><span class="cx"> repository_name varchar(64) NOT NULL,
</span><span class="cx"> repository_url varchar(1024),
</span><del>- repository_blame_url varchar(1024),
- CONSTRAINT repository_name_must_be_unique UNIQUE(repository_parent, repository_name));
</del><ins>+ repository_blame_url varchar(1024));
</ins><span class="cx">
</span><ins>+CREATE UNIQUE INDEX repository_name_owner_unique_index ON repositories (repository_owner, repository_name)
+ WHERE repository_owner IS NOT NULL;
+CREATE UNIQUE INDEX repository_name_unique_index ON repositories (repository_name)
+ WHERE repository_owner IS NULL;
+
</ins><span class="cx"> CREATE TABLE bug_trackers (
</span><span class="cx"> tracker_id serial PRIMARY KEY,
</span><span class="cx"> tracker_name varchar(64) NOT NULL,
</span><span class="lines">@@ -98,6 +103,12 @@
</span><span class="cx"> CREATE INDEX commit_time_index ON commits(commit_time);
</span><span class="cx"> CREATE INDEX commit_order_index ON commits(commit_order);
</span><span class="cx">
</span><ins>+CREATE TABLE commit_ownerships (
+ commit_owner integer NOT NULL REFERENCES commits ON DELETE CASCADE,
+ commit_owned integer NOT NULL REFERENCES commits ON DELETE CASCADE,
+ PRIMARY KEY (commit_owner, commit_owned)
+);
+
</ins><span class="cx"> CREATE TABLE build_commits (
</span><span class="cx"> commit_build integer NOT NULL REFERENCES builds ON DELETE CASCADE,
</span><span class="cx"> build_commit integer NOT NULL REFERENCES commits ON DELETE CASCADE,
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicapireportcommitsphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/api/report-commits.php (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/api/report-commits.php        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/public/api/report-commits.php        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -2,7 +2,8 @@
</span><span class="cx">
</span><span class="cx"> require('../include/json-header.php');
</span><span class="cx">
</span><del>-function main($post_data) {
</del><ins>+function main($post_data)
+{
</ins><span class="cx"> $db = new Database;
</span><span class="cx"> if (!$db->connect())
</span><span class="cx"> exit_with_error('DatabaseConnectionFailure');
</span><span class="lines">@@ -25,50 +26,27 @@
</span><span class="cx">
</span><span class="cx"> $db->begin_transaction();
</span><span class="cx"> foreach ($commits as $commit_info) {
</span><del>- $repository_id = $db->select_or_insert_row('repositories', 'repository', array('name' => $commit_info['repository']));
</del><ins>+ $repository_id = $db->select_or_insert_repository_row($commit_info['repository'], NULL);
</ins><span class="cx"> if (!$repository_id) {
</span><span class="cx"> $db->rollback_transaction();
</span><span class="cx"> exit_with_error('FailedToInsertRepository', array('commit' => $commit_info));
</span><span class="cx"> }
</span><ins>+ $owner_commit_id = insert_commit($db, $commit_info, $repository_id, NULL);
+ if (!array_key_exists('subCommits', $commit_info))
+ continue;
</ins><span class="cx">
</span><del>- $author = array_get($commit_info, 'author');
- $committer_id = NULL;
- if ($author) {
- $account = array_get($author, 'account');
- $committer_query = array('repository' => $repository_id, 'account' => $account);
- $committer_data = $committer_query;
- $name = array_get($author, 'name');
- if ($name)
- $committer_data['name'] = $name;
- $committer_id = $db->update_or_insert_row('committers', 'committer', $committer_query, $committer_data);
- if (!$committer_id) {
</del><ins>+ foreach($commit_info['subCommits'] as $sub_commit_repository_name => $sub_commit_info) {
+ if (array_key_exists('time', $sub_commit_info)) {
</ins><span class="cx"> $db->rollback_transaction();
</span><del>- exit_with_error('FailedToInsertCommitter', array('committer' => $committer_data));
</del><ins>+ exit_with_error('SubCommitShouldNotContainTimestamp', array('commit' => $sub_commit_info));
</ins><span class="cx"> }
</span><del>- }
-
- $previous_commit_revision = array_get($commit_info, 'previousCommit');
- $previous_commit_id = NULL;
- if ($previous_commit_revision) {
- $previous_commit = $db->select_first_row('commits', 'commit', array('repository' => $repository_id, 'revision' => $previous_commit_revision));
- if (!$previous_commit) {
</del><ins>+ $sub_commit_repository_id = $db->select_or_insert_repository_row($sub_commit_repository_name, $repository_id);
+ if (!$sub_commit_repository_id) {
</ins><span class="cx"> $db->rollback_transaction();
</span><del>- exit_with_error('FailedToFindPreviousCommit', array('commit' => $commit_info));
</del><ins>+ exit_with_error('FailedToInsertRepository', array('commit' => $sub_commit_info));
</ins><span class="cx"> }
</span><del>- $previous_commit_id = $previous_commit['commit_id'];
</del><ins>+ insert_commit($db, $sub_commit_info, $sub_commit_repository_id, $owner_commit_id);
</ins><span class="cx"> }
</span><del>-
- $data = array(
- 'repository' => $repository_id,
- 'revision' => $commit_info['revision'],
- 'previous_commit' => $previous_commit_id,
- 'order' => array_get($commit_info, 'order'),
- 'time' => array_get($commit_info, 'time'),
- 'committer' => $committer_id,
- 'message' => array_get($commit_info, 'message'),
- 'reported' => true,
- );
- $db->update_or_insert_row('commits', 'commit', array('repository' => $repository_id, 'revision' => $data['revision']), $data);
</del><span class="cx"> }
</span><span class="cx"> $db->commit_transaction();
</span><span class="cx">
</span><span class="lines">@@ -75,6 +53,52 @@
</span><span class="cx"> exit_with_success();
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+function insert_commit($db, $commit_info, $repository_id, $owner_commit_id)
+{
+ $author = array_get($commit_info, 'author');
+ $committer_id = NULL;
+ if ($author) {
+ $account = array_get($author, 'account');
+ $committer_query = array('repository' => $repository_id, 'account' => $account);
+ $committer_data = $committer_query;
+ $name = array_get($author, 'name');
+ if ($name)
+ $committer_data['name'] = $name;
+ $committer_id = $db->update_or_insert_row('committers', 'committer', $committer_query, $committer_data);
+ if (!$committer_id) {
+ $db->rollback_transaction();
+ exit_with_error('FailedToInsertCommitter', array('committer' => $committer_data));
+ }
+ }
+
+ $previous_commit_revision = array_get($commit_info, 'previousCommit');
+ $previous_commit_id = NULL;
+ if ($previous_commit_revision) {
+ $previous_commit = $db->select_first_row('commits', 'commit', array('repository' => $repository_id, 'revision' => $previous_commit_revision));
+ if (!$previous_commit) {
+ $db->rollback_transaction();
+ exit_with_error('FailedToFindPreviousCommit', array('commit' => $commit_info));
+ }
+ $previous_commit_id = $previous_commit['commit_id'];
+ }
+
+ $data = array(
+ 'repository' => $repository_id,
+ 'revision' => $commit_info['revision'],
+ 'previous_commit' => $previous_commit_id,
+ 'order' => array_get($commit_info, 'order'),
+ 'time' => array_get($commit_info, 'time'),
+ 'committer' => $committer_id,
+ 'message' => array_get($commit_info, 'message'),
+ 'reported' => true,
+ );
+ $inserted_commit_id = $db->update_or_insert_row('commits', 'commit', array('repository' => $repository_id, 'revision' => $data['revision']), $data);
+
+ if ($owner_commit_id)
+ $db->select_or_insert_row('commit_ownerships', 'commit', array('owner' => $owner_commit_id, 'owned' => $inserted_commit_id), NULL, '*');
+ return $inserted_commit_id;
+}
+
</ins><span class="cx"> main($HTTP_RAW_POST_DATA);
</span><span class="cx">
</span><span class="cx"> ?>
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicincludecommitlogfetcherphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/include/commit-log-fetcher.php (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/include/commit-log-fetcher.php        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/public/include/commit-log-fetcher.php        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -27,10 +27,10 @@
</span><span class="cx">
</span><span class="cx"> function repository_id_from_name($name)
</span><span class="cx"> {
</span><del>- $repository_row = $this->db->select_first_row('repositories', 'repository', array('name' => $name));
</del><ins>+ $repository_row = $this->db->query_and_fetch_all('SELECT repository_id FROM repositories WHERE repository_name = $1 AND repository_owner is NULL', array($name));
</ins><span class="cx"> if (!$repository_row)
</span><span class="cx"> return NULL;
</span><del>- return $repository_row['repository_id'];
</del><ins>+ return $repository_row[0]['repository_id'];
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> function fetch_between($repository_id, $first, $second, $keyword = NULL) {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicincludedbphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/include/db.php (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/include/db.php        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/public/include/db.php        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -188,6 +188,26 @@
</span><span class="cx"> return $rows ? ($returning == '*' ? $rows[0] : $rows[0][$returning_column_name]) : NULL;
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ // FIXME: Should improve _select_update_or_insert_row to handle the NULL column case.
+ function select_or_insert_repository_row($repository_name, $repository_owner_id)
+ {
+ $result = NULL;
+ if ($repository_owner_id == NULL) {
+ $result = $this->query_and_fetch_all('INSERT INTO repositories (repository_name) SELECT $1
+ WHERE NOT EXISTS (SELECT repository_id FROM repositories WHERE repository_name = $2 AND repository_owner IS NULL) RETURNING repository_id',
+ array($repository_name, $repository_name));
+ if (!$result)
+ $result = $this->query_and_fetch_all('SELECT repository_id FROM repositories WHERE repository_name = $1 AND repository_owner IS NULL', array($repository_name));
+ } else {
+ $result = $this->query_and_fetch_all('INSERT INTO repositories (repository_name, repository_owner) SELECT $1, $2
+ WHERE NOT EXISTS (SELECT repository_id FROM repositories WHERE (repository_name, repository_owner) = ($3, $4)) RETURNING repository_id',
+ array($repository_name, $repository_owner_id, $repository_name, $repository_owner_id));
+ if (!$result)
+ $result = $this->query_and_fetch_all('SELECT repository_id FROM repositories WHERE (repository_name, repository_owner) = ($1, $2)', array($repository_name, $repository_owner_id));
+ }
+ return $result ? $result[0]['repository_id'] : NULL;
+ }
+
</ins><span class="cx"> function select_first_row($table, $prefix, $params, $order_by = NULL) {
</span><span class="cx"> return $this->select_first_or_last_row($table, $prefix, $params, $order_by, FALSE);
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicincludemanifestgeneratorphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/include/manifest-generator.php (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/include/manifest-generator.php        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/public/include/manifest-generator.php        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -138,6 +138,7 @@
</span><span class="cx"> 'name' => $row['repository_name'],
</span><span class="cx"> 'url' => $row['repository_url'],
</span><span class="cx"> 'blameUrl' => $row['repository_blame_url'],
</span><ins>+ 'owner'=> $row['repository_owner'],
</ins><span class="cx"> 'hasReportedCommits' => in_array($row['repository_id'], $repositories_with_commit));
</span><span class="cx"> }
</span><span class="cx">
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicincludereportprocessorphp"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/include/report-processor.php (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/include/report-processor.php        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/public/include/report-processor.php        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -150,7 +150,7 @@
</span><span class="cx">
</span><span class="cx">
</span><span class="cx"> foreach ($revisions as $repository_name => $revision_data) {
</span><del>- $repository_id = $this->db->select_or_insert_row('repositories', 'repository', array('name' => $repository_name));
</del><ins>+ $repository_id = $this->db->select_or_insert_repository_row($repository_name, NULL);
</ins><span class="cx"> if (!$repository_id)
</span><span class="cx"> $this->exit_with_error('FailedToInsertRepository', array('name' => $repository_name));
</span><span class="cx">
</span><span class="lines">@@ -309,7 +309,7 @@
</span><span class="cx"> foreach ($aggregators_and_values as $aggregator_and_values) {
</span><span class="cx"> if ($aggregator_and_values['aggregator'] == $aggregator) {
</span><span class="cx"> $values = $aggregator_and_values['values'];
</span><del>- break;
</del><ins>+ break;
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> if (!$values) {
</span><span class="lines">@@ -404,7 +404,7 @@
</span><span class="cx"> }
</span><span class="cx"> $iteration_value = $iteration_value[1];
</span><span class="cx"> }
</span><del>- array_push($flattened_value, $iteration_value);
</del><ins>+ array_push($flattened_value, $iteration_value);
</ins><span class="cx"> }
</span><span class="cx"> }
</span><span class="cx"> $this->values_to_commit[$i]['mean'] = $this->aggregate_values('Arithmetic', $flattened_value);
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgpublicv3modelsrepositoryjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/public/v3/models/repository.js (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/public/v3/models/repository.js        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/public/v3/models/repository.js        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -7,6 +7,7 @@
</span><span class="cx"> this._url = object.url;
</span><span class="cx"> this._blameUrl = object.blameUrl;
</span><span class="cx"> this._hasReportedCommits = object.hasReportedCommits;
</span><ins>+ this._owner = object.owner;
</ins><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> hasUrlForRevision() { return !!this._url; }
</span><span class="lines">@@ -21,6 +22,11 @@
</span><span class="cx"> return (this._blameUrl || '').replace(/\$1/g, from).replace(/\$2/g, to);
</span><span class="cx"> }
</span><span class="cx">
</span><ins>+ owner()
+ {
+ return this._owner;
+ }
+
</ins><span class="cx"> static sortByNamePreferringOnesWithURL(repositories)
</span><span class="cx"> {
</span><span class="cx"> return repositories.sort(function (a, b) {
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsadminreprocessreporttestsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/server-tests/admin-reprocess-report-tests.js (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/admin-reprocess-report-tests.js        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/server-tests/admin-reprocess-report-tests.js        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -24,6 +24,44 @@
</span><span class="cx"> },
</span><span class="cx"> }];
</span><span class="cx">
</span><ins>+ const simpleReportWithRevisions = [{
+ "buildNumber": "1986",
+ "buildTime": "2013-02-28T10:12:03",
+ "builderName": "someBuilder",
+ "builderPassword": "somePassword",
+ "platform": "Mountain Lion",
+ "tests": {
+ "test": {
+ "metrics": {"FrameRate": { "current": [[1, 2, 3], [4, 5, 6]] }}
+ },
+ },
+ "revisions": {
+ "WebKit": {
+ "timestamp": "2017-03-01T09:38:44.826833Z",
+ "revision": "213214"
+ }
+ }
+ }];
+
+ it("should still create new repository when repository ownerships are different", function (done) {
+ let db = TestServer.database();
+ addBuilderForReport(simpleReportWithRevisions[0]).then(function () {
+ return db.insert('repositories', {'name': 'WebKit', 'owner': 1});
+ }).then(function () {
+ return TestServer.remoteAPI().postJSON('/api/report/', simpleReportWithRevisions);
+ }).then(function (response) {
+ assert.equal(response['status'], 'OK');
+ return db.selectRows('repositories', {'name': 'WebKit'});
+ }).then(function (repositories) {
+ assert.equal(repositories.length, 2);
+ const webkitRepsitoryId = repositories[0].owner == 1 ? repositories[1].id : repositories[0].id;
+ return db.selectRows('commits', {'revision': '213214', 'repository': webkitRepsitoryId});
+ }).then(function (result) {
+ assert(result.length, 1);
+ done();
+ }).catch(done);
+ });
+
</ins><span class="cx"> it("should add build", function (done) {
</span><span class="cx"> let db = TestServer.database();
</span><span class="cx"> let reportId;
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsapimanifestjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/server-tests/api-manifest.js (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/api-manifest.js        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/server-tests/api-manifest.js        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -282,6 +282,7 @@
</span><span class="cx"> Promise.all([
</span><span class="cx"> db.insert('repositories', {id: 11, name: 'WebKit', url: 'https://trac.webkit.org/$1'}),
</span><span class="cx"> db.insert('repositories', {id: 9, name: 'OS X'}),
</span><ins>+ db.insert('repositories', {id: 101, name: 'WebKit', owner: 9, url: 'https://trac.webkit.org/$1'}),
</ins><span class="cx"> db.insert('build_triggerables', {id: 200, name: 'build.webkit.org'}),
</span><span class="cx"> db.insert('build_triggerables', {id: 201, name: 'ios-build.webkit.org'}),
</span><span class="cx"> db.insert('tests', {id: 1, name: 'SomeTest'}),
</span><span class="lines">@@ -311,6 +312,11 @@
</span><span class="cx"> assert.equal(webkit.name(), 'WebKit');
</span><span class="cx"> assert.equal(webkit.urlForRevision(123), 'https://trac.webkit.org/123');
</span><span class="cx">
</span><ins>+ let osWebkit1 = Repository.findById(101);
+ assert.equal(osWebkit1.name(), 'WebKit');
+ assert.equal(osWebkit1.owner(), 9);
+ assert.equal(osWebkit1.urlForRevision(123), 'https://trac.webkit.org/123');
+
</ins><span class="cx"> let osx = Repository.findById(9);
</span><span class="cx"> assert.equal(osx.name(), 'OS X');
</span><span class="cx">
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgservertestsapireportcommitstestsjs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/server-tests/api-report-commits-tests.js (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/server-tests/api-report-commits-tests.js        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/server-tests/api-report-commits-tests.js        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -287,4 +287,334 @@
</span><span class="cx"> }).catch(done);
</span><span class="cx"> });
</span><span class="cx">
</span><ins>+ const sameRepositoryNameInSubCommitAndMajorCommit = {
+ "slaveName": "someSlave",
+ "slavePassword": "somePassword",
+ "commits": [
+ {
+ "repository": "OSX",
+ "revision": "Sierra16D32",
+ "order": 1,
+ "subCommits": {
+ "WebKit": {
+ "revision": "141978",
+ "author": {"name": "Commit Queue", "account": "commit-queue@webkit.org"},
+ "message": "WebKit Commit",
+ },
+ "JavaScriptCore": {
+ "revision": "141978",
+ "author": {"name": "Mikhail Pozdnyakov", "account": "mikhail.pozdnyakov@intel.com"},
+ "message": "JavaScriptCore commit",
+ }
+ }
+ },
+ {
+ "repository": "WebKit",
+ "revision": "141978",
+ "author": {"name": "Commit Queue", "account": "commit-queue@webkit.org"},
+ "message": "WebKit Commit",
+ }
+ ]
+ }
+
+ it("should distinguish between repositories with the asme name but with a different owner.", function (done) {
+ const db = TestServer.database();
+ addSlaveForReport(sameRepositoryNameInSubCommitAndMajorCommit).then(function () {
+ return TestServer.remoteAPI().postJSON('/api/report-commits/', sameRepositoryNameInSubCommitAndMajorCommit);
+ }).then(function (response) {
+ assert.equal(response['status'], 'OK');
+ return db.selectRows('repositories', {'name': 'WebKit'});
+ }).then(function (result) {
+ assert.equal(result.length, 2);
+ let osWebKit = result[0];
+ let webkitRepository = result[1];
+ assert.notEqual(osWebKit.id, webkitRepository.id);
+ assert.equal(osWebKit.name, webkitRepository.name);
+ assert.equal(webkitRepository.owner, null);
+ done();
+ })
+ });
+
+ const systemVersionCommitWithSubcommits = {
+ "slaveName": "someSlave",
+ "slavePassword": "somePassword",
+ "commits": [
+ {
+ "repository": "OSX",
+ "revision": "Sierra16D32",
+ "order": 1,
+ "subCommits": {
+ "WebKit": {
+ "revision": "141978",
+ "author": {"name": "Commit Queue", "account": "commit-queue@webkit.org"},
+ "message": "WebKit Commit",
+ },
+ "JavaScriptCore": {
+ "revision": "141978",
+ "author": {"name": "Mikhail Pozdnyakov", "account": "mikhail.pozdnyakov@intel.com"},
+ "message": "JavaScriptCore commit",
+ }
+ }
+ }
+ ]
+ }
+
+ it("should accept inserting one commit with some sub commits", function (done) {
+ const db = TestServer.database();
+ addSlaveForReport(systemVersionCommitWithSubcommits).then(function () {
+ return TestServer.remoteAPI().postJSON('/api/report-commits/', systemVersionCommitWithSubcommits);
+ }).then(function (response) {
+ assert.equal(response['status'], 'OK');
+ return Promise.all([db.selectRows('commits', {'revision': 'Sierra16D32'}),
+ db.selectRows('commits', {'message': 'WebKit Commit'}),
+ db.selectRows('commits', {'message': 'JavaScriptCore commit'}),
+ db.selectRows('repositories', {'name': 'OSX'}),
+ db.selectRows('repositories', {'name': "WebKit"}),
+ db.selectRows('repositories', {'name': 'JavaScriptCore'})])
+ }).then(function (result) {
+ assert.equal(result.length, 6);
+
+ assert.equal(result[0].length, 1);
+ const osxCommit = result[0][0];
+ assert.notEqual(osxCommit, null);
+
+ assert.equal(result[1].length, 1);
+ const webkitCommit = result[1][0];
+ assert.notEqual(webkitCommit, null);
+
+ assert.equal(result[2].length, 1);
+ const jscCommit = result[2][0];
+ assert.notEqual(jscCommit, null);
+
+ assert.equal(result[3].length, 1);
+ const osxRepository = result[3][0];
+ assert.notEqual(osxRepository, null);
+
+ assert.equal(result[4].length, 1);
+ const webkitRepository = result[4][0];
+ assert.notEqual(webkitRepository, null);
+
+ assert.equal(result[5].length, 1);
+ const jscRepository = result[5][0];
+ assert.notEqual(jscRepository, null);
+
+ assert.equal(osxCommit.repository, osxRepository.id);
+ assert.equal(webkitCommit.repository, webkitRepository.id);
+ assert.equal(jscCommit.repository, jscRepository.id);
+ assert.equal(osxRepository.owner, null);
+ assert.equal(webkitRepository.owner, osxRepository.id);
+ assert.equal(jscRepository.owner, osxRepository.id);
+
+ return Promise.all([db.selectRows('commit_ownerships', {'owner': osxCommit.id, 'owned': webkitCommit.id}, {'sortBy': 'owner'}),
+ db.selectRows('commit_ownerships', {'owner': osxCommit.id, 'owned': jscCommit.id}, {'sortBy': 'owner'}),
+ db.selectRows('commits', {'repository': webkitRepository.id})]);
+ }).then(function (result) {
+ assert.equal(result.length, 3);
+
+ assert.equal(result[0].length, 1);
+ const ownerCommitForWebKitCommit = result[0][0];
+ assert.notEqual(ownerCommitForWebKitCommit, null);
+
+ assert.equal(result[1].length, 1);
+ const ownerCommitForJSCCommit = result[1][0];
+ assert.notEqual(ownerCommitForJSCCommit, null);
+
+ assert.equal(result[2].length, 1);
+ done();
+ }).catch(done);
+ })
+
+ const multipleSystemVersionCommitsWithSubcommits = {
+ "slaveName": "someSlave",
+ "slavePassword": "somePassword",
+ "commits": [
+ {
+ "repository": "OSX",
+ "revision": "Sierra16D32",
+ "order": 2,
+ "subCommits": {
+ "WebKit": {
+ "revision": "141978",
+ "author": {"name": "Commit Queue", "account": "commit-queue@webkit.org"},
+ "message": "WebKit Commit",
+ },
+ "JavaScriptCore": {
+ "revision": "141978",
+ "author": {"name": "Mikhail Pozdnyakov", "account": "mikhail.pozdnyakov@intel.com"},
+ "message": "JavaScriptCore commit",
+ }
+ }
+ },
+ {
+ "repository": "OSX",
+ "revision": "Sierra16C67",
+ "order": 1,
+ "subCommits": {
+ "WebKit": {
+ "revision": "141978",
+ "author": {"name": "Commit Queue", "account": "commit-queue@webkit.org"},
+ "message": "WebKit Commit",
+ },
+ "JavaScriptCore": {
+ "revision": "141999",
+ "author": {"name": "Mikhail Pozdnyakov", "account": "mikhail.pozdnyakov@intel.com"},
+ "message": "new JavaScriptCore commit",
+ }
+ }
+ }
+ ]
+ };
+
+ it("should accept inserting multiple commits with multiple sub-commits", function (done) {
+ const db = TestServer.database();
+ addSlaveForReport(multipleSystemVersionCommitsWithSubcommits).then(function () {
+ return TestServer.remoteAPI().postJSON('/api/report-commits/', multipleSystemVersionCommitsWithSubcommits);
+ }).then(function (response) {
+ assert.equal(response['status'], 'OK');
+ return Promise.all([db.selectRows('commits', {'revision': 'Sierra16D32'}),
+ db.selectRows('commits', {'revision': 'Sierra16C67'}),
+ db.selectRows('commits', {'message': 'WebKit Commit'}),
+ db.selectRows('commits', {'message': 'JavaScriptCore commit'}),
+ db.selectRows('commits', {'message': 'new JavaScriptCore commit'}),
+ db.selectRows('repositories', {'name': 'OSX'}),
+ db.selectRows('repositories', {'name': "WebKit"}),
+ db.selectRows('repositories', {'name': 'JavaScriptCore'})])
+ }).then(function (result) {
+ assert.equal(result.length, 8);
+
+ assert.equal(result[0].length, 1);
+ const osxCommit0 = result[0][0];
+ assert.notEqual(osxCommit0, null);
+
+ assert.equal(result[1].length, 1);
+ const osxCommit1 = result[1][0];
+ assert.notEqual(osxCommit1, null);
+
+ assert.equal(result[2].length, 1);
+ const webkitCommit = result[2][0];
+ assert.notEqual(webkitCommit, null);
+
+ assert.equal(result[3].length, 1);
+ const jscCommit0 = result[3][0];
+ assert.notEqual(jscCommit0, null);
+
+ assert.equal(result[4].length, 1);
+ const jscCommit1 = result[4][0];
+ assert.notEqual(jscCommit1, null);
+
+ assert.equal(result[5].length, 1)
+ const osxRepository = result[5][0];
+ assert.notEqual(osxRepository, null);
+ assert.equal(osxRepository.owner, null);
+
+ assert.equal(result[6].length, 1)
+ const webkitRepository = result[6][0];
+ assert.equal(webkitRepository.owner, osxRepository.id);
+
+ assert.equal(result[7].length, 1);
+ const jscRepository = result[7][0];
+ assert.equal(jscRepository.owner, osxRepository.id);
+
+ assert.equal(osxCommit0.repository, osxRepository.id);
+ assert.equal(osxCommit1.repository, osxRepository.id);
+ assert.equal(webkitCommit.repository, webkitRepository.id);
+ assert.equal(jscCommit0.repository, jscRepository.id);
+ assert.equal(jscCommit1.repository, jscRepository.id);
+ assert.equal(osxRepository.owner, null);
+ assert.equal(webkitRepository.owner, osxRepository.id);
+ assert.equal(jscRepository.owner, osxRepository.id);
+
+ return Promise.all([db.selectRows('commit_ownerships', {'owner': osxCommit0.id, 'owned': webkitCommit.id}, {'sortBy': 'owner'}),
+ db.selectRows('commit_ownerships', {'owner': osxCommit1.id, 'owned': webkitCommit.id}, {'sortBy': 'owner'}),
+ db.selectRows('commit_ownerships', {'owner': osxCommit0.id, 'owned': jscCommit0.id}, {'sortBy': 'owner'}),
+ db.selectRows('commit_ownerships', {'owner': osxCommit1.id, 'owned': jscCommit1.id}, {'sortBy': 'owner'}),
+ db.selectRows('commits', {'repository': webkitRepository.id})]);
+ }).then(function (result) {
+ assert.equal(result.length, 5);
+
+ assert.equal(result[0].length, 1);
+ const ownerCommitForWebKitCommit0 = result[0][0];
+ assert.notEqual(ownerCommitForWebKitCommit0, null);
+
+ assert.equal(result[1].length, 1);
+ const ownerCommitForWebKitCommit1 = result[1][0];
+ assert.notEqual(ownerCommitForWebKitCommit1, null);
+
+ assert.equal(result[2].length, 1);
+ const ownerCommitForJSCCommit0 = result[2][0];
+ assert.notEqual(ownerCommitForJSCCommit0, null);
+
+ assert.equal(result[3].length, 1);
+ const ownerCommitForJSCCommit1 = result[3][0];
+ assert.notEqual(ownerCommitForJSCCommit1, null);
+
+ assert.equal(result[4].length, 1);
+
+ done();
+ }).catch(done);
+ });
+
+ const systemVersionCommitWithEmptySubcommits = {
+ "slaveName": "someSlave",
+ "slavePassword": "somePassword",
+ "commits": [
+ {
+ "repository": "OSX",
+ "revision": "Sierra16D32",
+ "order": 1,
+ "subCommits": {
+ }
+ }
+ ]
+ }
+
+ it("should accept inserting one commit with no sub commits", function (done) {
+ const db = TestServer.database();
+ addSlaveForReport(systemVersionCommitWithEmptySubcommits).then(function () {
+ return TestServer.remoteAPI().postJSON('/api/report-commits/', systemVersionCommitWithEmptySubcommits);
+ }).then(function (response) {
+ assert.equal(response['status'], 'OK');
+ return Promise.all([db.selectAll('commits'), db.selectAll('repositories'), db.selectAll('commit_ownerships', 'owner')]);
+ }).then(function (result) {
+ let commits = result[0];
+ let repositories = result[1];
+ let commit_ownerships = result[2];
+ assert.equal(commits.length, 1);
+ assert.equal(repositories.length, 1);
+ assert.equal(commits[0].repository, repositories[0].id);
+ assert.equal(repositories[0].name, 'OSX');
+ assert.equal(commit_ownerships.length, 0);
+ done();
+ }).catch(done);
+ });
+
+ const systemVersionCommitAndSubcommitWithTimestamp = {
+ "slaveName": "someSlave",
+ "slavePassword": "somePassword",
+ "commits": [
+ {
+ "repository": "OSX",
+ "revision": "Sierra16D32",
+ "order": 1,
+ "subCommits": {
+ "WebKit": {
+ "revision": "141978",
+ "time": "2013-02-06T08:55:20.9Z",
+ "author": {"name": "Commit Queue", "account": "commit-queue@webkit.org"},
+ "message": "WebKit Commit",
+ }
+ }
+ }
+ ]
+ }
+
+ it("should reject inserting one commit with sub commits that contains timestamp", function (done) {
+ const db = TestServer.database();
+ addSlaveForReport(systemVersionCommitAndSubcommitWithTimestamp).then(function () {
+ return TestServer.remoteAPI().postJSON('/api/report-commits/', systemVersionCommitAndSubcommitWithTimestamp);
+ }).then(function (response) {
+ assert.equal(response['status'], 'SubCommitShouldNotContainTimestamp');
+ done();
+ }).catch(done);
+ });
</ins><span class="cx"> });
</span></span></pre></div>
<a id="trunkWebsitesperfwebkitorgtoolsjsdatabasejs"></a>
<div class="modfile"><h4>Modified: trunk/Websites/perf.webkit.org/tools/js/database.js (213787 => 213788)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Websites/perf.webkit.org/tools/js/database.js        2017-03-13 08:01:16 UTC (rev 213787)
+++ trunk/Websites/perf.webkit.org/tools/js/database.js        2017-03-13 09:10:29 UTC (rev 213788)
</span><span class="lines">@@ -136,6 +136,7 @@
</span><span class="cx"> 'builds': 'build',
</span><span class="cx"> 'builders': 'builder',
</span><span class="cx"> 'commits': 'commit',
</span><ins>+ 'commit_ownerships': 'commit',
</ins><span class="cx"> 'committers': 'committer',
</span><span class="cx"> 'test_configurations': 'config',
</span><span class="cx"> 'test_metrics': 'metric',
</span></span></pre>
</div>
</div>
</body>
</html>