blob: 8b9c4ab8d36c2bbd081ff8deccef1f5d993d13ae [file] [log] [blame]
# Copyright 2019 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
import argparse
import fnmatch
import re
import attr
# TODO(crbug.com/1057298) Recipes can actually have '.' characters in their name
# and this will fail to get the recipe name. Recipes should be restricted from
# having '.' characters in their name.
def split(test_name):
"""Split a fully-qualified test name.
Returns:
A tuple: (recipe name, simple test name)
"""
recipe, simple_test_name = test_name.split('.', 1)
return recipe, simple_test_name
@attr.s
class Filter:
"""Filter is an argparse `type` which collects --filter arguments from the
CLI into a usable filter object.
"""
# TODO: Also track module names indicated by the filters.
# TODO: Upstream this into RecipeDeps so that it will only scan the
# modules/recipes that we're interested in.
_recipe_patterns : list[str] = attr.ib(default=[])
_full_test_name_patterns : list[str] = attr.ib(default=[])
_compiled_recipe_pattern : str = attr.ib(default=None)
_compiled_test_name_pattern : str = attr.ib(default=None)
def append(self, filt: str):
"""Argparse calls this function with each argument to --filter on the
command line."""
if not filt:
raise argparse.ArgumentTypeError('empty --filter values are not allowed')
# filters missing a test_name portion imply that it is a recipe prefix and we
# should run all tests for any recipes which match.
filt = filt if '.' in filt else filt+'*.*'
self._recipe_patterns.append(fnmatch.translate(split(filt)[0]))
self._full_test_name_patterns.append(fnmatch.translate(filt))
def __bool__(self):
"""Returns True if this object has any filter patterns."""
# NOTE: self._recipe_patterns implies that self._full_test_name_patterns
# also has values.
return bool(self._recipe_patterns)
def recipe_name(self, recipe_name: str) -> bool:
"""Returns True if `recipe_name` matches the accumulated filter state.
Note that a complete absence of --filter arguments will always return True.
"""
if not self._recipe_patterns:
return True
if self._compiled_recipe_pattern is None:
self._compiled_recipe_pattern = re.compile('|'.join(self._recipe_patterns))
return self._compiled_recipe_pattern.match(recipe_name)
def full_name(self, test_name: str) -> bool:
"""Returns True if `test_name` matches the accumulated filter state.
Note that a complete absence of --filter arguments will always return True.
"""
if not self._full_test_name_patterns:
return True
if self._compiled_test_name_pattern is None:
self._compiled_test_name_pattern = re.compile('|'.join(self._full_test_name_patterns))
return self._compiled_test_name_pattern.match(test_name)