test_health_extractor.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. # Lint as: python3
  2. # Copyright 2022 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import dataclasses
  6. import datetime as dt
  7. import logging
  8. import os
  9. import pathlib
  10. import sys
  11. from typing import List, Optional, Set, Tuple, Union
  12. _TOOLS_ANDROID_PATH = pathlib.Path(__file__).resolve(strict=True).parents[1]
  13. if str(_TOOLS_ANDROID_PATH) not in sys.path:
  14. sys.path.append(str(_TOOLS_ANDROID_PATH))
  15. from python_utils import git_metadata_utils
  16. import java_test_utils
  17. _CHROMIUM_SRC_PATH = git_metadata_utils.get_chromium_src_path()
  18. _IGNORED_DIRS = ('out', 'third_party', 'clank', 'build/linux', 'native_client',
  19. 'tools/android/test_health/testdata')
  20. _IGNORED_FILES = set()
  21. @dataclasses.dataclass(frozen=True)
  22. class GitRepoInfo:
  23. """Holder class for Git repository information."""
  24. git_head: str
  25. """The SHA1 hash of the Git repository's commit at HEAD."""
  26. git_head_time: dt.datetime
  27. """The datetime of the Git repository's commit at HEAD."""
  28. @dataclasses.dataclass(frozen=True)
  29. class TestHealthInfo:
  30. """Holder class for test health information about a test class."""
  31. test_name: str
  32. """The name of the test, e.g., the class name of a Java test."""
  33. test_dir: pathlib.Path
  34. """The directory containing the test, relative to the Git repo root."""
  35. test_filename: str
  36. """The filename of the test, e.g., FooJavaTest.java."""
  37. java_test_health: Optional[java_test_utils.JavaTestHealth]
  38. """Java test health info and counters; this is None if not a Java test."""
  39. git_repo_info: GitRepoInfo
  40. """Information about the Git repository being sampled."""
  41. def get_repo_test_health(git_repo: Optional[pathlib.Path] = None,
  42. *,
  43. test_dir: Union[str, pathlib.Path, None] = None,
  44. ignored_dirs: Tuple[str, ...] = _IGNORED_DIRS,
  45. ignored_files: Set[str] = _IGNORED_FILES
  46. ) -> List[TestHealthInfo]:
  47. """Gets test health information and stats for a Git repository.
  48. This function checks for Java tests annotated as disabled or flaky but could
  49. be extended to check other metrics or languages in the future.
  50. Args:
  51. git_repo:
  52. The path to the root of the Git repository being checked; defaults
  53. to the Chromium repo.
  54. test_dir:
  55. The subdirectory, relative to the Git repo root, containing the
  56. tests of interest; defaults to the root of the Git repo.
  57. ignored_dirs:
  58. A list of directories to skip (paths relative to `test_dir`);
  59. defaults to a set of directories that should be ignored in the
  60. Chromium Git repo.
  61. ignored_files:
  62. A set of file paths to skip (relative to `test_dir`); defaults to
  63. files in the Chromium Git repo with unsupported Java syntax.
  64. Returns:
  65. A list of `TestHealthInfo` objects, one for each test file processed.
  66. """
  67. git_repo = git_repo or _CHROMIUM_SRC_PATH
  68. test_dir = test_dir or pathlib.Path('.')
  69. tests_root = (git_repo / test_dir).resolve(strict=True)
  70. repo_info = _get_git_repo_info(git_repo)
  71. test_health_infos: list[TestHealthInfo] = []
  72. for dirpath, _, filenames in os.walk(tests_root):
  73. if os.path.relpath(dirpath, tests_root).startswith(ignored_dirs):
  74. continue
  75. for filename in filenames:
  76. if not filename.endswith('Test.java'):
  77. continue
  78. test_path = pathlib.Path(dirpath) / filename
  79. if os.path.relpath(test_path, tests_root) in ignored_files:
  80. continue
  81. test_health_info = _get_test_health_info(git_repo, test_path,
  82. repo_info)
  83. if test_health_info:
  84. test_health_infos.append(test_health_info)
  85. return test_health_infos
  86. def _get_test_health_info(repo_root: pathlib.Path, test_path: pathlib.Path,
  87. repo_info: GitRepoInfo) -> Optional[TestHealthInfo]:
  88. test_file = test_path.relative_to(repo_root)
  89. try:
  90. test_health_stats = java_test_utils.get_java_test_health(test_path)
  91. except java_test_utils.JavaSyntaxError:
  92. # This can occur if the file uses syntax not supported by the underlying
  93. # javalang python module used by java_test_utils. These files should be
  94. # investigated manually.
  95. logging.warning(f'Skipped file "{test_file}" due to'
  96. ' Java syntax error.')
  97. return None
  98. return TestHealthInfo(test_name=test_file.stem,
  99. test_dir=test_file.parent,
  100. test_filename=test_file.name,
  101. java_test_health=test_health_stats,
  102. git_repo_info=repo_info)
  103. def _get_git_repo_info(git_repo: pathlib.Path) -> GitRepoInfo:
  104. return GitRepoInfo(git_metadata_utils.get_head_commit_hash(git_repo),
  105. git_metadata_utils.get_head_commit_datetime(git_repo))