Record subtest values in Dromaeo tests
authorrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Nov 2013 05:33:17 +0000 (05:33 +0000)
committerrniwa@webkit.org <rniwa@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 27 Nov 2013 05:33:17 +0000 (05:33 +0000)
https://bugs.webkit.org/show_bug.cgi?id=124498

Reviewed by Andreas Kling.

PerformanceTests:

Made Dromaeo's test runner report values in DRT.progress via newly added PerfTestRunner.reportValues.

* Dromaeo/resources/dromaeorunner.js:
(.): Moved the definition out of DRT.setup.
(DRT.setup): Ditto.
(DRT.testObject): Extracted from DRT.setup. Set the subtest name and continueTesting.
continueTesting is set true for subtests; i.e. when name is specified.
(DRT.progress): Call PerfTestRunner.reportValues to report subtest results.
(DRT.teardown): Call PerfTestRunner.reportValues instead of measureValueAsync.

* resources/runner.js: Made various changes for newly added PerfTestRunner.reportValues.
(.): Moved the initialization of completedIterations, results, jsHeapResults, and mallocHeapResults into
start since they need to be initialized before running each subtest. Initialize logLines here since we
need to use the same logger for all subtests.
(.start): Initialize the variables mentioned above here. Also respect doNotLogStart used by reportValues.
(ignoreWarmUpAndLog): Added doNotLogProgress. Used by reportValues since it reports all values at once.
(finish): Compute the metric name such as FrameFrame and Runs from unit. Also don't log or notify done
when continueTesting is set on the test object.
(PerfTestRunner.reportValues): Added. Reports all values for the main/sub test.

Tools:

Supported parsing subtest results.

* Scripts/webkitpy/performance_tests/perftest.py: Replaced _metrics with an ordered list of subtests, each of
which contains a dictionary with its name and an ordered list of subtest's metrics.
(PerfTest.__init__): Initialize _metrics as a list.
(PerfTest.run): Go through each subtest and its metrics to create a list of TestMetrics.
(PerfTest._run_with_driver):
(PerfTest._ensure_metrics): Look for a subtest then a metric in _metrics.

* Scripts/webkitpy/performance_tests/perftest_unittest.py:
(TestPerfTest._assert_results_are_correct): Updated the assertions per changes to _metrics.
(TestPerfTest.test_parse_output): Ditto.
(TestPerfTest.test_parse_output_with_subtests): Added the metric and the unit on each subtest result as well as
assertions to ensure subtest results are parsed properly.
(TestReplayPerfTest.test_run_with_driver_accumulates_results): Updated the assertions per changes to _metrics.
(TestReplayPerfTest.test_run_with_driver_accumulates_memory_results): Dittp.

* Scripts/webkitpy/performance_tests/perftestsrunner.py:
(_generate_results_dict): When the metric for a subtest is processed before that of the main test, the url is
incorrectly suffixed with '/'. Fix this later by re-computing the url with TestPerfMetric.test_file_name when
adding new results.

* Scripts/webkitpy/performance_tests/perftestsrunner_integrationtest.py:
(TestWithSubtestsData): Added.
(TestDriver.run_test):
(MainTest.test_run_test_with_subtests): Added.

LayoutTests:

Rebaselined the test.

* fast/harness/perftests/runs-per-second-log-expected.txt:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@159805 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/harness/perftests/runs-per-second-log-expected.txt
PerformanceTests/ChangeLog
PerformanceTests/Dromaeo/resources/dromaeorunner.js
PerformanceTests/resources/runner.js
Tools/ChangeLog
Tools/Scripts/webkitpy/performance_tests/perftest.py
Tools/Scripts/webkitpy/performance_tests/perftest_unittest.py
Tools/Scripts/webkitpy/performance_tests/perftestsrunner.py
Tools/Scripts/webkitpy/performance_tests/perftestsrunner_integrationtest.py

