def _pre_put_hook(self): """This method is called before a TestMetadata is put into the datastore. Here, we check the key to make sure it is valid and check the sheriffs and anomaly configs to make sure they are current. We also update the monitored list of the test suite. """ # Check to make sure the key is valid. # TestMetadata should not be an ancestor, so key.pairs() should have length # of 1. The id should have at least 3 slashes to represent master/bot/suite. assert len(self.key.pairs()) == 1 path_parts = self.key.id().split('/') assert len(path_parts) >= 3 # Set the sheriff to the first sheriff (alphabetically by sheriff name) # that has a test pattern that matches this test. self.sheriff = None for sheriff_entity in sheriff_module.Sheriff.query().fetch(): for pattern in sheriff_entity.patterns: if utils.TestMatchesPattern(self, pattern): self.sheriff = sheriff_entity.key if self.sheriff: break # If this test is monitored, add it to the monitored list of its test suite. # A test is be monitored iff it has a sheriff, and monitored tests are # tracked in the monitored list of a test suite TestMetadata entity. test_suite = ndb.Key('TestMetadata', '/'.join(path_parts[:3])).get() if self.sheriff: if test_suite and self.key not in test_suite.monitored: test_suite.monitored.append(self.key) test_suite.put() elif test_suite and self.key in test_suite.monitored: test_suite.monitored.remove(self.key) test_suite.put() # Set the anomaly threshold config to the first one that has a test pattern # that matches this test, if there is one. Anomaly configs are sorted by # name, so that a config with a name that comes earlier lexicographically # is considered higher-priority. self.overridden_anomaly_config = None anomaly_configs = anomaly_config.AnomalyConfig.query().fetch() anomaly_configs.sort(key=lambda config: config.key.string_id()) for anomaly_config_entity in anomaly_configs: for pattern in anomaly_config_entity.patterns: if utils.TestMatchesPattern(self, pattern): self.overridden_anomaly_config = anomaly_config_entity.key if self.overridden_anomaly_config: break
def _pre_put_hook(self): """This method is called before a TestMetadata is put into the datastore. Here, we check the key to make sure it is valid and check the sheriffs and anomaly configs to make sure they are current. We also update the monitored list of the test suite. """ # Check to make sure the key is valid. # TestMetadata should not be an ancestor, so key.pairs() should have length # of 1. The id should have at least 3 slashes to represent master/bot/suite. assert len(self.key.pairs()) == 1 path_parts = self.key.id().split('/') assert len(path_parts) >= 3 # Set the sheriff to the first sheriff (alphabetically by sheriff name) # that has a test pattern that matches this test. old_sheriff = self.sheriff self.sheriff = None for sheriff_entity in sheriff_module.Sheriff.query().fetch(): for pattern in sheriff_entity.patterns: if utils.TestMatchesPattern(self, pattern): self.sheriff = sheriff_entity.key if self.sheriff: break # TODO(simonhatch): Remove this logging. Trying to track down alerts being # generated for tests that seemingly have no sheriff. # https://github.com/catapult-project/catapult/issues/3903 if old_sheriff != self.sheriff: logging.info('Sheriff Modified') logging.info(' Test: %s', self.test_path) logging.info(' Old Sheriff: %s', old_sheriff) logging.info(' New Sheriff: %s', self.sheriff) # If this test is monitored, add it to the monitored list of its test suite. # A test is be monitored iff it has a sheriff, and monitored tests are # tracked in the monitored list of a test suite TestMetadata entity. test_suite = ndb.Key('TestMetadata', '/'.join(path_parts[:3])).get() if self.sheriff: if test_suite and self.key not in test_suite.monitored: test_suite.monitored.append(self.key) test_suite.put() elif test_suite and self.key in test_suite.monitored: test_suite.monitored.remove(self.key) test_suite.put() # Set the anomaly threshold config to the first one that has a test pattern # that matches this test, if there is one. Anomaly configs with a pattern # that more specifically matches the test are given higher priority. # ie. */*/*/foo is chosen over */*/*/* self.overridden_anomaly_config = None anomaly_configs = anomaly_config.AnomalyConfig.query().fetch() anomaly_data_list = [] for e in anomaly_configs: for p in e.patterns: anomaly_data_list.append((p, e)) anomaly_config_to_use = utils.MostSpecificMatchingPattern( self, anomaly_data_list) if anomaly_config_to_use: self.overridden_anomaly_config = anomaly_config_to_use.key
def GetTestsMatchingPattern(pattern, only_with_rows=False, list_entities=False): """Gets the TestMetadata entities or keys which match |pattern|. For this function, it's assumed that a test path should only have up to seven parts. In theory, tests can be arbitrarily nested, but in practice, tests are usually structured as master/bot/suite/graph/trace, and only a few have seven parts. Args: pattern: /-separated string of '*' wildcard and TestMetadata string_ids. only_with_rows: If True, only return TestMetadata entities which have data points. list_entities: If True, return entities. If false, return keys (faster). Returns: A list of test paths, or test entities if list_entities is True. """ property_names = [ 'master_name', 'bot_name', 'suite_name', 'test_part1_name', 'test_part2_name', 'test_part3_name', 'test_part4_name', 'test_part5_name' ] pattern_parts = pattern.split('/') if len(pattern_parts) > 8: return [] # Below, we first build a list of (property_name, value) pairs to filter on. query_filters = [] for index, part in enumerate(pattern_parts): if '*' not in part: query_filters.append((property_names[index], part)) for index in range(len(pattern_parts), 7): # Tests longer than the desired pattern will have non-empty property names, # so they can be filtered out by matching against an empty string. # Bug: 'test_part5_name' was added recently, and TestMetadata entities which # were created before then do not match it. Since it is the last part, and # rarely used, it's okay not to test for it. See # https://github.com/catapult-project/catapult/issues/2885 query_filters.append((property_names[index], '')) # Query tests based on the above filters. Pattern parts with * won't be # filtered here; the set of tests queried is a superset of the matching tests. query = graph_data.TestMetadata.query() for f in query_filters: query = query.filter( # pylint: disable=protected-access graph_data.TestMetadata._properties[f[0]] == f[1]) query = query.order(graph_data.TestMetadata.key) if only_with_rows: query = query.filter(graph_data.TestMetadata.has_rows == True) test_keys = query.fetch(keys_only=True) # Filter to include only tests that match the pattern. test_keys = [k for k in test_keys if utils.TestMatchesPattern(k, pattern)] if list_entities: return ndb.get_multi(test_keys) return [utils.TestPath(k) for k in test_keys]
def GetBugLabelsForTest(test): """Returns a list of bug labels to be applied to the test.""" matching = [] for label, patterns in GetBugLabelPatterns().iteritems(): for pattern in patterns: if utils.TestMatchesPattern(test, pattern): matching.append(label) return sorted(matching)
def UpdateSheriffAsync(self, sheriffs=None, anomaly_configs=None): """This method is called before a TestMetadata is put into the datastore. Here, we check the key to make sure it is valid and check the sheriffs and anomaly configs to make sure they are current. """ # Check to make sure the key is valid. # TestMetadata should not be an ancestor, so key.pairs() should have length # of 1. The id should have at least 3 slashes to represent master/bot/suite. assert len(self.key.pairs()) == 1 path_parts = self.key.id().split('/') assert len(path_parts) >= 3 # Set the sheriff to the first sheriff (alphabetically by sheriff name) # that has a test pattern that matches this test. old_sheriff = self.sheriff old_anomaly_config = self.overridden_anomaly_config if not sheriffs: sheriffs = yield sheriff_module.Sheriff.query().fetch_async() self.sheriff = None for sheriff_entity in sheriffs: for pattern in sheriff_entity.patterns: if utils.TestMatchesPattern(self, pattern): self.sheriff = sheriff_entity.key if self.sheriff: break # Set the anomaly threshold config to the first one that has a test pattern # that matches this test, if there is one. Anomaly configs with a pattern # that more specifically matches the test are given higher priority. # ie. */*/*/foo is chosen over */*/*/* self.overridden_anomaly_config = None if not anomaly_configs: anomaly_configs = yield anomaly_config.AnomalyConfig.query( ).fetch_async() anomaly_data_list = [] for e in anomaly_configs: for p in e.patterns: anomaly_data_list.append((p, e)) anomaly_config_to_use = utils.MostSpecificMatchingPattern( self, anomaly_data_list) if anomaly_config_to_use: self.overridden_anomaly_config = anomaly_config_to_use.key raise ndb.Return( self.sheriff != old_sheriff or self.overridden_anomaly_config != old_anomaly_config)
def GetTestsMatchingPatternAsync(pattern, only_with_rows=False, list_entities=False, use_cache=True): property_names = [ 'master_name', 'bot_name', 'suite_name', 'test_part1_name', 'test_part2_name', 'test_part3_name', 'test_part4_name', 'test_part5_name' ] pattern_parts = pattern.split('/') if len(pattern_parts) > 8: raise ndb.Return([]) # Below, we first build a list of (property_name, value) pairs to filter on. query_filters = [] for index, part in enumerate(pattern_parts): if '*' not in part: query_filters.append((property_names[index], part)) for index in range(len(pattern_parts), 7): # Tests longer than the desired pattern will have non-empty property names, # so they can be filtered out by matching against an empty string. # Bug: 'test_part5_name' was added recently, and TestMetadata entities which # were created before then do not match it. Since it is the last part, and # rarely used, it's okay not to test for it. See # https://github.com/catapult-project/catapult/issues/2885 query_filters.append((property_names[index], '')) # Query tests based on the above filters. Pattern parts with * won't be # filtered here; the set of tests queried is a superset of the matching tests. query = graph_data.TestMetadata.query() for f in query_filters: query = query.filter( # pylint: disable=protected-access graph_data.TestMetadata._properties[f[0]] == f[1]) query = query.order(graph_data.TestMetadata.key) if only_with_rows: query = query.filter(graph_data.TestMetadata.has_rows == True) test_keys = yield query.fetch_async(keys_only=True) # Filter to include only tests that match the pattern. test_keys = [k for k in test_keys if utils.TestMatchesPattern(k, pattern)] if list_entities: result = yield ndb.get_multi_async(test_keys, use_cache=use_cache) raise ndb.Return(result) raise ndb.Return([utils.TestPath(k) for k in test_keys])
def _AssertDoesntMatch(self, test_path, pattern): """Asserts that a test path doesn't match a pattern with MatchesPattern.""" test_key = utils.TestKey(test_path) self.assertFalse(utils.TestMatchesPattern(test_key, pattern))
def _AssertMatches(self, test_path, pattern): """Asserts that a test path matches a pattern with MatchesPattern.""" test_key = utils.TestKey(test_path) self.assertTrue(utils.TestMatchesPattern(test_key, pattern))