expectations.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. # Copyright 2021 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Code specific to disabling tests using test expectations files"""
  5. import collections
  6. import os
  7. import sys
  8. from typing import Optional, List
  9. import conditions
  10. from conditions import Condition
  11. import errors
  12. sys.path.append(
  13. os.path.join(os.path.dirname(__file__), '..', '..', 'third_party',
  14. 'catapult', 'third_party', 'typ'))
  15. from typ.expectations_parser import ( # type: ignore
  16. Expectation, TaggedTestListParser)
  17. sys.path.pop()
  18. def search_for_expectations(filename: str, test_name: str) -> str:
  19. # Web test have "virtual test suites", where the same set of tests is run with
  20. # different parameters. These are specified by "VirtualTestSuites" files, but
  21. # generally the way it works is that a test a/b/c.html will have a virtual
  22. # test in virtual/foo/a/b/c.html. So we handle this by stripping parts of the
  23. # path off until the filename and test name match.
  24. while not filename.endswith(test_name) and '/' in test_name:
  25. test_name = test_name[test_name.index('/') + 1:]
  26. # TODO: Is this ever not the case? If so we might need to just keep searching
  27. # upwards, directory by directory until we find a test expectations file
  28. # referencing this test.
  29. assert filename.endswith(test_name)
  30. expectations_dir = filename[:-len(test_name)]
  31. # TODO: I think ASan and some other conditions are handled via different
  32. # files.
  33. expectations_path = os.path.join(expectations_dir, 'TestExpectations')
  34. if os.path.exists(expectations_path):
  35. return expectations_path
  36. raise errors.InternalError("Couldn't find TestExpectations file for test " +
  37. f"{test_name} " +
  38. f"(expected to find it at {expectations_path})")
  39. def disabler(full_test_name: str, source_file: str, new_cond: Condition,
  40. message: Optional[str]) -> str:
  41. comment = None
  42. if message:
  43. comment = f"# {message}"
  44. existing_expectation: Optional[Expectation] = None
  45. condition = conditions.NEVER
  46. for expectation in TaggedTestListParser(source_file).expectations:
  47. if expectation.test == full_test_name:
  48. existing_expectation = expectation
  49. if set(expectation.results) & {'SKIP', 'FAIL'}:
  50. tags = set(expectation.tags)
  51. if not tags:
  52. condition = conditions.ALWAYS
  53. break
  54. merged = conditions.merge(condition, new_cond)
  55. if existing_expectation is None:
  56. ex = Expectation(test=full_test_name,
  57. results=['SKIP'],
  58. tags=condition_to_tags(merged))
  59. while not source_file.endswith('\n\n'):
  60. source_file += '\n'
  61. if comment:
  62. source_file += f"{comment}\n"
  63. source_file += ex.to_string()
  64. return source_file
  65. new_expectation = Expectation(
  66. reason=existing_expectation.reason,
  67. test=existing_expectation.test,
  68. trailing_comments=existing_expectation.trailing_comments,
  69. results=['SKIP'],
  70. tags=condition_to_tags(merged),
  71. )
  72. lines = source_file.split('\n')
  73. # Minus 1 as 'lineno' is 1-based.
  74. lines[existing_expectation.lineno - 1] = new_expectation.to_string()
  75. if comment:
  76. lines.insert(existing_expectation.lineno - 1, comment)
  77. return '\n'.join(lines)
  78. ExpectationsInfo = collections.namedtuple('ExpectationsInfo', 'tag')
  79. for t_name, t_tag in [
  80. ('android', 'Android'),
  81. ('fuchsia', 'Fuchsia'),
  82. ('linux', 'Linux'),
  83. ('mac', 'Mac'),
  84. ('win', 'Win'),
  85. ]:
  86. conditions.get_term(t_name).expectations_info = ExpectationsInfo(tag=t_tag)
  87. def condition_to_tags(cond: Condition) -> List[str]:
  88. if cond is conditions.ALWAYS:
  89. return []
  90. assert not isinstance(cond, conditions.BaseCondition)
  91. if isinstance(cond, conditions.Terminal):
  92. return [conditions.get_term(cond.name).expectations_info.tag]
  93. op, args = cond
  94. if op == 'or':
  95. return [tag for arg in args for tag in condition_to_tags(arg)]
  96. raise errors.InternalError(
  97. f"Unable to express condition in test expectations format: {cond}")