index 123266f..766c03c 100644 (file)
@@ -1,3 +1,14 @@
+2013-11-26  Ryosuke Niwa  <rniwa@webkit.org>
+
+        Record subtest values in Dromaeo tests
+        https://bugs.webkit.org/show_bug.cgi?id=124498
+
+        Reviewed by Andreas Kling.
+
+        Rebaselined the test.
+
+        * fast/harness/perftests/runs-per-second-log-expected.txt:
+
 2013-11-26  Nick Diego Yamane  <nick.yamane@openbossa.org>
 
         [MediaStream API] HTMLMediaElement should be able to use MediaStream as source
index 1a7ee62..d2aa7bf 100644 (file)
@@ -1,5 +1,5 @@
 This test verifies PerfTestRunner.runPerSecond() outputs log as expected.
 
 
-:Time -> [2, 4, 5, 8, 10] runs/s
+:Runs -> [2, 4, 5, 8, 10] runs/s
 
index a5cd206..e72cc30 100644 (file)
@@ -1,5 +1,32 @@
 2013-11-26  Ryosuke Niwa  <rniwa@webkit.org>
 
+        Record subtest values in Dromaeo tests
+        https://bugs.webkit.org/show_bug.cgi?id=124498
+
+        Reviewed by Andreas Kling.
+
+        Made Dromaeo's test runner report values in DRT.progress via newly added PerfTestRunner.reportValues.
+
+        * Dromaeo/resources/dromaeorunner.js:
+        (.): Moved the definition out of DRT.setup.
+        (DRT.setup): Ditto.
+        (DRT.testObject): Extracted from DRT.setup. Set the subtest name and continueTesting.
+        continueTesting is set true for subtests; i.e. when name is specified.
+        (DRT.progress): Call PerfTestRunner.reportValues to report subtest results.
+        (DRT.teardown): Call PerfTestRunner.reportValues instead of measureValueAsync.
+
+        * resources/runner.js: Made various changes for newly added PerfTestRunner.reportValues.
+        (.): Moved the initialization of completedIterations, results, jsHeapResults, and mallocHeapResults into
+        start since they need to be initialized before running each subtest. Initialize logLines here since we
+        need to use the same logger for all subtests.
+        (.start): Initialize the variables mentioned above here. Also respect doNotLogStart used by reportValues.
+        (ignoreWarmUpAndLog): Added doNotLogProgress. Used by reportValues since it reports all values at once.
+        (finish): Compute the metric name such as FrameFrame and Runs from unit. Also don't log or notify done
+        when continueTesting is set on the test object.
+        (PerfTestRunner.reportValues): Added. Reports all values for the main/sub test.
+
+2013-11-26  Ryosuke Niwa  <rniwa@webkit.org>
+
         Remove replay performance tests as it's not actively maintained
         https://bugs.webkit.org/show_bug.cgi?id=124764
 
index cb306e9..d74c3e9 100644 (file)
@@ -1,11 +1,9 @@
 (function(){
+     var ITERATION_COUNT = 5;
      var DRT  = {
          baseURL: "./resources/dromaeo/web/index.html",
 
          setup: function(testName) {
-             var ITERATION_COUNT = 5;
-             PerfTestRunner.prepareToMeasureValuesAsync({dromaeoIterationCount: ITERATION_COUNT, doNotMeasureMemoryUsage: true, doNotIgnoreInitialRun: true, unit: 'runs/s'});
-
              var iframe = document.createElement("iframe");
              var url = DRT.baseURL + "?" + testName + '&numTests=' + ITERATION_COUNT;
              iframe.setAttribute("src", url);
                  });
          },
 
+         testObject: function(name) {
+             return {dromaeoIterationCount: ITERATION_COUNT, doNotMeasureMemoryUsage: true, doNotIgnoreInitialRun: true, unit: 'runs/s',
+                name: name, continueTesting: !!name};
+         },
+
          start: function() {
              DRT.targetWindow.postMessage({ name: "dromaeo:start" } , "*");
          },
@@ -40,7 +43,7 @@
          progress: function(message) {
             var score = message.status.score;
             if (score)
-                DRT.log(score.name + ' -> [' + score.times.join(', ') + ']');
+                PerfTestRunner.reportValues(this.testObject(score.name), score.times);
          },
 
          teardown: function(data) {
@@ -55,8 +58,7 @@
                  }
              }
 
