| # Copyright 2015 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import unittest |
| |
| import webapp2 |
| import webtest |
| |
| from google.appengine.ext import ndb |
| |
| from dashboard import migrate_test_names |
| from dashboard import testing_common |
| from dashboard import utils |
| from dashboard.models import anomaly |
| from dashboard.models import graph_data |
| from dashboard.models import sheriff |
| from dashboard.models import stoppage_alert |
| |
| # Masters, bots and test names to add to the mock datastore. |
| _MOCK_DATA = [ |
| ['ChromiumPerf', 'ChromiumWebkit'], |
| ['win7', 'mac'], |
| { |
| 'SunSpider': { |
| 'Total': { |
| 't': {}, |
| 't_ref': {}, |
| 't_extwr': {}, |
| }, |
| '3d-cube': {'t': {}}, |
| }, |
| 'moz': { |
| 'read_op_b': {'r_op_b': {}}, |
| }, |
| } |
| ] |
| |
| |
| class MigrateTestNamesTest(testing_common.TestCase): |
| |
| def setUp(self): |
| super(MigrateTestNamesTest, self).setUp() |
| app = webapp2.WSGIApplication([( |
| '/migrate_test_names', migrate_test_names.MigrateTestNamesHandler)]) |
| self.testapp = webtest.TestApp(app) |
| # Make sure puts get split up into multiple calls. |
| migrate_test_names._MAX_DATASTORE_PUTS_PER_PUT_MULTI_CALL = 30 |
| |
| def _AddMockData(self): |
| """Adds sample Test, Row, and Anomaly entities.""" |
| testing_common.AddTests(*_MOCK_DATA) |
| |
| # Add 50 Row entities to one of the tests. |
| # Also add 2 Anomaly entities. |
| test_path = 'ChromiumPerf/mac/SunSpider/Total/t' |
| test_key = utils.TestKey(test_path) |
| test_container_key = utils.GetTestContainerKey(test_key) |
| for rev in range(15000, 15100, 2): |
| graph_data.Row(id=rev, parent=test_container_key, value=(rev * 2)).put() |
| if rev % 50 == 0: |
| anomaly.Anomaly( |
| start_revision=(rev - 2), end_revision=rev, |
| median_before_anomaly=100, median_after_anomaly=50, |
| test=test_key).put() |
| |
| def _CheckRows(self, test_path, multiplier=2): |
| """Checks the rows match the expected sample data for a given test. |
| |
| The expected revisions that should be present are based on the sample data |
| added in _AddMockData. |
| |
| Args: |
| test_path: Test path of the test to get rows for. |
| multiplier: Number to multiply with revision to get expected value. |
| """ |
| test_key = utils.TestKey(test_path) |
| rows = graph_data.Row.query(graph_data.Row.parent_test == test_key).fetch() |
| self.assertEqual(50, len(rows)) |
| self.assertEqual(15000, rows[0].revision) |
| self.assertEqual(15000 * multiplier, rows[0].value) |
| self.assertEqual(15098, rows[49].revision) |
| self.assertEqual(15098 * multiplier, rows[49].value) |
| |
| def _CheckAnomalies(self, test_path, r1=15000, r2=15050): |
| """Checks whether the anomalies match the ones added in _AddMockData. |
| |
| Args: |
| test_path: The test path for the Test which the Anomalies are on. |
| r1: Expected end revision of first Anomaly. |
| r2: Expected end revision of second Anomaly. |
| """ |
| key = utils.TestKey(test_path) |
| anomalies = anomaly.Anomaly.query( |
| anomaly.Anomaly.test == key).fetch() |
| self.assertEqual(2, len(anomalies)) |
| self.assertEqual(r1, anomalies[0].end_revision) |
| self.assertEqual(r2, anomalies[1].end_revision) |
| |
| def _CheckTests(self, expected_tests): |
| """Checks whether the current Test entities match the expected list. |
| |
| Args: |
| expected_tests: List of test paths without the master/bot part. |
| """ |
| for master in _MOCK_DATA[0]: |
| for bot in _MOCK_DATA[1]: |
| expected = ['%s/%s/%s' % (master, bot, t) for t in expected_tests] |
| bot_key = ndb.Key('Master', master, 'Bot', bot) |
| tests = graph_data.Test.query(ancestor=bot_key).fetch() |
| actual = [t.test_path for t in tests] |
| self.assertEqual(expected, actual) |
| |
| def testPost_MigrateTraceLevelTest(self): |
| self._AddMockData() |
| self.testapp.post('/migrate_test_names', { |
| 'old_pattern': '*/*/*/*/t', |
| 'new_pattern': '*/*/*/*/time', |
| }) |
| self.ExecuteTaskQueueTasks( |
| '/migrate_test_names', migrate_test_names._TASK_QUEUE_NAME) |
| expected_tests = [ |
| 'SunSpider', |
| 'SunSpider/3d-cube', |
| 'SunSpider/3d-cube/time', |
| 'SunSpider/Total', |
| 'SunSpider/Total/t_extwr', |
| 'SunSpider/Total/t_ref', |
| 'SunSpider/Total/time', |
| 'moz', |
| 'moz/read_op_b', |
| 'moz/read_op_b/r_op_b', |
| ] |
| self._CheckTests(expected_tests) |
| |
| def testPost_RenameTraceWithPartialWildCardsInNewPattern_Fails(self): |
| # If there's a wildcard in a part of the new pattern, it should |
| # just be a single wildcard by itself, and it just means "copy |
| # over whatever was in the old test path". Wildcards mixed with |
| # substrings should be rejected. |
| self._AddMockData() |
| self.testapp.post('/migrate_test_names', { |
| 'old_pattern': '*/*/Sun*/*/t', |
| 'new_pattern': '*/*/Sun*/*/time', |
| }, status=400) |
| self.ExecuteTaskQueueTasks( |
| '/migrate_test_names', migrate_test_names._TASK_QUEUE_NAME) |
| # Nothing was renamed since there was an error. |
| expected_tests = [ |
| 'SunSpider', |
| 'SunSpider/3d-cube', |
| 'SunSpider/3d-cube/t', |
| 'SunSpider/Total', |
| 'SunSpider/Total/t', |
| 'SunSpider/Total/t_extwr', |
| 'SunSpider/Total/t_ref', |
| 'moz', |
| 'moz/read_op_b', |
| 'moz/read_op_b/r_op_b', |
| ] |
| self._CheckTests(expected_tests) |
| |
| def testPost_MigrateChartLevelTest(self): |
| self._AddMockData() |
| |
| self.testapp.post('/migrate_test_names', { |
| 'old_pattern': '*/*/SunSpider/Total', |
| 'new_pattern': '*/*/SunSpider/OverallScore', |
| }) |
| self.ExecuteTaskQueueTasks( |
| '/migrate_test_names', migrate_test_names._TASK_QUEUE_NAME) |
| |
| self._CheckRows('ChromiumPerf/mac/SunSpider/OverallScore/t') |
| self._CheckAnomalies('ChromiumPerf/mac/SunSpider/OverallScore/t') |
| expected_tests = [ |
| 'SunSpider', |
| 'SunSpider/3d-cube', |
| 'SunSpider/3d-cube/t', |
| 'SunSpider/OverallScore', |
| 'SunSpider/OverallScore/t', |
| 'SunSpider/OverallScore/t_extwr', |
| 'SunSpider/OverallScore/t_ref', |
| 'moz', |
| 'moz/read_op_b', |
| 'moz/read_op_b/r_op_b', |
| ] |
| self._CheckTests(expected_tests) |
| |
| def testPost_MigrateSuiteLevelTest(self): |
| self._AddMockData() |
| |
| self.testapp.post('/migrate_test_names', { |
| 'old_pattern': '*/*/SunSpider', |
| 'new_pattern': '*/*/SunSpider1.0', |
| }) |
| self.ExecuteTaskQueueTasks( |
| '/migrate_test_names', migrate_test_names._TASK_QUEUE_NAME) |
| |
| self._CheckRows('ChromiumPerf/mac/SunSpider1.0/Total/t') |
| self._CheckAnomalies('ChromiumPerf/mac/SunSpider1.0/Total/t') |
| expected_tests = [ |
| 'SunSpider1.0', |
| 'SunSpider1.0/3d-cube', |
| 'SunSpider1.0/3d-cube/t', |
| 'SunSpider1.0/Total', |
| 'SunSpider1.0/Total/t', |
| 'SunSpider1.0/Total/t_extwr', |
| 'SunSpider1.0/Total/t_ref', |
| 'moz', |
| 'moz/read_op_b', |
| 'moz/read_op_b/r_op_b', |
| ] |
| self._CheckTests(expected_tests) |
| |
| def testPost_MigrateSeriesToChartLevelTest(self): |
| self._AddMockData() |
| |
| self.testapp.post('/migrate_test_names', { |
| 'old_pattern': '*/*/SunSpider/Total/t', |
| 'new_pattern': '*/*/SunSpider/Total', |
| }) |
| self.ExecuteTaskQueueTasks( |
| '/migrate_test_names', migrate_test_names._TASK_QUEUE_NAME) |
| |
| # The Row and Anomaly entities have been moved. |
| self._CheckRows('ChromiumPerf/mac/SunSpider/Total') |
| self._CheckAnomalies('ChromiumPerf/mac/SunSpider/Total') |
| |
| # There is no SunSpider/Total/time any more. |
| expected_tests = [ |
| 'SunSpider', |
| 'SunSpider/3d-cube', |
| 'SunSpider/3d-cube/t', |
| 'SunSpider/Total', |
| 'SunSpider/Total/t_extwr', |
| 'SunSpider/Total/t_ref', |
| 'moz', |
| 'moz/read_op_b', |
| 'moz/read_op_b/r_op_b', |
| ] |
| self._CheckTests(expected_tests) |
| |
| def testPost_MigrationFinished_EmailsSheriff(self): |
| self._AddMockData() |
| |
| # Add a sheriff for one test. |
| test_path = 'ChromiumPerf/mac/moz/read_op_b/r_op_b' |
| test = utils.TestKey(test_path).get() |
| sheriff_key = sheriff.Sheriff( |
| id='Perf Sheriff Mac', email='sullivan@google.com', |
| patterns=['*/mac/*/*/r_op_b']).put() |
| test.sheriff = sheriff_key |
| test.put() |
| |
| # Add another sheriff for another test. |
| test_path = 'ChromiumPerf/win7/moz/read_op_b/r_op_b' |
| test = utils.TestKey(test_path).get() |
| sheriff_key = sheriff.Sheriff( |
| id='Perf Sheriff Win', email='sullivan@google.com', |
| patterns=['*/win7/*/*/r_op_b']).put() |
| test.sheriff = sheriff_key |
| test.put() |
| |
| # Make a request to t migrate a test and then execute tasks on the queue. |
| self.testapp.post('/migrate_test_names', { |
| 'old_pattern': '*/*/moz/read_op_b/r_op_b', |
| 'new_pattern': '*/*/moz/read_operations_browser', |
| }) |
| self.ExecuteTaskQueueTasks( |
| '/migrate_test_names', migrate_test_names._TASK_QUEUE_NAME) |
| |
| # Check the emails that were sent. |
| messages = self.mail_stub.get_sent_messages() |
| self.assertEqual(2, len(messages)) |
| self.assertEqual('gasper-alerts@google.com', messages[0].sender) |
| self.assertEqual('chrome-perf-dashboard-alerts@google.com', messages[0].to) |
| self.assertEqual('Sheriffed Test Migrated', messages[0].subject) |
| body = str(messages[0].body) |
| self.assertIn( |
| 'test ChromiumPerf/mac/moz/read_op_b/r_op_b has been migrated', body) |
| self.assertIn( |
| 'migrated to ChromiumPerf/mac/moz/read_operations_browser', body) |
| self.assertIn( |
| 'sheriffed by Perf Sheriff Mac', body) |
| self.assertEqual('gasper-alerts@google.com', messages[1].sender) |
| self.assertEqual('chrome-perf-dashboard-alerts@google.com', messages[1].to) |
| self.assertEqual('Sheriffed Test Migrated', messages[1].subject) |
| body = str(messages[1].body) |
| self.assertIn( |
| 'test ChromiumPerf/win7/moz/read_op_b/r_op_b has been migrated', body) |
| self.assertIn( |
| 'migrated to ChromiumPerf/win7/moz/read_operations_browser', body) |
| self.assertIn('sheriffed by Perf Sheriff Win', body) |
| |
| def testPost_MigratesStoppageAlerts(self): |
| testing_common.AddTests(['Master'], ['b'], {'suite': {'foo': {}}}) |
| test_path = 'Master/b/suite/foo' |
| test_key = utils.TestKey(test_path) |
| test_container_key = utils.GetTestContainerKey(test_key) |
| row_key = graph_data.Row(id=100, parent=test_container_key, value=5).put() |
| stoppage_alert.CreateStoppageAlert(test_key.get(), row_key.get()).put() |
| self.assertIsNotNone( |
| stoppage_alert.GetStoppageAlert('Master/b/suite/foo', 100)) |
| self.assertIsNone( |
| stoppage_alert.GetStoppageAlert('Master/b/suite/bar', 100)) |
| |
| self.testapp.post('/migrate_test_names', { |
| 'old_pattern': 'Master/b/suite/foo', |
| 'new_pattern': 'Master/b/suite/bar', |
| }) |
| self.ExecuteTaskQueueTasks( |
| '/migrate_test_names', migrate_test_names._TASK_QUEUE_NAME) |
| |
| self.assertIsNotNone( |
| stoppage_alert.GetStoppageAlert('Master/b/suite/bar', 100)) |
| self.assertIsNone( |
| stoppage_alert.GetStoppageAlert('Master/b/suite/foo', 100)) |
| |
| def testGetNewTestPath_WithAsterisks(self): |
| self.assertEqual( |
| 'A/b/c/X', |
| migrate_test_names._GetNewTestPath('A/b/c/d', '*/*/*/X')) |
| self.assertEqual( |
| 'A/b/c/d', |
| migrate_test_names._GetNewTestPath('A/b/c/d', '*/*/*/*')) |
| self.assertEqual( |
| 'A/b/c', |
| migrate_test_names._GetNewTestPath('A/b/c/d', '*/*/*')) |
| |
| def testGetNewTestPath_WithBrackets(self): |
| # Brackets are just used to delete parts of names, no other functionality. |
| self.assertEqual( |
| 'A/b/c/x', |
| migrate_test_names._GetNewTestPath('A/b/c/xxxx', '*/*/*/[xxx]')) |
| self.assertEqual( |
| 'A/b/c', |
| migrate_test_names._GetNewTestPath('A/b/c/xxxx', '*/*/*/[xxxx]')) |
| self.assertEqual( |
| 'A/b/c/x', |
| migrate_test_names._GetNewTestPath('A/b/c/x', '*/*/*/[]')) |
| self.assertEqual( |
| 'A/b/c/d', |
| migrate_test_names._GetNewTestPath('AA/bb/cc/dd', '[A]/[b]/[c]/[d]')) |
| |
| def testGetNewTestPath_NewPathHasDifferentLength(self): |
| self.assertEqual( |
| 'A/b/c', |
| migrate_test_names._GetNewTestPath('A/b/c/d', 'A/*/c')) |
| self.assertEqual( |
| 'A/b/c/d', |
| migrate_test_names._GetNewTestPath('A/b/c', 'A/*/c/d')) |
| self.assertRaises( |
| migrate_test_names.BadInputPatternError, |
| migrate_test_names._GetNewTestPath, 'A/b/c', 'A/b/c/*') |
| |
| def testGetNewTestPath_InvalidArgs(self): |
| self.assertRaises( |
| AssertionError, |
| migrate_test_names._GetNewTestPath, 'A/b/*/d', 'A/b/c/d') |
| self.assertRaises( |
| migrate_test_names.BadInputPatternError, |
| migrate_test_names._GetNewTestPath, 'A/b/c/d', 'A/b/c/d*') |
| |
| |
| if __name__ == '__main__': |
| unittest.main() |