Title: [225898] trunk/Websites/perf.webkit.org
Revision
225898
Author
dewei_...@apple.com
Date
2017-12-14 01:49:37 -0800 (Thu, 14 Dec 2017)

Log Message

Add a test freshness page.
https://bugs.webkit.org/show_bug.cgi?id=180126

Reviewed by Ryosuke Niwa.

Added a page to show freshness of a test.
The test freshness page reports on the same set of tests as the one shown in the summary page.
Use a logistic function to evaluate the freshness of the data points.
This function has the desired property which increase dramatically when it close to the center of the graph.
'acceptableLastDataPointDurationInHour' configs the center of the graph.

* public/include/manifest-generator.php:
* public/v3/components/freshness-indicator.js: Added.
(FreshnessIndicator): A cell of the test freshness table, color will transit from green to red.
(FreshnessIndicator.prototype.update): Update the the data point information and triggers
the cell to re-render if anything changes.
(FreshnessIndicator.prototype._renderIndicator): Re-render the indicator.
(FreshnessIndicator.prototype.render): Render the box color base on a logistic function.
(FreshnessIndicator.prototype._createIndicator):
(FreshnessIndicator.htmlTemplate):
(FreshnessIndicator.cssTemplate):
* public/v3/index.html:
* public/v3/main.js: Added test freshness page.
(main):
* public/v3/models/build-request.js: Refactored waitingTime function to make it reusable.
(BuildRequest.formatTimeInterval): Format time interval in million seconds to more user friendly text.
(BuildRequest.prototype.waitingTime):
* public/v3/pages/test-freshness-page.js: Added.
(TestFreshnessPage):
(TestFreshnessPage.prototype.name):
(TestFreshnessPage.prototype._loadConfig): Load config from summary page configurations.
(TestFreshnessPage.prototype.open):
(TestFreshnessPage.prototype._fetchTestResults):
(TestFreshnessPage.prototype.render):
(TestFreshnessPage.prototype._renderTable):
(TestFreshnessPage.prototype._isValidPlatformMetricCombination): Return whether a platform
and metric combination is valid.
(TestFreshnessPage.prototype._constructTableCell):
(TestFreshnessPage.cssTemplate):
(TestFreshnessPage.prototype.routeName):
* server-tests/api-manifest-tests.js: Added 'warningHourBaseline' so that we can config the
parameter of logistic funciton.
* unit-tests/build-request-tests.js: Added unit tests for formatTimeInterval.

Modified Paths

Added Paths

Diff

Modified: trunk/Websites/perf.webkit.org/ChangeLog (225897 => 225898)


