xref: /aosp_15_r20/external/bazelbuild-rules_python/tests/integration/runner.py (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1*60517a1eSAndroid Build Coastguard Worker# Copyright 2024 The Bazel Authors. All rights reserved.
2*60517a1eSAndroid Build Coastguard Worker#
3*60517a1eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*60517a1eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*60517a1eSAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*60517a1eSAndroid Build Coastguard Worker#
7*60517a1eSAndroid Build Coastguard Worker#    http://www.apache.org/licenses/LICENSE-2.0
8*60517a1eSAndroid Build Coastguard Worker#
9*60517a1eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*60517a1eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*60517a1eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*60517a1eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*60517a1eSAndroid Build Coastguard Worker# limitations under the License.
14*60517a1eSAndroid Build Coastguard Worker
15*60517a1eSAndroid Build Coastguard Workerimport logging
16*60517a1eSAndroid Build Coastguard Workerimport os
17*60517a1eSAndroid Build Coastguard Workerimport os.path
18*60517a1eSAndroid Build Coastguard Workerimport pathlib
19*60517a1eSAndroid Build Coastguard Workerimport re
20*60517a1eSAndroid Build Coastguard Workerimport shlex
21*60517a1eSAndroid Build Coastguard Workerimport subprocess
22*60517a1eSAndroid Build Coastguard Workerimport unittest
23*60517a1eSAndroid Build Coastguard Worker
24*60517a1eSAndroid Build Coastguard Worker_logger = logging.getLogger(__name__)
25*60517a1eSAndroid Build Coastguard Worker
26*60517a1eSAndroid Build Coastguard Workerclass ExecuteError(Exception):
27*60517a1eSAndroid Build Coastguard Worker    def __init__(self, result):
28*60517a1eSAndroid Build Coastguard Worker        self.result = result
29*60517a1eSAndroid Build Coastguard Worker    def __str__(self):
30*60517a1eSAndroid Build Coastguard Worker        return self.result.describe()
31*60517a1eSAndroid Build Coastguard Worker
32*60517a1eSAndroid Build Coastguard Workerclass ExecuteResult:
33*60517a1eSAndroid Build Coastguard Worker    def __init__(
34*60517a1eSAndroid Build Coastguard Worker        self,
35*60517a1eSAndroid Build Coastguard Worker        args: list[str],
36*60517a1eSAndroid Build Coastguard Worker        env: dict[str, str],
37*60517a1eSAndroid Build Coastguard Worker        cwd: pathlib.Path,
38*60517a1eSAndroid Build Coastguard Worker        proc_result: subprocess.CompletedProcess,
39*60517a1eSAndroid Build Coastguard Worker    ):
40*60517a1eSAndroid Build Coastguard Worker        self.args = args
41*60517a1eSAndroid Build Coastguard Worker        self.env = env
42*60517a1eSAndroid Build Coastguard Worker        self.cwd = cwd
43*60517a1eSAndroid Build Coastguard Worker        self.exit_code = proc_result.returncode
44*60517a1eSAndroid Build Coastguard Worker        self.stdout = proc_result.stdout
45*60517a1eSAndroid Build Coastguard Worker        self.stderr = proc_result.stderr
46*60517a1eSAndroid Build Coastguard Worker
47*60517a1eSAndroid Build Coastguard Worker    def describe(self) -> str:
48*60517a1eSAndroid Build Coastguard Worker        env_lines = [
49*60517a1eSAndroid Build Coastguard Worker            "  " + shlex.quote(f"{key}={value}")
50*60517a1eSAndroid Build Coastguard Worker            for key, value in sorted(self.env.items())
51*60517a1eSAndroid Build Coastguard Worker        ]
52*60517a1eSAndroid Build Coastguard Worker        env = " \\\n".join(env_lines)
53*60517a1eSAndroid Build Coastguard Worker        args = shlex.join(self.args)
54*60517a1eSAndroid Build Coastguard Worker        maybe_stdout_nl = "" if self.stdout.endswith("\n") else "\n"
55*60517a1eSAndroid Build Coastguard Worker        maybe_stderr_nl = "" if self.stderr.endswith("\n") else "\n"
56*60517a1eSAndroid Build Coastguard Worker        return f"""\
57*60517a1eSAndroid Build Coastguard WorkerCOMMAND:
58*60517a1eSAndroid Build Coastguard Workercd {self.cwd} && \\
59*60517a1eSAndroid Build Coastguard Workerenv \\
60*60517a1eSAndroid Build Coastguard Worker{env} \\
61*60517a1eSAndroid Build Coastguard Worker  {args}
62*60517a1eSAndroid Build Coastguard WorkerRESULT: exit_code: {self.exit_code}
63*60517a1eSAndroid Build Coastguard Worker===== STDOUT START =====
64*60517a1eSAndroid Build Coastguard Worker{self.stdout}{maybe_stdout_nl}===== STDOUT END   =====
65*60517a1eSAndroid Build Coastguard Worker===== STDERR START =====
66*60517a1eSAndroid Build Coastguard Worker{self.stderr}{maybe_stderr_nl}===== STDERR END   =====
67*60517a1eSAndroid Build Coastguard Worker"""
68*60517a1eSAndroid Build Coastguard Worker
69*60517a1eSAndroid Build Coastguard Worker
70*60517a1eSAndroid Build Coastguard Workerclass TestCase(unittest.TestCase):
71*60517a1eSAndroid Build Coastguard Worker    def setUp(self):
72*60517a1eSAndroid Build Coastguard Worker        super().setUp()
73*60517a1eSAndroid Build Coastguard Worker        self.repo_root = pathlib.Path(os.environ["BIT_WORKSPACE_DIR"])
74*60517a1eSAndroid Build Coastguard Worker        self.bazel = pathlib.Path(os.environ["BIT_BAZEL_BINARY"])
75*60517a1eSAndroid Build Coastguard Worker        outer_test_tmpdir = pathlib.Path(os.environ["TEST_TMPDIR"])
76*60517a1eSAndroid Build Coastguard Worker        self.test_tmp_dir = outer_test_tmpdir / "bit_test_tmp"
77*60517a1eSAndroid Build Coastguard Worker        # Put the global tmp not under the test tmp to better match how a real
78*60517a1eSAndroid Build Coastguard Worker        # execution has entirely different directories for these.
79*60517a1eSAndroid Build Coastguard Worker        self.tmp_dir = outer_test_tmpdir / "bit_tmp"
80*60517a1eSAndroid Build Coastguard Worker        self.bazel_env = {
81*60517a1eSAndroid Build Coastguard Worker            "PATH": os.environ["PATH"],
82*60517a1eSAndroid Build Coastguard Worker            "TEST_TMPDIR": str(self.test_tmp_dir),
83*60517a1eSAndroid Build Coastguard Worker            "TMP": str(self.tmp_dir),
84*60517a1eSAndroid Build Coastguard Worker            # For some reason, this is necessary for Bazel 6.4 to work.
85*60517a1eSAndroid Build Coastguard Worker            # If not present, it can't find some bash helpers in @bazel_tools
86*60517a1eSAndroid Build Coastguard Worker            "RUNFILES_DIR": os.environ["TEST_SRCDIR"]
87*60517a1eSAndroid Build Coastguard Worker        }
88*60517a1eSAndroid Build Coastguard Worker
89*60517a1eSAndroid Build Coastguard Worker    def run_bazel(self, *args: str, check: bool = True) -> ExecuteResult:
90*60517a1eSAndroid Build Coastguard Worker        """Run a bazel invocation.
91*60517a1eSAndroid Build Coastguard Worker
92*60517a1eSAndroid Build Coastguard Worker        Args:
93*60517a1eSAndroid Build Coastguard Worker            *args: The args to pass to bazel; the leading `bazel` command is
94*60517a1eSAndroid Build Coastguard Worker                added automatically
95*60517a1eSAndroid Build Coastguard Worker            check: True if the execution must succeed, False if failure
96*60517a1eSAndroid Build Coastguard Worker                should raise an error.
97*60517a1eSAndroid Build Coastguard Worker        Returns:
98*60517a1eSAndroid Build Coastguard Worker            An `ExecuteResult` from running Bazel
99*60517a1eSAndroid Build Coastguard Worker        """
100*60517a1eSAndroid Build Coastguard Worker        args = [str(self.bazel), *args]
101*60517a1eSAndroid Build Coastguard Worker        env = self.bazel_env
102*60517a1eSAndroid Build Coastguard Worker        _logger.info("executing: %s", shlex.join(args))
103*60517a1eSAndroid Build Coastguard Worker        cwd = self.repo_root
104*60517a1eSAndroid Build Coastguard Worker        proc_result = subprocess.run(
105*60517a1eSAndroid Build Coastguard Worker            args=args,
106*60517a1eSAndroid Build Coastguard Worker            text=True,
107*60517a1eSAndroid Build Coastguard Worker            capture_output=True,
108*60517a1eSAndroid Build Coastguard Worker            cwd=cwd,
109*60517a1eSAndroid Build Coastguard Worker            env=env,
110*60517a1eSAndroid Build Coastguard Worker            check=False,
111*60517a1eSAndroid Build Coastguard Worker        )
112*60517a1eSAndroid Build Coastguard Worker        exec_result = ExecuteResult(args, env, cwd, proc_result)
113*60517a1eSAndroid Build Coastguard Worker        if check and exec_result.exit_code:
114*60517a1eSAndroid Build Coastguard Worker            raise ExecuteError(exec_result)
115*60517a1eSAndroid Build Coastguard Worker        else:
116*60517a1eSAndroid Build Coastguard Worker            return exec_result
117*60517a1eSAndroid Build Coastguard Worker
118*60517a1eSAndroid Build Coastguard Worker    def assert_result_matches(self, result: ExecuteResult, regex: str) -> None:
119*60517a1eSAndroid Build Coastguard Worker        """Assert stdout/stderr of an invocation matches a regex.
120*60517a1eSAndroid Build Coastguard Worker
121*60517a1eSAndroid Build Coastguard Worker        Args:
122*60517a1eSAndroid Build Coastguard Worker            result: ExecuteResult from `run_bazel` whose stdout/stderr will
123*60517a1eSAndroid Build Coastguard Worker                be checked.
124*60517a1eSAndroid Build Coastguard Worker            regex: Pattern to match, using `re.search` semantics.
125*60517a1eSAndroid Build Coastguard Worker        """
126*60517a1eSAndroid Build Coastguard Worker        if not re.search(regex, result.stdout + result.stderr):
127*60517a1eSAndroid Build Coastguard Worker            self.fail(
128*60517a1eSAndroid Build Coastguard Worker                "Bazel output did not match expected pattern\n"
129*60517a1eSAndroid Build Coastguard Worker                + f"expected pattern: {regex}\n"
130*60517a1eSAndroid Build Coastguard Worker                + f"invocation details:\n{result.describe()}"
131*60517a1eSAndroid Build Coastguard Worker            )
132