132 lines
4.7 KiB
Python
132 lines
4.7 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
# Copyright 2010-2025 Google LLC
|
||
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
# you may not use this file except in compliance with the License.
|
||
|
|
# You may obtain a copy of the License at
|
||
|
|
#
|
||
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
#
|
||
|
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
# See the License for the specific language governing permissions and
|
||
|
|
# limitations under the License.
|
||
|
|
|
||
|
|
"""Base class for binary tests."""
|
||
|
|
|
||
|
|
from collections.abc import Sequence
|
||
|
|
import re
|
||
|
|
import subprocess
|
||
|
|
|
||
|
|
from absl.testing import absltest
|
||
|
|
from tools.testing import bintest_matchers
|
||
|
|
from tools.testing import bintest_run_utils
|
||
|
|
|
||
|
|
|
||
|
|
class BinaryTestCase(absltest.TestCase):
|
||
|
|
"""Base class for testing binaries.
|
||
|
|
|
||
|
|
Provides utility methods for running binaries, getting paths to data
|
||
|
|
dependencies (defined in the `named_data` attribute of the build rule),
|
||
|
|
and extracting information from logs.
|
||
|
|
"""
|
||
|
|
|
||
|
|
def assert_binary_succeeds(self, binary: str, *args: str) -> str:
|
||
|
|
"""Runs a command, expanding $(label) expressions, and returns the output.
|
||
|
|
|
||
|
|
`binary` and `args` can contain expressions of the form `$(label)`. These
|
||
|
|
expressions are replaced by the absolute path of the data dependency
|
||
|
|
associated with `label` in the `named_data` attribute of the build rule.
|
||
|
|
|
||
|
|
See `bintest_run_utils.run` for more details.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
binary: The path to the binary to execute.
|
||
|
|
*args: Arguments to pass to the command.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
The standard output of the command execution.
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
ValueError: If any label expansion fails.
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
return bintest_run_utils.run(binary, *args)
|
||
|
|
except subprocess.CalledProcessError as e:
|
||
|
|
self.fail(
|
||
|
|
f"Expected {binary!r} to succeed but it failed with a return code"
|
||
|
|
f" {e.returncode}.\n"
|
||
|
|
f"The expanded command line was: {e.cmd!r}.\n"
|
||
|
|
f"The standard error was: {e.stderr!r}.\n"
|
||
|
|
f"The standard output was: {e.stdout!r}.\n"
|
||
|
|
)
|
||
|
|
|
||
|
|
def assert_binary_fails(
|
||
|
|
self, binary: str, *args: str
|
||
|
|
) -> subprocess.CalledProcessError:
|
||
|
|
"""Runs a command, expanding $(label) expressions, and asserts that it fails.
|
||
|
|
|
||
|
|
`binary` and `args` can contain expressions of the form `$(label)`. These
|
||
|
|
expressions are replaced by the absolute path of the data dependency
|
||
|
|
associated with `label` in the `named_data` attribute of the build rule.
|
||
|
|
|
||
|
|
See `bintest_run_utils.run` for more details.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
binary: The path to the binary to execute.
|
||
|
|
*args: Arguments to pass to the command.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
The subprocess.CalledProcessError raised by the command execution.
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
ValueError: If any label expansion fails.
|
||
|
|
"""
|
||
|
|
with self.assertRaises(subprocess.CalledProcessError) as e:
|
||
|
|
bintest_run_utils.run(binary, *args)
|
||
|
|
return e.exception
|
||
|
|
|
||
|
|
def assert_extract(self, log: str, *patterns: str) -> Sequence[float]:
|
||
|
|
"""Extracts floating point numbers from a log string based on patterns.
|
||
|
|
|
||
|
|
Patterns are applied sequentially. Each pattern can contain "@num()"
|
||
|
|
placeholders, which are replaced by a regex to capture floating-point
|
||
|
|
numbers.
|
||
|
|
|
||
|
|
See `bintest_matchers.extract` for more details.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
log: The log string to search within.
|
||
|
|
*patterns: Pattern strings to search for sequentially.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
A tuple of floating point numbers extracted from the log, in the order of
|
||
|
|
their `@num()` matchers in `patterns`.
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
AssertionError: If any pattern is not found or if number extraction fails.
|
||
|
|
"""
|
||
|
|
try:
|
||
|
|
return bintest_matchers.extract(log, *patterns, check_matcher_specs=False)
|
||
|
|
except bintest_matchers.MatchError as e:
|
||
|
|
self.fail(e)
|
||
|
|
|
||
|
|
def assert_has_line_with_prefixed_number(self, prefix: str, log: str) -> float:
|
||
|
|
"""Extracts a floating point number from a log line starting with a prefix.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
prefix: The prefix of the line to search for.
|
||
|
|
log: The log string to search within.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
The floating point number extracted from the line.
|
||
|
|
"""
|
||
|
|
regex = re.escape(prefix) + " *" + bintest_matchers.Re.NUMBER
|
||
|
|
if match := re.search(regex, log):
|
||
|
|
return float(match[1])
|
||
|
|
self.fail(f"No line starting with {prefix!r} found in {log!r}")
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> None:
|
||
|
|
absltest.main()
|