blob: 5113ab98d00f3462bcc1032cc69c0a35c17342c3 [file] [log] [blame]
# Copyright 2025 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import os.path
import sys
import tempfile
import unittest
# Force local directory to be in front of sys.path to avoid importing different
# version of PRESUBMIT.py which can be added in different files within python
# invocation.
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import PRESUBMIT
from presubmit_caching_support import PresubmitCache
# Append chrome source root to import `PRESUBMIT_test_mocks.py`.
sys.path.append(
os.path.dirname(
os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
from PRESUBMIT_test_mocks import MockAffectedFile, MockInputApi, MockOutputApi
_BASE_DIR = os.path.dirname(os.path.abspath(__file__))
_TOP_LEVEL_ENUMS_PATH = (f'{os.path.dirname(__file__)}/enums.xml')
def _TempCacheFile():
file_handle, file_path = tempfile.mkstemp(suffix='.json', text=True)
os.close(file_handle)
return file_path
def _MockInputFromTestFile(relative_path: str) -> (MockInputApi, str):
""" Returns a MockInputApi that list a file relative to test_data/ as changed.
The provided file is read and its contents are provided to the MockInputApi.
Args:
relative_path: The relative path to the file to mock.
Returns:
A MockInputApi that lists the provided file as only one changed and full
path to that file.
"""
full_path = f'{os.path.dirname(__file__)}/test_data/{relative_path}'
with open(full_path, 'r') as f:
contents = f.read()
mock_input_api = MockInputApi()
mock_input_api.presubmit_local_path = _BASE_DIR
mock_input_api.files = [
MockAffectedFile(full_path, [contents]),
]
return (mock_input_api, full_path)
def _MockInputFromString(path: str, contents: str) -> MockInputApi:
""" Returns a MockInputApi with single changed file with given contents.Api.
Args:
path: Fake path to the file to mock.
contents: The contents of the file to mock.
Returns:
A MockInputApi that lists the provided file as only one changed.
"""
mock_input_api = MockInputApi()
mock_input_api.presubmit_local_path = _BASE_DIR
mock_input_api.files = [
MockAffectedFile(path, [contents]),
]
return mock_input_api
class MetricsPresubmitTest(unittest.TestCase):
def testCheckHistogramFormattingFailureIsDetected(self):
(mock_input_api, malformed_histograms_path
) = _MockInputFromTestFile('tokens/token_errors_histograms.xml')
results = PRESUBMIT.ExecuteCheckHistogramFormatting(
mock_input_api,
MockOutputApi(),
allow_test_paths=True,
xml_paths_override=[malformed_histograms_path, _TOP_LEVEL_ENUMS_PATH])
self.assertEqual(len(results), 2)
self.assertEqual(results[0].type, 'error')
self.assertRegex(
results[0].message,
'.*histograms.xml contains histogram.* using <variants> not defined in'
' the file, please run validate_token.py .*histograms.xml to fix.')
# validate_format.py also reports errors when the variants are not defined
# in the file, hence there is a second error from the same check.
self.assertEqual(results[1].type, 'error')
self.assertRegex(
results[1].message,
'Histograms are not well-formatted; please run .*validate_format.py and'
' fix the reported errors.')
def testCheckWebViewHistogramsAllowlistOnUploadFailureIsDetected(self):
valid_enums_path = (f'{os.path.dirname(__file__)}'
'/test_data'
'/example_valid_enums.xml')
example_allowlist_path = (f'{os.path.dirname(__file__)}'
'/test_data'
'/AllowlistExample.java')
(mock_input_api, missing_allow_list_entries_histograms_path
) = _MockInputFromTestFile('no_allowlist_entries_histograms.xml')
results = PRESUBMIT.ExecuteCheckWebViewHistogramsAllowlistOnUpload(
mock_input_api,
MockOutputApi(),
allowlist_path_override=example_allowlist_path,
xml_paths_override=[
missing_allow_list_entries_histograms_path, valid_enums_path,
_TOP_LEVEL_ENUMS_PATH
],
)
self.assertEqual(len(results), 1)
self.assertRegex(
results[0].message.replace('\n', ' '), 'All histograms in'
' .*AllowlistExample.java must be valid.')
self.assertEqual(results[0].type, 'error')
def testCheckBooleansAreEnumsFailureIsDetected(self):
mock_input_api = _MockInputFromString(
'histograms.xml', '<histogram name="Foo" units="Boolean" />')
results = PRESUBMIT.ExecuteCheckBooleansAreEnums(mock_input_api,
MockOutputApi())
self.assertEqual(len(results), 1)
self.assertRegex(
results[0].message.replace('\n', ' '),
'.*You are using .units. for a boolean histogram, but you should be'
' using\\s+.enum. instead\\.')
self.assertEqual(results[0].type, 'promptOrNotify')
def testCheckHistogramFormattingPasses(self):
valid_enums_path = (f'{os.path.dirname(__file__)}'
'/test_data'
'/example_valid_enums.xml')
(mock_input_api, valid_histograms_path
) = _MockInputFromTestFile('example_valid_histograms.xml')
results = PRESUBMIT.ExecuteCheckHistogramFormatting(
mock_input_api,
MockOutputApi(),
allow_test_paths=True,
xml_paths_override=[
valid_histograms_path, valid_enums_path, _TOP_LEVEL_ENUMS_PATH
])
# Zero results mean that there were no errors reported.
self.assertEqual(len(results), 0)
def testCheckWebViewHistogramsAllowlistOnUploadPasses(self):
valid_enums_path = (f'{os.path.dirname(__file__)}'
'/test_data'
'/example_valid_enums.xml')
example_allowlist_path = (f'{os.path.dirname(__file__)}'
'/test_data'
'/AllowlistExample.java')
(mock_input_api, valid_histograms_path
) = _MockInputFromTestFile('example_valid_histograms.xml')
results = PRESUBMIT.ExecuteCheckWebViewHistogramsAllowlistOnUpload(
mock_input_api,
MockOutputApi(),
allowlist_path_override=example_allowlist_path,
xml_paths_override=[
valid_histograms_path, valid_enums_path, _TOP_LEVEL_ENUMS_PATH
])
# Zero results mean that there were no errors reported.
self.assertEqual(len(results), 0)
def testCheckBooleansAreEnumsPasses(self):
mock_input_api = _MockInputFromString(
'histograms.xml', '<histogram name="Foo" enum="Boolean" />')
results = PRESUBMIT.ExecuteCheckBooleansAreEnums(mock_input_api,
MockOutputApi())
# Zero results mean that there were no errors reported.
self.assertEqual(len(results), 0)
def _CacheSize(self, cache_file_path, observed_directory_path):
cache = PresubmitCache(cache_file_path, observed_directory_path)
return len(cache.InspectCacheForTesting().data)
def testSecondCheckOnTheSameDataReturnsSameResult(self):
test_cache_file = _TempCacheFile()
mock_input_api = _MockInputFromString(
'histograms.xml', '<histogram name="Foo" units="Boolean" />')
# The cache should be empty before we run any presubmit checks.
self.assertEqual(self._CacheSize(test_cache_file, _BASE_DIR), 0)
results = PRESUBMIT.CheckBooleansAreEnums(mock_input_api,
MockOutputApi(),
cache_file_path=test_cache_file)
self.assertEqual(len(results), 1)
self.assertRegex(
results[0].message.replace('\n', ' '),
'.*You are using .units. for a boolean histogram, but you should be'
' using\\s+.enum. instead\\.')
self.assertEqual(results[0].type, 'promptOrNotify')
# The cache should now store a single entry for the check above.
self.assertEqual(self._CacheSize(test_cache_file, _BASE_DIR), 1)
second_results = PRESUBMIT.CheckBooleansAreEnums(
mock_input_api, MockOutputApi(), cache_file_path=test_cache_file)
self.assertEqual(len(second_results), 1)
self.assertEqual(results[0].message, second_results[0].message)
self.assertEqual(results[0].type, second_results[0].type)
# The check result should be retrieved from the cache and the cache should
# still have only one entry.
self.assertEqual(self._CacheSize(test_cache_file, _BASE_DIR), 1)
def testSecondCheckOnTheSameDataReturnsSameEmptyResult(self):
test_cache_file = _TempCacheFile()
mock_input_api = _MockInputFromString(
'histograms.xml', '<histogram name="Foo" enum="Boolean" />')
# The cache should be empty before we run any presubmit checks.
self.assertEqual(self._CacheSize(test_cache_file, _BASE_DIR), 0)
results = PRESUBMIT.CheckBooleansAreEnums(mock_input_api,
MockOutputApi(),
cache_file_path=test_cache_file)
# Zero results mean that there were no errors reported.
self.assertEqual(len(results), 0)
# The cache should now store a single entry for the check above.
self.assertEqual(self._CacheSize(test_cache_file, _BASE_DIR), 1)
second_results = PRESUBMIT.CheckBooleansAreEnums(
mock_input_api, MockOutputApi(), cache_file_path=test_cache_file)
# Zero results mean that there were no errors reported.
self.assertEqual(len(second_results), 0)
# The check result should be retrieved from the cache and the cache should
# still have only one entry.
self.assertEqual(self._CacheSize(test_cache_file, _BASE_DIR), 1)
def testFailureInModifiedFileIsDetected(self):
test_dir = tempfile.mkdtemp()
initial_histograms_content = '<histogram name="Foo" enum="Boolean" />'
modified_histograms_content = '<histogram name="Foo" units="Boolean" />'
histograms_path = os.path.join(test_dir, 'histograms.xml')
with open(histograms_path, 'w') as f:
f.write(initial_histograms_content)
test_cache_file = _TempCacheFile()
mock_input_api = MockInputApi()
mock_input_api.presubmit_local_path = test_dir
mock_input_api.files = [
MockAffectedFile('histograms.xml', [initial_histograms_content]),
]
# The cache should be empty before we run any presubmit checks.
self.assertEqual(self._CacheSize(test_cache_file, test_dir), 0)
results = PRESUBMIT.CheckBooleansAreEnums(mock_input_api,
MockOutputApi(),
cache_file_path=test_cache_file)
# Zero results mean that there were no errors reported.
self.assertEqual(len(results), 0)
# The cache should now store a single entry for the check above.
self.assertEqual(self._CacheSize(test_cache_file, test_dir), 1)
with open(histograms_path, 'w') as f:
f.write(modified_histograms_content)
mock_input_api.files = [
MockAffectedFile('histograms.xml', [modified_histograms_content]),
]
second_results = PRESUBMIT.CheckBooleansAreEnums(
mock_input_api, MockOutputApi(), cache_file_path=test_cache_file)
self.assertEqual(len(second_results), 1)
self.assertRegex(
second_results[0].message.replace('\n', ' '),
'.*You are using .units. for a boolean histogram, but you should be'
' using\\s+.enum. instead\\.')
self.assertEqual(second_results[0].type, 'promptOrNotify')
# The cache should now have an extra entry as the second check was done on
# a different version of the file.
self.assertEqual(self._CacheSize(test_cache_file, test_dir), 2)
def testRegisteredVariantsArePassingValidation(self):
valid_tokens_histograms_relative_paths = [
'tokens/variants_inline_histograms.xml',
'tokens/variants_out_of_line_explicit_histograms.xml',
'tokens/variants_out_of_line_implicit_histograms.xml',
]
for relative_path in valid_tokens_histograms_relative_paths:
(mock_input_api, input_path) = _MockInputFromTestFile(relative_path)
results = PRESUBMIT.ExecuteCheckHistogramFormatting(
mock_input_api,
MockOutputApi(),
allow_test_paths=True,
xml_paths_override=[input_path])
self.assertEqual(len(results), 0)
def testNonregisteredVariantsAreFailingValidation(self):
(mock_input_api, input_path
) = _MockInputFromTestFile('tokens/variants_missing_histograms.xml')
results = PRESUBMIT.ExecuteCheckHistogramFormatting(
mock_input_api,
MockOutputApi(),
allow_test_paths=True,
xml_paths_override=[input_path, _TOP_LEVEL_ENUMS_PATH])
self.assertEqual(len(results), 1)
# validate_format.py also reports all errors as Histogram malformatted
# errors, detailed errors are printed and the error itself refers back
# to validate_format.py.
self.assertEqual(results[0].type, 'error')
self.assertRegex(
results[0].message,
'Histograms are not well-formatted; please run .*validate_format.py and'
' fix the reported errors.')
def testDeletedFileIsIgnoredByBooleansAreEnumsCheck(self):
mock_input_api = MockInputApi()
mock_input_api.presubmit_local_path = _BASE_DIR
mock_input_api.files = [
MockAffectedFile('histograms.xml',
['<histogram name="Foo" units="Boolean" />'],
action='D'),
]
results = PRESUBMIT.ExecuteCheckBooleansAreEnums(mock_input_api,
MockOutputApi())
# Zero results mean that there were no errors reported.
self.assertEqual(len(results), 0)
def testDeletedFileIsIgnoredByHistogramFormattingCheck(self):
full_path = (f'{os.path.dirname(__file__)}'
'/test_data/non_existing_histograms.xml')
mock_input_api = MockInputApi()
mock_input_api.presubmit_local_path = _BASE_DIR
mock_input_api.files = [
MockAffectedFile(full_path, [], action='D'),
]
results = PRESUBMIT.ExecuteCheckHistogramFormatting(
mock_input_api,
MockOutputApi(),
allow_test_paths=True,
xml_paths_override=[_TOP_LEVEL_ENUMS_PATH])
# Zero results mean that there were no errors reported.
self.assertEqual(len(results), 0)
def testDeletedFileIsIgnoredByAllowlistCheck(self):
non_existing_histograms_path = (f'{os.path.dirname(__file__)}'
'/test_data/non_existing_histograms.xml')
valid_histograms_path = (f'{os.path.dirname(__file__)}'
'/test_data'
'/example_valid_histograms.xml')
valid_enums_path = (f'{os.path.dirname(__file__)}'
'/test_data'
'/example_valid_enums.xml')
example_allowlist_path = (f'{os.path.dirname(__file__)}'
'/test_data'
'/AllowlistExample.java')
with open(valid_histograms_path, 'r') as f:
valid_histograms_contents = f.read()
mock_input_api = MockInputApi()
mock_input_api.presubmit_local_path = _BASE_DIR
mock_input_api.files = [
MockAffectedFile(non_existing_histograms_path, [], action='D'),
MockAffectedFile(valid_histograms_path, [valid_histograms_contents]),
]
results = PRESUBMIT.ExecuteCheckWebViewHistogramsAllowlistOnUpload(
mock_input_api,
MockOutputApi(),
allowlist_path_override=example_allowlist_path,
xml_paths_override=[
valid_histograms_path, valid_enums_path, _TOP_LEVEL_ENUMS_PATH
])
# Zero results mean that there were no errors reported.
self.assertEqual(len(results), 0)
if __name__ == '__main__':
unittest.main()