| #!/usr/bin/env python3 | |
| # Copyright (C) 2023 The Android Open Source Project | |
| # | |
| # 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. | |
| import os | |
| import re | |
| from dataclasses import dataclass | |
| from enum import Enum | |
| from typing import List, Optional, Tuple | |
| from .testing import DiffTestBlueprint | |
| TestName = str | |
| @dataclass | |
| class DiscoveredTests: | |
| """In-memory database of all discovered tests.""" | |
| # All tests which match the given name filter and module constraints. | |
| runnable: List['TestCase'] | |
| # All tests which are skipped because they don't match the name filter. | |
| skipped_name_filter: List[str] | |
| # All tests which are skipped due to a missing module. | |
| skipped_module_missing: List[Tuple[str, str]] | |
| @dataclass | |
| class Config: | |
| """Configuration for the diff test runner.""" | |
| name_filter: str | |
| trace_processor_path: str | |
| trace_descriptor: str | |
| no_colors: bool | |
| override_sql_package_paths: List[str] | |
| test_dir: str | |
| quiet: bool | |
| summary_descriptor: str | |
| metrics_descriptor_paths: List[str] | |
| chrome_extensions: str | |
| test_extensions: str | |
| winscope_extensions: str | |
| gpu_extensions: str | |
| gpu_interned_data_extensions: str | |
| simpleperf_descriptor: str | |
| keep_input: bool | |
| print_slowest_tests: bool | |
| jobs: int = 0 # Number of parallel jobs. 0 means use all available CPUs. | |
| class TestType(Enum): | |
| """The type of the diff test.""" | |
| QUERY = 1 | |
| METRIC = 2 | |
| METRIC_V2 = 3 | |
| STRUCTURED_QUERY = 4 | |
| @dataclass | |
| class TestCase: | |
| """Description of a diff test.""" | |
| name: str | |
| blueprint: DiffTestBlueprint | |
| query_path: Optional[str] | |
| trace_path: Optional[str] | |
| expected_path: Optional[str] | |
| expected_str: str | |
| register_files_dir: Optional[str] | |
| type: TestType | |
| spec_file_path: Optional[str] = None | |
| spec_textproto: Optional[str] = None | |
| def validate(self, name_filter: str): | |
| query_metric_pattern = re.compile(name_filter) | |
| return bool(query_metric_pattern.match(os.path.basename(self.name))) | |
| @dataclass | |
| class PerfResult: | |
| """Performance result of running the test.""" | |
| test: 'TestCase' | |
| ingest_time_ns: int | |
| real_time_ns: int | |
| def __init__(self, test: 'TestCase', perf_lines: List[str]): | |
| self.test = test | |
| assert len(perf_lines) == 1 | |
| perf_numbers = perf_lines[0].split(',') | |
| assert len(perf_numbers) == 2 | |
| self.ingest_time_ns = int(perf_numbers[0]) | |
| self.real_time_ns = int(perf_numbers[1]) | |
| @dataclass | |
| class TestResult: | |
| """Data gathered from running the test.""" | |
| test: 'TestCase' | |
| trace: str | |
| cmd: List[str] | |
| expected: str | |
| actual: str | |
| passed: bool | |
| stderr: str | |
| exit_code: int | |
| perf_result: Optional[PerfResult] | |
| def __init__( | |
| self, | |
| test: 'TestCase', | |
| gen_trace_path: str, | |
| cmd: List[str], | |
| expected_text: str, | |
| actual_text: str, | |
| stderr: str, | |
| exit_code: int, | |
| perf_lines: List[str], | |
| ) -> None: | |
| self.test = test | |
| self.trace = gen_trace_path | |
| self.cmd = cmd | |
| self.stderr = stderr | |
| self.exit_code = exit_code | |
| # For better string formatting we often add whitespaces, which has to now | |
| # be removed. | |
| def strip_whitespaces(text: str): | |
| no_front_new_line_text = text.lstrip('\n') | |
| return '\n'.join(s.strip() for s in no_front_new_line_text.split('\n')) | |
| self.expected = strip_whitespaces(expected_text) | |
| self.actual = strip_whitespaces(actual_text) | |
| expected_content = self.expected.replace('\r\n', '\n') | |
| actual_content = self.actual.replace('\r\n', '\n') | |
| if test.blueprint.is_out_expected_error(): | |
| # The test expects the trace load to fail: pass iff trace_processor | |
| # exited with an error and stderr contains the expected message. | |
| self.passed = (self.exit_code != 0 and expected_content in self.stderr) | |
| else: | |
| self.passed = (expected_content == actual_content) | |
| if self.exit_code == 0: | |
| self.perf_result = PerfResult(self.test, perf_lines) | |
| else: | |
| self.perf_result = None |