-             for (var i = 0; i < times.length; ++i)
-                 PerfTestRunner.measureValueAsync(1 / times[i]);
+             PerfTestRunner.reportValues(this.testObject(), times.map(function (time) { return 1 / time; }));
          },
 
          targetDelegateOf: function(functionName) {
index f1c658c..db674b0 100755 (executable)
@@ -6,14 +6,14 @@ if (window.testRunner) {
 }
 
 (function () {
-    var logLines = null;
+    var logLines = window.testRunner ? [] : null;
     var verboseLogging = false;
-    var completedIterations = -1;
+    var completedIterations;
     var callsPerIteration = 1;
     var currentTest = null;
-    var results = [];
-    var jsHeapResults = [];
-    var mallocHeapResults = [];
+    var results;
+    var jsHeapResults;
+    var mallocHeapResults;
     var iterationCount = undefined;
 
     var PerfTestRunner = {};
@@ -145,7 +145,7 @@ if (window.testRunner) {
         finish();
     }
 
-    function start(test, runner) {
+    function start(test, runner, doNotLogStart) {
         if (!test) {
             logFatalError("Got a bad test object.");
             return;
@@ -154,9 +154,15 @@ if (window.testRunner) {
         // FIXME: We should be using multiple instances of test runner on Dromaeo as well but it's too slow now.
         // FIXME: Don't hard code the number of in-process iterations to use inside a test runner.
         iterationCount = test.dromaeoIterationCount || (window.testRunner ? 5 : 20);
-        logLines = window.testRunner ? [] : null;
+        completedIterations = -1;
+        results = [];
+        jsHeapResults = [];
+        mallocHeapResults = [];
         verboseLogging = !window.testRunner;
-        PerfTestRunner.logInfo("Running " + iterationCount + " times");
+        if (!doNotLogStart) {
+            PerfTestRunner.logInfo('');
+            PerfTestRunner.logInfo("Running " + iterationCount + " times");
+        }
         if (test.doNotIgnoreInitialRun)
             completedIterations++;
         if (runner)
@@ -192,39 +198,50 @@ if (window.testRunner) {
         }, 0);
     }
 
-    function ignoreWarmUpAndLog(measuredValue) {
+    function ignoreWarmUpAndLog(measuredValue, doNotLogProgress) {
         var labeledResult = measuredValue + " " + PerfTestRunner.unit;
-        if (completedIterations <= 0)
-            PerfTestRunner.logDetail(completedIterations, labeledResult + " (Ignored warm-up run)");
-        else {
-            results.push(measuredValue);
-            if (window.internals && !currentTest.doNotMeasureMemoryUsage) {
-                jsHeapResults.push(getUsedJSHeap());
-                mallocHeapResults.push(getUsedMallocHeap());
-            }
-            PerfTestRunner.logDetail(completedIterations, labeledResult);
+        if (completedIterations <= 0) {
+            if (!doNotLogProgress)
+                PerfTestRunner.logDetail(completedIterations, labeledResult + " (Ignored warm-up run)");
+            return;
         }
+
+        results.push(measuredValue);
+        if (window.internals && !currentTest.doNotMeasureMemoryUsage) {
+            jsHeapResults.push(getUsedJSHeap());
+            mallocHeapResults.push(getUsedMallocHeap());
+        }
+        if (!doNotLogProgress)
+            PerfTestRunner.logDetail(completedIterations, labeledResult);
     }
 
     function finish() {
         try {
+            var prefix = currentTest.name || '';
             if (currentTest.description)
                 PerfTestRunner.log("Description: " + currentTest.description);
-            PerfTestRunner.logStatistics(results, PerfTestRunner.unit, ":Time");
+            metric = {'fps': 'FrameRate', 'runs/s': 'Runs', 'ms': 'Time'}[PerfTestRunner.unit]
+            PerfTestRunner.logStatistics(results, PerfTestRunner.unit, prefix + ":" + metric);
             if (jsHeapResults.length) {
-                PerfTestRunner.logStatistics(jsHeapResults, "bytes", ":JSHeap");
-                PerfTestRunner.logStatistics(mallocHeapResults, "bytes", ":Malloc");
+                PerfTestRunner.logStatistics(jsHeapResults, "bytes", prefix + ":JSHeap");
+                PerfTestRunner.logStatistics(mallocHeapResults, "bytes", prefix + ":Malloc");
             }
-            if (logLines)
-                logLines.forEach(logInDocument);
             if (currentTest.done)
                 currentTest.done();
+
+            if (logLines && !currentTest.continueTesting)
+                logLines.forEach(logInDocument);
         } catch (exception) {
             logInDocument("Got an exception while finalizing the test with name=" + exception.name + ", message=" + exception.message);
         }
 
-        if (window.testRunner)
-            testRunner.notifyDone();
+        if (!currentTest.continueTesting) {
+            if (window.testRunner)
+                testRunner.notifyDone();
+            return;
+        }
+
+        currentTest = null;
     }
 
     PerfTestRunner.prepareToMeasureValuesAsync = function (test) {
@@ -250,6 +267,16 @@ if (window.testRunner) {
         return true;
     }
 
+    PerfTestRunner.reportValues = function (test, values) {
+        PerfTestRunner.unit = test.unit;
+        start(test, null, true);
+        for (var i = 0; i < values.length; i++) {
+            completedIterations++;
+            ignoreWarmUpAndLog(values[i], true);
+        }
+        finish();
+    }
+
     PerfTestRunner.measureTime = function (test) {
         PerfTestRunner.unit = "ms";
         start(test, measureTimeOnce);
index edb3d41..90362a5 100644 (file)
@@ -1,5 +1,39 @@
 2013-11-26  Ryosuke Niwa  <rniwa@webkit.org>
 
+        Record subtest values in Dromaeo tests
+        https://bugs.webkit.org/show_bug.cgi?id=124498
+
+        Reviewed by Andreas Kling.
+
+        Supported parsing subtest results.
+
+        * Scripts/webkitpy/performance_tests/perftest.py: Replaced _metrics with an ordered list of subtests, each of
+        which contains a dictionary with its name and an ordered list of subtest's metrics.
+        (PerfTest.__init__): Initialize _metrics as a list.
+        (PerfTest.run): Go through each subtest and its metrics to create a list of TestMetrics.
+        (PerfTest._run_with_driver):
+        (PerfTest._ensure_metrics): Look for a subtest then a metric in _metrics.
+
+        * Scripts/webkitpy/performance_tests/perftest_unittest.py:
+        (TestPerfTest._assert_results_are_correct): Updated the assertions per changes to _metrics.
+        (TestPerfTest.test_parse_output): Ditto.
+        (TestPerfTest.test_parse_output_with_subtests): Added the metric and the unit on each subtest result as well as
+        assertions to ensure subtest results are parsed properly.
+        (TestReplayPerfTest.test_run_with_driver_accumulates_results): Updated the assertions per changes to _metrics.
+        (TestReplayPerfTest.test_run_with_driver_accumulates_memory_results): Dittp.
+
+        * Scripts/webkitpy/performance_tests/perftestsrunner.py:
+        (_generate_results_dict): When the metric for a subtest is processed before that of the main test, the url is
+        incorrectly suffixed with '/'. Fix this later by re-computing the url with TestPerfMetric.test_file_name when
+        adding new results.
+
+        * Scripts/webkitpy/performance_tests/perftestsrunner_integrationtest.py:
+        (TestWithSubtestsData): Added.
+        (TestDriver.run_test):
+        (MainTest.test_run_test_with_subtests): Added.
+
+2013-11-26  Ryosuke Niwa  <rniwa@webkit.org>
+
         Enable HTML template element on Windows ports
         https://bugs.webkit.org/show_bug.cgi?id=124758
 
index 05537e0..4a9c763 100644 (file)
@@ -100,8 +100,7 @@ class PerfTest(object):
         self._test_name = test_name
         self._test_path = test_path
         self._description = None
-        self._metrics = {}
-        self._ordered_metrics_name = []
+        self._metrics = []
         self._test_runner_count = test_runner_count
 
     def test_name(self):
@@ -136,13 +135,13 @@ class PerfTest(object):
             _log.info('DESCRIPTION: %s' % self._description)
 
         results = []
-        for metric_name in self._ordered_metrics_name:
-            metric = self._metrics[metric_name]
-            results.append(metric)
-            if should_log:
-                legacy_chromium_bot_compatible_name = self.test_name_without_file_extension().replace('/', ': ')
-                self.log_statistics(legacy_chromium_bot_compatible_name + ': ' + metric.name(),
-                    metric.flattened_iteration_values(), metric.unit())
+        for subtest in self._metrics:
+            for metric in subtest['metrics']:
+                results.append(metric)
+                if should_log and not subtest['name']:
+                    legacy_chromium_bot_compatible_name = self.test_name_without_file_extension().replace('/', ': ')
+                    self.log_statistics(legacy_chromium_bot_compatible_name + ': ' + metric.name(),
+                        metric.flattened_iteration_values(), metric.unit())
 
         return results
 
@@ -169,7 +168,7 @@ class PerfTest(object):
             (median, unit, stdev, unit, sorted_values[0], unit, sorted_values[-1], unit))
 
     _description_regex = re.compile(r'^Description: (?P<description>.*)$', re.IGNORECASE)
-    _metrics_regex = re.compile(r'^:(?P<metric>Time|Malloc|JSHeap) -> \[(?P<values>(\d+(\.\d+)?)(, \d+(\.\d+)?)+)\] (?P<unit>[a-z/]+)')
+    _metrics_regex = re.compile(r'^(?P<subtest>[A-Za-z0-9\(\[].+)?:(?P<metric>[A-Z][A-Za-z]+) -> \[(?P<values>(\d+(\.\d+)?)(, \d+(\.\d+)?)+)\] (?P<unit>[a-z/]+)?$')
 
     def _run_with_driver(self, driver, time_out_ms):
         output = self.run_single(driver, self.test_path(), time_out_ms)
@@ -189,16 +188,27 @@ class PerfTest(object):
                 _log.error('ERROR: ' + line)
                 return False
 
-            metric = self._ensure_metrics(metric_match.group('metric'), metric_match.group('unit'))
+            metric = self._ensure_metrics(metric_match.group('metric'), metric_match.group('subtest'), metric_match.group('unit'))
             metric.append_group(map(lambda value: float(value), metric_match.group('values').split(', ')))
 
         return True
 
-    def _ensure_metrics(self, metric_name, unit=None):
-        if metric_name not in self._metrics:
-            self._metrics[metric_name] = PerfTestMetric(self.test_name_without_file_extension().split('/'), self._test_name, metric_name, unit)
-            self._ordered_metrics_name.append(metric_name)
-        return self._metrics[metric_name]
+    def _ensure_metrics(self, metric_name, subtest_name='', unit=None):
+        try:
+            subtest = next(subtest for subtest in self._metrics if subtest['name'] == subtest_name)
+        except StopIteration:
+            subtest = {'name': subtest_name, 'metrics': []}
+            self._metrics.append(subtest)
+
+        try:
+            return next(metric for metric in subtest['metrics'] if metric.name() == metric_name)
+        except StopIteration:
+            path = self.test_name_without_file_extension().split('/')
+            if subtest_name:
+                path += [subtest_name]
+            metric = PerfTestMetric(path, self._test_name, metric_name, unit)
+            subtest['metrics'].append(metric)
+            return metric
 
     def run_single(self, driver, test_path, time_out_ms, should_run_pixel_test=False):
         return driver.run_test(DriverInput(test_path, time_out_ms, image_hash=None, should_run_pixel_test=should_run_pixel_test), stop_when_done=False)
@@ -236,9 +246,6 @@ class PerfTest(object):
         re.compile(re.escape("""Blocked access to external URL http://www.whatwg.org/specs/web-apps/current-work/""")),
         re.compile(r"CONSOLE MESSAGE: (line \d+: )?Blocked script execution in '[A-Za-z0-9\-\.:]+' because the document's frame is sandboxed and the 'allow-scripts' permission is not set."),
         re.compile(r"CONSOLE MESSAGE: (line \d+: )?Not allowed to load local resource"),
-        # Dromaeo reports values for subtests. Ignore them for now.
-        # FIXME: Remove once subtests are supported
-        re.compile(r'^[A-Za-z0-9\(\[].+( -> )(\[?[0-9\., ]+\])( [a-z/]+)?$'),
     ]
 
     def _filter_output(self, output):
index e7d94f8..23818e4 100644 (file)
@@ -91,9 +91,12 @@ class TestPerfTestMetric(unittest.TestCase):
 class TestPerfTest(unittest.TestCase):
     def _assert_results_are_correct(self, test, output):
         test.run_single = lambda driver, path, time_out_ms: output
-        self.assertTrue(test._run_with_driver(None, None))
-        self.assertEqual(test._metrics.keys(), ['Time'])
-        self.assertEqual(test._metrics['Time'].flattened_iteration_values(), [1080, 1120, 1095, 1101, 1104])
+        self.assertTrue(test.run(10))
+        subtests = test._metrics
+        self.assertEqual(map(lambda test: test['name'], subtests), [None])
+        metrics = subtests[0]['metrics']
+        self.assertEqual(map(lambda metric: metric.name(), metrics), ['Time'])
+        self.assertEqual(metrics[0].flattened_iteration_values(), [1080, 1120, 1095, 1101, 1104] * 4)
 
     def test_parse_output(self):
         output = DriverOutput("""
@@ -108,7 +111,9 @@ class TestPerfTest(unittest.TestCase):
             actual_stdout, actual_stderr, actual_logs = output_capture.restore_output()
         self.assertEqual(actual_stdout, '')
         self.assertEqual(actual_stderr, '')
-        self.assertEqual(actual_logs, '')
+        self.assertEqual(actual_logs, """RESULT some-test: Time= 1100.0 ms
+median= 1101.0 ms, stdev= 13.3140211016 ms, min= 1080.0 ms, max= 1120.0 ms
+""")
 
     def _assert_failed_on_line(self, output_text, expected_log):
         output = DriverOutput(output_text, image=None, image_hash=None, audio=None)
@@ -155,28 +160,56 @@ Description: this is a test description.
     def test_parse_output_with_subtests(self):
         output = DriverOutput("""
 Description: this is a test description.
-some test -> [1, 2, 3, 4, 5]
-some other test = else -> [6, 7, 8, 9, 10]
-Array Construction, [] -> [11, 12, 13, 14, 15]
-Concat String -> [15163, 15304, 15386, 15608, 15622]
-jQuery - addClass -> [2785, 2815, 2826, 2841, 2861]
-Dojo - div:only-child -> [7825, 7910, 7950, 7958, 7970]
-Dojo - div:nth-child(2n+1) -> [3620, 3623, 3633, 3641, 3658]
-Dojo - div > div -> [10158, 10172, 10180, 10183, 10231]
-Dojo - div ~ div -> [6673, 6675, 6714, 6848, 6902]
+some test:Time -> [1, 2, 3, 4, 5] ms
+some other test = else:Time -> [6, 7, 8, 9, 10] ms
+some other test = else:Malloc -> [11, 12, 13, 14, 15] bytes
+Array Construction, []:Time -> [11, 12, 13, 14, 15] ms
+Concat String:Time -> [15163, 15304, 15386, 15608, 15622] ms
+jQuery - addClass:Time -> [2785, 2815, 2826, 2841, 2861] ms
+Dojo - div:only-child:Time -> [7825, 7910, 7950, 7958, 7970] ms
+Dojo - div:nth-child(2n+1):Time -> [3620, 3623, 3633, 3641, 3658] ms
+Dojo - div > div:Time -> [10158, 10172, 10180, 10183, 10231] ms
+Dojo - div ~ div:Time -> [6673, 6675, 6714, 6848, 6902] ms
 
 :Time -> [1080, 1120, 1095, 1101, 1104] ms
 """, image=None, image_hash=None, audio=None)
         output_capture = OutputCapture()
         output_capture.capture_output()
         try:
-            test = PerfTest(MockPort(), 'some-test', '/path/some-dir/some-test')
-            self._assert_results_are_correct(test, output)
+            test = PerfTest(MockPort(), 'some-dir/some-test', '/path/some-dir/some-test')
+            test.run_single = lambda driver, path, time_out_ms: output
+            self.assertTrue(test.run(10))
         finally:
             actual_stdout, actual_stderr, actual_logs = output_capture.restore_output()
+
+        subtests = test._metrics
+        self.assertEqual(map(lambda test: test['name'], subtests), ['some test', 'some other test = else',
+            'Array Construction, []', 'Concat String', 'jQuery - addClass', 'Dojo - div:only-child',
+            'Dojo - div:nth-child(2n+1)', 'Dojo - div > div', 'Dojo - div ~ div', None])
+
+        some_test_metrics = subtests[0]['metrics']
+        self.assertEqual(map(lambda metric: metric.name(), some_test_metrics), ['Time'])
+        self.assertEqual(some_test_metrics[0].path(), ['some-dir', 'some-test', 'some test'])
+        self.assertEqual(some_test_metrics[0].flattened_iteration_values(), [1, 2, 3, 4, 5] * 4)
+
+        some_other_test_metrics = subtests[1]['metrics']
+        self.assertEqual(map(lambda metric: metric.name(), some_other_test_metrics), ['Time', 'Malloc'])
+        self.assertEqual(some_other_test_metrics[0].path(), ['some-dir', 'some-test', 'some other test = else'])
+        self.assertEqual(some_other_test_metrics[0].flattened_iteration_values(), [6, 7, 8, 9, 10] * 4)
+        self.assertEqual(some_other_test_metrics[1].path(), ['some-dir', 'some-test', 'some other test = else'])
+        self.assertEqual(some_other_test_metrics[1].flattened_iteration_values(), [11, 12, 13, 14, 15] * 4)
+
+        main_metrics = subtests[len(subtests) - 1]['metrics']
+        self.assertEqual(map(lambda metric: metric.name(), main_metrics), ['Time'])
+        self.assertEqual(main_metrics[0].path(), ['some-dir', 'some-test'])
+        self.assertEqual(main_metrics[0].flattened_iteration_values(), [1080, 1120, 1095, 1101, 1104] * 4)
+
         self.assertEqual(actual_stdout, '')
         self.assertEqual(actual_stderr, '')
-        self.assertEqual(actual_logs, '')
+        self.assertEqual(actual_logs, """DESCRIPTION: this is a test description.
+RESULT some-dir: some-test: Time= 1100.0 ms
+median= 1101.0 ms, stdev= 13.3140211016 ms, min= 1080.0 ms, max= 1120.0 ms
+""")
 
 
 class TestSingleProcessPerfTest(unittest.TestCase):
index 0fe27d4..19da752 100644 (file)
@@ -262,10 +262,11 @@ class PerfTestsRunner(object):
             path = metric.path()
             for i in range(0, len(path)):
                 is_last_token = i + 1 == len(path)
-                url = view_source_url('PerformanceTests/' + (metric.test_file_name() if is_last_token else '/'.join(path[0:i + 1])))
+                url = view_source_url('PerformanceTests/' + '/'.join(path[0:i + 1]))
                 tests.setdefault(path[i], {'url': url})
                 current_test = tests[path[i]]
                 if is_last_token:
+                    current_test['url'] = view_source_url('PerformanceTests/' + metric.test_file_name())
                     current_test.setdefault('metrics', {})
                     assert metric.name() not in current_test['metrics']
                     current_test['metrics'][metric.name()] = {'current': metric.grouped_iteration_values()}
index 0b04bf9..06d3b5c 100644 (file)
@@ -96,6 +96,26 @@ Finished: 0.1 s
     malloc_results = {'current': [[529000, 511000, 548000, 536000, 521000]] * 4}
 
 
+class TestWithSubtestsData:
+    text = """subtest:Time -> [1, 2, 3, 4, 5] ms
+:Time -> [1080, 1120, 1095, 1101, 1104] ms
+"""
+
+    output = """Running 1 tests
+Running Parser/test-with-subtests.html (1 of 1)
+RESULT Parser: test-with-subtests: Time= 1100.0 ms
+median= 1101.0 ms, stdev= 13.31402 ms, min= 1080.0 ms, max= 1120.0 ms
+Finished: 0.1 s
+"""
+
+    results = {'url': 'http://trac.webkit.org/browser/trunk/PerformanceTests/Parser/test-with-subtests.html',
+        'metrics': {'Time': {'current': [[1080.0, 1120.0, 1095.0, 1101.0, 1104.0]] * 4}},
+        'tests': {
+            'subtest': {
+                'url': 'http://trac.webkit.org/browser/trunk/PerformanceTests/Parser/test-with-subtests.html',
+                'metrics': {'Time': {'current': [[1.0, 2.0, 3.0, 4.0, 5.0]] * 4}}}}}
+
+
 class TestDriver:
     def run_test(self, driver_input, stop_when_done):
         text = ''
@@ -117,6 +137,8 @@ class TestDriver:
             text = SomeParserTestData.text
         elif driver_input.test_name.endswith('memory-test.html'):
             text = MemoryTestData.text
+        elif driver_input.test_name.endswith('test-with-subtests.html'):
+            text = TestWithSubtestsData.text
         return DriverOutput(text, '', '', '', crash=crash, timeout=timeout)
 
     def start(self):
@@ -223,6 +245,24 @@ class MainTest(unittest.TestCase):
         self.assertEqual(parser_tests['memory-test']['metrics']['JSHeap'], MemoryTestData.js_heap_results)
         self.assertEqual(parser_tests['memory-test']['metrics']['Malloc'], MemoryTestData.malloc_results)
 
+    def test_run_test_with_subtests(self):
+        runner, port = self.create_runner_and_setup_results_template()
+        runner._timestamp = 123456789
+        port.host.filesystem.write_text_file(runner._base_path + '/Parser/test-with-subtests.html', 'some content')
+
+        output = OutputCapture()
+        output.capture_output()
+        try:
+            unexpected_result_count = runner.run()
+        finally:
+            stdout, stderr, log = output.restore_output()
+
+        self.assertEqual(unexpected_result_count, 0)
+        self.assertEqual(self._normalize_output(log), TestWithSubtestsData.output + '\nMOCK: user.open_url: file://...\n')
+        parser_tests = self._load_output_json(runner)[0]['tests']['Parser']['tests']
+        self.maxDiff = None
+        self.assertEqual(parser_tests['test-with-subtests'], TestWithSubtestsData.results)
+
     def _test_run_with_json_output(self, runner, filesystem, upload_succeeds=False, results_shown=True, expected_exit_code=0, repeat=1, compare_logs=True):
         filesystem.write_text_file(runner._base_path + '/Parser/some-parser.html', 'some content')
         filesystem.write_text_file(runner._base_path + '/Bindings/event-target-wrapper.html', 'some content')