--- trunk/Websites/perf.webkit.org/ChangeLog	2017-12-14 09:39:59 UTC (rev 225897)
+++ trunk/Websites/perf.webkit.org/ChangeLog	2017-12-14 09:49:37 UTC (rev 225898)
@@ -1,3 +1,49 @@
+2017-12-13  Dewei Zhu  <dewei_...@apple.com>
+
+        Add a test freshness page.
+        https://bugs.webkit.org/show_bug.cgi?id=180126
+
+        Reviewed by Ryosuke Niwa.
+
+        Added a page to show freshness of a test.
+        The test freshness page reports on the same set of tests as the one shown in the summary page.
+        Use a logistic function to evaluate the freshness of the data points.
+        This function has the desired property which increase dramatically when it close to the center of the graph.
+        'acceptableLastDataPointDurationInHour' configs the center of the graph.
+
+        * public/include/manifest-generator.php:
+        * public/v3/components/freshness-indicator.js: Added.
+        (FreshnessIndicator): A cell of the test freshness table, color will transit from green to red.
+        (FreshnessIndicator.prototype.update): Update the the data point information and triggers
+        the cell to re-render if anything changes.
+        (FreshnessIndicator.prototype._renderIndicator): Re-render the indicator.
+        (FreshnessIndicator.prototype.render): Render the box color base on a logistic function.
+        (FreshnessIndicator.prototype._createIndicator):
+        (FreshnessIndicator.htmlTemplate):
+        (FreshnessIndicator.cssTemplate):
+        * public/v3/index.html:
+        * public/v3/main.js: Added test freshness page.
+        (main):
+        * public/v3/models/build-request.js: Refactored waitingTime function to make it reusable.
+        (BuildRequest.formatTimeInterval): Format time interval in million seconds to more user friendly text.
+        (BuildRequest.prototype.waitingTime):
+        * public/v3/pages/test-freshness-page.js: Added.
+        (TestFreshnessPage):
+        (TestFreshnessPage.prototype.name):
+        (TestFreshnessPage.prototype._loadConfig): Load config from summary page configurations.
+        (TestFreshnessPage.prototype.open):
+        (TestFreshnessPage.prototype._fetchTestResults):
+        (TestFreshnessPage.prototype.render):
+        (TestFreshnessPage.prototype._renderTable):
+        (TestFreshnessPage.prototype._isValidPlatformMetricCombination): Return whether a platform
+        and metric combination is valid.
+        (TestFreshnessPage.prototype._constructTableCell):
+        (TestFreshnessPage.cssTemplate):
+        (TestFreshnessPage.prototype.routeName):
+        * server-tests/api-manifest-tests.js: Added 'warningHourBaseline' so that we can config the
+        parameter of logistic funciton.
+        * unit-tests/build-request-tests.js: Added unit tests for formatTimeInterval.
+
 2017-11-02  Dewei Zhu  <dewei_...@apple.com>
 
         Add platform argument for syncing script.

Modified: trunk/Websites/perf.webkit.org/public/include/manifest-generator.php (225897 => 225898)


--- trunk/Websites/perf.webkit.org/public/include/manifest-generator.php	2017-12-14 09:39:59 UTC (rev 225897)
+++ trunk/Websites/perf.webkit.org/public/include/manifest-generator.php	2017-12-14 09:49:37 UTC (rev 225898)
@@ -44,6 +44,7 @@
             'dashboards' => (object)config('dashboards'),
             'summaryPages' => config('summaryPages'),
             'fileUploadSizeLimit' => config('uploadFileLimitInMB', 0) * 1024 * 1024,
+            'testAgeToleranceInHours' => config('testAgeToleranceInHours'),
         );
 
         $this->manifest['elapsedTime'] = (microtime(true) - $start_time) * 1000;

Added: trunk/Websites/perf.webkit.org/public/v3/components/freshness-indicator.js (0 => 225898)


