forked from bazel-contrib/rules_python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrunner.py
More file actions
131 lines (120 loc) · 4.36 KB
/
Copy pathrunner.py
File metadata and controls
131 lines (120 loc) · 4.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# 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 logging
import os
import os.path
import pathlib
import re
import shlex
import subprocess
import unittest
_logger = logging.getLogger(__name__)
class ExecuteError(Exception):
def __init__(self, result):
self.result = result
def __str__(self):
return self.result.describe()
class ExecuteResult:
def __init__(
self,
args: list[str],
env: dict[str, str],
cwd: pathlib.Path,
proc_result: subprocess.CompletedProcess,
):
self.args = args
self.env = env
self.cwd = cwd
self.exit_code = proc_result.returncode
self.stdout = proc_result.stdout
self.stderr = proc_result.stderr
def describe(self) -> str:
env_lines = [
" " + shlex.quote(f"{key}={value}")
for key, value in sorted(self.env.items())
]
env = " \\\n".join(env_lines)
args = shlex.join(self.args)
maybe_stdout_nl = "" if self.stdout.endswith("\n") else "\n"
maybe_stderr_nl = "" if self.stderr.endswith("\n") else "\n"
return f"""\
COMMAND:
cd {self.cwd} && \\
env \\
{env} \\
{args}
RESULT: exit_code: {self.exit_code}
===== STDOUT START =====
{self.stdout}{maybe_stdout_nl}===== STDOUT END =====
===== STDERR START =====
{self.stderr}{maybe_stderr_nl}===== STDERR END =====
"""
class TestCase(unittest.TestCase):
def setUp(self):
super().setUp()
self.repo_root = pathlib.Path(os.environ["BIT_WORKSPACE_DIR"])
self.bazel = pathlib.Path(os.environ["BIT_BAZEL_BINARY"])
outer_test_tmpdir = pathlib.Path(os.environ["TEST_TMPDIR"])
self.test_tmp_dir = outer_test_tmpdir / "bit_test_tmp"
# Put the global tmp not under the test tmp to better match how a real
# execution has entirely different directories for these.
self.tmp_dir = outer_test_tmpdir / "bit_tmp"
self.bazel_env = {
"PATH": os.environ["PATH"],
"TEST_TMPDIR": str(self.test_tmp_dir),
"TMP": str(self.tmp_dir),
# For some reason, this is necessary for Bazel 6.4 to work.
# If not present, it can't find some bash helpers in @bazel_tools
"RUNFILES_DIR": os.environ["TEST_SRCDIR"]
}
def run_bazel(self, *args: str, check: bool = True) -> ExecuteResult:
"""Run a bazel invocation.
Args:
*args: The args to pass to bazel; the leading `bazel` command is
added automatically
check: True if the execution must succeed, False if failure
should raise an error.
Returns:
An `ExecuteResult` from running Bazel
"""
args = [str(self.bazel), *args]
env = self.bazel_env
_logger.info("executing: %s", shlex.join(args))
cwd = self.repo_root
proc_result = subprocess.run(
args=args,
text=True,
capture_output=True,
cwd=cwd,
env=env,
check=False,
)
exec_result = ExecuteResult(args, env, cwd, proc_result)
if check and exec_result.exit_code:
raise ExecuteError(exec_result)
else:
return exec_result
def assert_result_matches(self, result: ExecuteResult, regex: str) -> None:
"""Assert stdout/stderr of an invocation matches a regex.
Args:
result: ExecuteResult from `run_bazel` whose stdout/stderr will
be checked.
regex: Pattern to match, using `re.search` semantics.
"""
if not re.search(regex, result.stdout + result.stderr):
self.fail(
"Bazel output did not match expected pattern\n"
+ f"expected pattern: {regex}\n"
+ f"invocation details:\n{result.describe()}"
)