--- trunk/Websites/perf.webkit.org/public/v3/components/freshness-indicator.js	                        (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/components/freshness-indicator.js	2017-12-14 09:49:37 UTC (rev 225898)
@@ -0,0 +1,79 @@
+class FreshnessIndicator extends ComponentBase {
+    constructor(lastDataPointDuration, testAgeTolerance, summary, url)
+    {
+        super('freshness-indicator');
+        this._lastDataPointDuration = lastDataPointDuration;
+        this._summary = summary;
+        this._testAgeTolerance = testAgeTolerance;
+        this._url = url;
+
+        this._renderIndicatorLazily = new LazilyEvaluatedFunction(this._renderIndicator.bind(this));
+    }
+
+    update(lastDataPointDuration, testAgeTolerance, summary, url)
+    {
+        this._lastDataPointDuration = lastDataPointDuration;
+        this._summary = summary;
+        this._testAgeTolerance = testAgeTolerance;
+        this._url = url;
+        this.enqueueToRender();
+    }
+
+    render()
+    {
+        super.render();
+        this._renderIndicatorLazily.evaluate(this._lastDataPointDuration, this._testAgeTolerance, this._summary, this._url);
+
+    }
+
+    _renderIndicator(lastDataPointDuration, testAgeTolerance, summary, url)
+    {
+        const element = ComponentBase.createElement;
+        if (!lastDataPointDuration) {
+            this.renderReplace(this.content('container'), new SpinnerIcon);
+            return;
+        }
+
+        const hoursSinceLastDataPoint = this._lastDataPointDuration / 3600 / 1000;
+        const testAgeToleranceInHours = testAgeTolerance / 3600 / 1000;
+        const rating = 1 / (1 + Math.exp(Math.log(1.2) * (hoursSinceLastDataPoint - testAgeToleranceInHours)));
+        const hue = Math.round(120 * rating);
+        const brightness = Math.round(30 + 50 * rating);
+        const indicator = element('a', {id: 'cell', title: summary, href: url});
+
+        indicator.style.backgroundColor = `hsl(${hue}, 100%, ${brightness}%)`;
+        this.renderReplace(this.content('container'), indicator);
+    }
+
+    static htmlTemplate()
+    {
+        return `<div id='container'></div>`;
+    }
+
+    static  cssTemplate()
+    {
+        return `
+            div {
+                height: 1.8rem;
+                width: 1.8rem;
+                padding-top: 0.1rem;
+            }
+            a {
+                display: block;
+                height:1.6rem;
+                width:1.6rem;
+                margin: 0.1rem;
+                padding: 0;
+            }
+
+            a:hover {
+                height: 1.8rem;
+                width: 1.8rem;
+                margin: 0rem;
+                padding: 0;
+            }`;
+    }
+}
+
+
+ComponentBase.defineElement('freshness-indicator', FreshnessIndicator);
\ No newline at end of file

Modified: trunk/Websites/perf.webkit.org/public/v3/index.html (225897 => 225898)


--- trunk/Websites/perf.webkit.org/public/v3/index.html	2017-12-14 09:39:59 UTC (rev 225897)
+++ trunk/Websites/perf.webkit.org/public/v3/index.html	2017-12-14 09:49:37 UTC (rev 225898)
@@ -98,6 +98,7 @@
         <script src=""
         <script src=""
         <script src=""
+        <script src=""
 
         <script src=""
         <script src=""
@@ -117,6 +118,7 @@
         <script src=""
         <script src=""
         <script src=""
+        <script src=""
 
         <script src=""
     </template>

Modified: trunk/Websites/perf.webkit.org/public/v3/main.js (225897 => 225898)


--- trunk/Websites/perf.webkit.org/public/v3/main.js	2017-12-14 09:39:59 UTC (rev 225897)
+++ trunk/Websites/perf.webkit.org/public/v3/main.js	2017-12-14 09:49:37 UTC (rev 225898)
@@ -47,8 +47,10 @@
         var buildRequestQueuePage = new BuildRequestQueuePage();
         buildRequestQueuePage.setParentPage(analysisCategoryPage);
 
+        const testHealthPage = new TestFreshnessPage(manifest.summaryPages, manifest.testAgeToleranceInHours);
+
         var heading = new Heading(manifest.siteTitle);
-        heading.addPageGroup(summaryPages.concat([chartsPage, analysisCategoryPage]));
+        heading.addPageGroup(summaryPages.concat([chartsPage, analysisCategoryPage, testHealthPage]));
 
         heading.setTitle(manifest.siteTitle);
         heading.addPageGroup(dashboardPages);
@@ -61,6 +63,7 @@
         router.addPage(analysisTaskPage);
         router.addPage(buildRequestQueuePage);
         router.addPage(analysisCategoryPage);
+        router.addPage(testHealthPage);
         for (var page of dashboardPages)
             router.addPage(page);
 

Modified: trunk/Websites/perf.webkit.org/public/v3/models/build-request.js (225897 => 225898)


--- trunk/Websites/perf.webkit.org/public/v3/models/build-request.js	2017-12-14 09:39:59 UTC (rev 225897)
+++ trunk/Websites/perf.webkit.org/public/v3/models/build-request.js	2017-12-14 09:49:37 UTC (rev 225898)
@@ -84,9 +84,9 @@
     buildId() { return this._buildId; }
     createdAt() { return this._createdAt; }
 
-    waitingTime(referenceTime)
-    {
-        var units = [
+    static formatTimeInterval(intervalInMillionSeconds) {
+        let intervalInSeconds = intervalInMillionSeconds / 1000;
+        const units = [
             {unit: 'week', length: 7 * 24 * 3600},
             {unit: 'day', length: 24 * 3600},
             {unit: 'hour', length: 3600},
@@ -93,34 +93,38 @@
             {unit: 'minute', length: 60},
         ];
 
-        var diff = (referenceTime - this.createdAt()) / 1000;
 
-        var indexOfFirstSmallEnoughUnit = units.length - 1;
-        for (var i = 0; i < units.length; i++) {
-            if (diff > 1.5 * units[i].length) {
+        let indexOfFirstSmallEnoughUnit = units.length - 1;
+        for (let i = 0; i < units.length; i++) {
+            if (intervalInSeconds > 1.5 * units[i].length) {
                 indexOfFirstSmallEnoughUnit = i;
                 break;
             }
         }
 
-        var label = '';
-        var lastUnit = false;
-        for (var i = indexOfFirstSmallEnoughUnit; !lastUnit; i++) {
+        let label = '';
+        let lastUnit = false;
+        for (let i = indexOfFirstSmallEnoughUnit; !lastUnit; i++) {
             lastUnit = i == indexOfFirstSmallEnoughUnit + 1 || i == units.length - 1;
-            var length = units[i].length;
-            var valueForUnit = lastUnit ? Math.round(diff / length) : Math.floor(diff / length);
+            const length = units[i].length;
+            const valueForUnit = lastUnit ? Math.round(intervalInSeconds / length) : Math.floor(intervalInSeconds / length);
 
-            var unit = units[i].unit + (valueForUnit == 1 ? '' : 's');
+            const unit = units[i].unit + (valueForUnit == 1 ? '' : 's');
             if (label)
                 label += ' ';
             label += `${valueForUnit} ${unit}`;
 
-            diff = diff - valueForUnit * length;
+            intervalInSeconds = intervalInSeconds - valueForUnit * length;
         }
 
         return label;
     }
 
+    waitingTime(referenceTime)
+    {
+        return BuildRequest.formatTimeInterval(referenceTime - this.createdAt());
+    }
+
     static fetchForTriggerable(triggerable)
     {
         return RemoteAPI.getJSONWithStatus('/api/build-requests/' + triggerable).then(function (data) {

Added: trunk/Websites/perf.webkit.org/public/v3/pages/test-freshness-page.js (0 => 225898)


--- trunk/Websites/perf.webkit.org/public/v3/pages/test-freshness-page.js	                        (rev 0)
+++ trunk/Websites/perf.webkit.org/public/v3/pages/test-freshness-page.js	2017-12-14 09:49:37 UTC (rev 225898)
@@ -0,0 +1,230 @@
+
+class TestFreshnessPage extends PageWithHeading {
+    constructor(summaryPageConfiguration, testAgeToleranceInHours)
+    {
+        super('test-freshness', null);
+        this._testAgeTolerance = (testAgeToleranceInHours || 24) * 3600 * 1000;
+        this._timeDuration = this._testAgeTolerance * 2;
+        this._excludedConfigurations = {};
+        this._lastDataPointByConfiguration = null;
+        this._indicatorByConfiguration = null;
+        this._renderTableLazily = new LazilyEvaluatedFunction(this._renderTable.bind(this));
+
+        this._loadConfig(summaryPageConfiguration);
+    }
+
+    name() { return 'Test-Freshness'; }
+
+    _loadConfig(summaryPageConfiguration)
+    {
+        const platformIdSet = new Set;
+        const metricIdSet = new Set;
+
+        for (const config of summaryPageConfiguration) {
+            for (const platformGroup of config.platformGroups) {
+                for (const platformId of platformGroup.platforms)
+                    platformIdSet.add(platformId);
+            }
+
+            for (const metricGroup of config.metricGroups) {
+                for (const subgroup of metricGroup.subgroups) {
+                    for (const metricId of subgroup.metrics)
+                        metricIdSet.add(metricId);
+                }
+            }
+
+            const excludedConfigs = config.excludedConfigurations;
+            for (const platform in excludedConfigs) {
+                if (platform in this._excludedConfigurations)
+                    this._excludedConfigurations[platform] = this._excludedConfigurations[platform].concat(excludedConfigs[platform]);
+                else
+                    this._excludedConfigurations[platform] = excludedConfigs[platform];
+            }
+        }
+        this._platforms = [...platformIdSet].map((platformId) => Platform.findById(platformId));
+        this._metrics = [...metricIdSet].map((metricId) => Metric.findById(metricId));
+    }
+
+    open(state)
+    {
+        this._fetchTestResults();
+        super.open(state);
+    }
+
+    _fetchTestResults()
+    {
+        this._measurementSetFetchTime = Date.now();
+        this._lastDataPointByConfiguration = new Map;
+
+        const startTime = this._measurementSetFetchTime - this._timeDuration;
+
+        for (const platform of this._platforms) {
+            const lastDataPointByMetric = new Map;
+            this._lastDataPointByConfiguration.set(platform, lastDataPointByMetric);
+
+            for (const metric of this._metrics) {
+                if (!this._isValidPlatformMetricCombination(platform, metric, this._excludedConfigurations))
+                    continue;
+
+                const measurementSet = MeasurementSet.findSet(platform.id(), metric.id(), platform.lastModified(metric));
+                measurementSet.fetchBetween(startTime, this._measurementSetFetchTime).then(() => {
+                    const currentTimeSeries = measurementSet.fetchedTimeSeries('current', false, false);
+
+                    let timeForLastDataPoint = startTime;
+                    if (currentTimeSeries.lastPoint())
+                        timeForLastDataPoint = currentTimeSeries.lastPoint().time;
+
+                    lastDataPointByMetric.set(metric, {time: timeForLastDataPoint, hasCurrentDataPoint: !!currentTimeSeries.lastPoint()});
+                    this.enqueueToRender();
+                });
+            }
+        }
+    }
+
+    render()
+    {
+        super.render();
+
+        this._renderTableLazily.evaluate(this._platforms, this._metrics);
+
+        for (const [platform, lastDataPointByMetric] of this._lastDataPointByConfiguration.entries()) {
+            for (const [metric, lastDataPoint] of lastDataPointByMetric.entries()) {
+                const timeDuration = this._measurementSetFetchTime - lastDataPoint.time;
+                const timeDurationSummaryPrefix = lastDataPoint.hasCurrentDataPoint ? '' : 'More than ';
+                const timeDurationSummary = BuildRequest.formatTimeInterval(timeDuration);
+                const testLabel = `"${metric.test().fullName()}" for "${platform.name()}"`;
+                const summary = `${timeDurationSummaryPrefix}${timeDurationSummary} since last data point on ${testLabel}`;
+                const url = "" ChartsPage.createStateForDashboardItem(platform.id(), metric.id(),
+                    this._measurementSetFetchTime - this._timeDuration));
+
+                const indicator = this._indicatorByConfiguration.get(platform).get(metric);
+                indicator.update(timeDuration, this._testAgeTolerance, summary, url);
+            }
+        }
+    }
+
+    _renderTable(platforms, metrics)
+    {
+        const element = ComponentBase.createElement;
+        const tableBodyElement = [];
+        const tableHeadElements = [element('th',  {class: 'table-corner'}, 'Platform \\ Test')];
+
+        for (const metric of metrics)
+            tableHeadElements.push(element('th', {class: 'diagonal-header'}, element('div', metric.test().fullName())));
+
+        this._indicatorByConfiguration = new Map;
+        for (const platform of platforms) {
+            const indicatorByMetric = new Map;
+            this._indicatorByConfiguration.set(platform, indicatorByMetric);
+            tableBodyElement.push(element('tr',
+                [element('th', platform.label()), ...metrics.map((metric) => this._constructTableCell(platform, metric, indicatorByMetric))]));
+        }
+
+        this.renderReplace(this.content('test-health'), [element('thead', tableHeadElements), element('tbody', tableBodyElement)]);
+    }
+
+    _isValidPlatformMetricCombination(platform, metric)
+    {
+        return !(this._excludedConfigurations && this._excludedConfigurations[platform.id()]
+            && this._excludedConfigurations[platform.id()].some((metricId) => metricId == metric.id()))
+            && platform.hasMetric(metric);
+    }
+
+    _constructTableCell(platform, metric, indicatorByMetric)
+    {
+        const element = ComponentBase.createElement;
+
+        if (!this._isValidPlatformMetricCombination(platform, metric))
+            return element('td', {class: 'blank-cell'}, element('div'));
+
+        const indicator = new FreshnessIndicator;
+        indicatorByMetric.set(metric, indicator);
+        return element('td', {class: 'status-cell'}, indicator);
+    }
+
+    static htmlTemplate()
+    {
+        return `<section class="page-with-heading"><table id="test-health"></table></section>`;
+    }
+
+    static cssTemplate()
+    {
+        return `
+            .page-with-heading {
+                display: flex;
+                justify-content: center;
+            }
+            #test-health {
+                font-size: 1rem;
+            }
+            #test-health th.table-corner {
+                text-align: right;
+                vertical-align: bottom;
+            }
+            #test-health th {
+                text-align: left;
+                border-bottom: 0.1rem solid #ccc;
+                font-weight: normal;
+            }
+            #test-health th.diagonal-header {
+                white-space: nowrap;
+                height: 16rem;
+                border-bottom: 0rem;
+            }
+            #test-health th.diagonal-header > div {
+                transform: translate(1rem, 7rem) rotate(315deg);
+                width: 2rem;
+                border: 0rem;
+            }
+            #test-health td.status-cell {
+                margin: 0;
+                padding: 0;
+                max-width: 2.2rem;
+                max-height: 2.2rem;
+                min-width: 2.2rem;
+                min-height: 2.2rem;
+            }
+            #test-health td.blank-cell {
+                margin: 0;
+                padding: 0;
+                max-width: 2.2rem;
+                max-height: 2.2rem;
+                min-width: 2.2rem;
+                min-height: 2.2rem;
+            }
+            #test-health td.blank-cell > div  {
+                background-color: #F9F9F9;
+                height: 1.6rem;
+                width: 1.6rem;
+                margin: 0.1rem;
+                padding: 0;
+                position: relative;
+                overflow: hidden;
+            }
+            #test-health td.blank-cell > div::before {
+              content: "";
+              position: absolute;
+              top: -1px;
+              left: -1px;
+              display: block;
+              width: 0px;
+              height: 0px;
+              border-right: calc(1.6rem + 1px) solid #ddd;
+              border-top: calc(1.6rem + 1px) solid transparent;
+            }
+            #test-health td.blank-cell > div::after {
+              content: "";
+              display: block;
+              position: absolute;
+              top: 1px;
+              left: 1px;
+              width: 0px;
+              height: 0px;
+              border-right: calc(1.6rem - 1px) solid #F9F9F9;
+              border-top: calc(1.6rem - 1px) solid transparent;
+            }
+        `;
+    }
+
+    routeName() { return 'test-freshness'; }
+}
\ No newline at end of file

Modified: trunk/Websites/perf.webkit.org/server-tests/api-manifest-tests.js (225897 => 225898)


--- trunk/Websites/perf.webkit.org/server-tests/api-manifest-tests.js	2017-12-14 09:39:59 UTC (rev 225897)
+++ trunk/Websites/perf.webkit.org/server-tests/api-manifest-tests.js	2017-12-14 09:49:37 UTC (rev 225898)
@@ -14,7 +14,7 @@
     it("should generate an empty manifest when database is empty", () => {
         return TestServer.remoteAPI().getJSON('/api/manifest').then((manifest) => {
             assert.deepEqual(Object.keys(manifest).sort(), ['all', 'bugTrackers', 'builders', 'dashboard', 'dashboards',
-                'elapsedTime', 'fileUploadSizeLimit', 'metrics', 'repositories', 'siteTitle', 'status', 'summaryPages', 'tests', 'triggerables']);
+                'elapsedTime', 'fileUploadSizeLimit', 'metrics', 'repositories', 'siteTitle', 'status', 'summaryPages', 'testAgeToleranceInHours', 'tests', 'triggerables']);
 
             assert.equal(typeof(manifest.elapsedTime), 'number');
             delete manifest.elapsedTime;
@@ -29,6 +29,7 @@
                 fileUploadSizeLimit: 2097152, // 2MB during testing.
                 metrics: {},
                 repositories: {},
+                testAgeToleranceInHours: null,
                 tests: {},
                 triggerables: {},
                 summaryPages: [],

Modified: trunk/Websites/perf.webkit.org/unit-tests/build-request-tests.js (225897 => 225898)


--- trunk/Websites/perf.webkit.org/unit-tests/build-request-tests.js	2017-12-14 09:39:59 UTC (rev 225897)
+++ trunk/Websites/perf.webkit.org/unit-tests/build-request-tests.js	2017-12-14 09:49:37 UTC (rev 225898)
@@ -158,4 +158,54 @@
 
     });
 
+    describe('formatTimeInterval', () => {
+        it('should return "0 minutes" when formatting for 0 second in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval(0), '0 minutes');
+        });
+
+        it('should return "1 minute" when formatting for 60 seconds in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval(60 * 1000), '1 minute');
+        });
+
+        it('should return "1 minute" when formatting for  75 seconds in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval(75 * 1000), '1 minute');
+        });
+
+        it('should return "2 minutes" when formatting for 118 seconds in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval(118 * 1000), '2 minutes');
+        });
+
+        it('should return "75 minutes" when formatting for 75 minutes in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval(75 * 60 * 1000), '75 minutes');
+        });
+
+        it('should return "1 hour 58 minutes" when formatting for 118 minutes in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval(118 * 60 * 1000), '1 hour 58 minutes');
+        });
+
+        it('should return "3 hours 2 minutes" when formatting for 182 minutes in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval(182 * 60 * 1000), '3 hours 2 minutes');
+        });
+
+        it('should return "27 hours 14 minutes" when formatting for 27 hours 14 minutes in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval((27 * 3600 + 14 * 60) * 1000), '27 hours 14 minutes');
+        });
+
+        it('should return "2 days 3 hours" when formatting for 51 hours 14 minutes in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval((51 * 3600 + 14 * 60) * 1000), '2 days 3 hours');
+        });
+
+        it('should return "2 days 0 hours" when formatting for 48 hours 1 minutes in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval((48 * 3600 + 1 * 60) * 1000), '2 days 0 hours');
+        });
+
+        it('should return "2 days 2 hours" when formatting for 49 hours 59 minutes in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval((49 * 3600 + 59 * 60) * 1000), '2 days 2 hours');
+        });
+
+        it('should return "2 weeks 6 days" when formatting for 20 days 5 hours 21 minutes in million seconds', () => {
+            assert.equal(BuildRequest.formatTimeInterval(((20 * 24 + 5) * 3600 + 21 * 60) * 1000), '2 weeks 6 days');
+        });
+    });
+
 });
\ No newline at end of file
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to