Skip to content

Commit e9a5908

Browse files
authored
test.py: only run *.gen.sh scripts actually generated during that run (#1488)
* Moving immunant/c2rust-testsuite#29. Otherwise, a previously generated `*.gen.sh` file will still be run even if it's no longer supposed to be generated (and isn't anymore).
2 parents afef0e8 + 47c2fd5 commit e9a5908

File tree

6 files changed

+147
-90
lines changed

6 files changed

+147
-90
lines changed

tests/integration/test.py

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,44 @@
99

1010

1111
def get_args():
12-
parser = argparse.ArgumentParser(description='C2Rust testsuite.')
13-
parser.add_argument('--verbose', dest='verbose', action='store_true',
14-
default=False,
15-
help='Enable verbose output')
16-
parser.add_argument('--stages', dest='stages', action='store',
17-
nargs='*', type=str, default=None, choices=tests.Test.STAGES,
18-
help='Only test specified stage(s)')
19-
parser.add_argument('--print-requirements', metavar='PLATFORM',
20-
dest='requirements', choices=['ubuntu'],
21-
action='store', type=str, default=None,
22-
help='Print requirements for platform and exit')
23-
parser.add_argument('--ignore-requirements',
24-
action='store_true',
25-
help='Ignore test requirements')
26-
parser.add_argument('projects', metavar='project', type=str, nargs='*',
27-
help='Project to test (defaults to all projects if none specified)')
12+
parser = argparse.ArgumentParser(description="C2Rust testsuite.")
13+
parser.add_argument(
14+
"--verbose",
15+
dest="verbose",
16+
action="store_true",
17+
default=False,
18+
help="Enable verbose output",
19+
)
20+
parser.add_argument(
21+
"--stages",
22+
dest="stages",
23+
action="store",
24+
nargs="*",
25+
type=str,
26+
default=None,
27+
choices=tests.Test.STAGES,
28+
help="Only test specified stage(s)",
29+
)
30+
parser.add_argument(
31+
"--print-requirements",
32+
metavar="PLATFORM",
33+
dest="requirements",
34+
choices=["ubuntu"],
35+
action="store",
36+
type=str,
37+
default=None,
38+
help="Print requirements for platform and exit",
39+
)
40+
parser.add_argument(
41+
"--ignore-requirements", action="store_true", help="Ignore test requirements"
42+
)
43+
parser.add_argument(
44+
"projects",
45+
metavar="project",
46+
type=str,
47+
nargs="*",
48+
help="Project to test (defaults to all projects if none specified)",
49+
)
2850
return parser.parse_args()
2951

3052

@@ -42,5 +64,5 @@ def print_requirements(args):
4264
elif not conf.project_dirs and len(args.projects) > 0:
4365
util.die(f"no such project: {args.project}")
4466
else:
45-
templates.autogen(conf)
46-
tests.run_tests(conf)
67+
generated_scripts = set(templates.autogen(conf))
68+
tests.run_tests(conf, generated_scripts)

tests/integration/tests/__init__.py

Lines changed: 42 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
21
from concurrent.futures import ThreadPoolExecutor
32
from dataclasses import dataclass
43
from datetime import timedelta
54
import os
5+
from pathlib import Path
66
import sys
77
import subprocess
88
from time import perf_counter
@@ -13,7 +13,6 @@
1313

1414

1515
class Test(object):
16-
1716
STAGES: dict[str, list[str]] = {
1817
"autogen": ["autogen.sh"],
1918
"configure": ["configure.sh"],
@@ -22,15 +21,25 @@ class Test(object):
2221
"cargo.transpile": ["cargo.transpile.gen.sh", "cargo.transpile.sh"],
2322
"refactor": ["refactor.gen.sh", "refactor.sh"],
2423
"cargo.refactor": ["cargo.refactor.gen.sh", "cargo.refactor.sh"],
25-
"check": ["check.sh", "test.sh"]
24+
"check": ["check.sh", "test.sh"],
2625
}
2726

28-
def __init__(self, directory: str):
29-
ff = next(os.walk(directory))[2]
30-
self.scripts = set(filter(lambda f: f.endswith(".sh"), ff))
31-
self.dir = directory
32-
self.conf_file = os.path.join(directory, CONF_YML)
33-
self.name = os.path.basename(directory)
27+
def __init__(self, directory: Path, generated_scripts: set[Path]):
28+
# We only want scripts that have been generated by us now,
29+
# but non `*.gen*` scripts we can search for.
30+
non_gen_script_paths = {
31+
f
32+
for f in directory.iterdir()
33+
if f.suffix == ".sh" and ".gen" not in f.suffixes
34+
}
35+
gen_script_paths = {
36+
path for path in generated_scripts if path.is_relative_to(directory)
37+
}
38+
script_paths = non_gen_script_paths | gen_script_paths
39+
self.scripts = {str(script.relative_to(directory)) for script in script_paths}
40+
self.dir = str(directory)
41+
self.conf_file = str(directory / CONF_YML)
42+
self.name = directory.name
3443

3544
def run_script(self, stage, script, verbose=False, xfail=False) -> bool:
3645
"""
@@ -40,41 +49,43 @@ def run_script(self, stage, script, verbose=False, xfail=False) -> bool:
4049
def print_log_tail_on_fail(script_path):
4150
logfile = f"{script_path}.log"
4251
if os.path.isfile(logfile):
43-
grep_cmd = ['grep', '-i', '-A', '20', '-E', 'panicked|error', logfile]
52+
grep_cmd = ["grep", "-i", "-A", "20", "-E", "panicked|error", logfile]
4453
grep = subprocess.Popen(grep_cmd, stdout=subprocess.PIPE)
4554
assert grep.stdout is not None
4655
for line in grep.stdout:
4756
print(line.decode().rstrip())
4857

4958
# fall back to tail if grep didn't find anything
5059
if grep.returncode != 0:
51-
tail = subprocess.Popen(['tail', '-n', '20', logfile], stdout=subprocess.PIPE)
60+
tail = subprocess.Popen(
61+
["tail", "-n", "20", logfile], stdout=subprocess.PIPE
62+
)
5263
assert tail.stdout is not None
5364
for line in tail.stdout:
5465
print(line.decode().rstrip())
5566
else:
56-
print("{color}Missing log file: {logf}{nocolor}".format(
57-
color=Colors.WARNING,
58-
logf=logfile,
59-
nocolor=Colors.NO_COLOR)
67+
print(
68+
"{color}Missing log file: {logf}{nocolor}".format(
69+
color=Colors.WARNING, logf=logfile, nocolor=Colors.NO_COLOR
70+
)
6071
)
6172

6273
script_path = os.path.join(self.dir, script)
6374

6475
if not os.path.isfile(script_path):
65-
print("{color}Missing script: {script}{nocolor}".format(
66-
color=Colors.FAIL,
67-
script=script_path,
68-
nocolor=Colors.NO_COLOR)
76+
print(
77+
"{color}Missing script: {script}{nocolor}".format(
78+
color=Colors.FAIL, script=script_path, nocolor=Colors.NO_COLOR
6979
)
80+
)
7081
return False
7182

7283
if not os.access(script_path, os.X_OK):
73-
print("{color}Script is not executable: {script}{nocolor}".format(
74-
color=Colors.FAIL,
75-
script=script_path,
76-
nocolor=Colors.NO_COLOR)
84+
print(
85+
"{color}Script is not executable: {script}{nocolor}".format(
86+
color=Colors.FAIL, script=script_path, nocolor=Colors.NO_COLOR
7787
)
88+
)
7889
return False
7990

8091
if not verbose:
@@ -84,7 +95,8 @@ def print_log_tail_on_fail(script_path):
8495
name=self.name,
8596
nc=Colors.NO_COLOR,
8697
stage=stage,
87-
script=relpath)
98+
script=relpath,
99+
)
88100
else:
89101
line = ""
90102

@@ -187,7 +199,9 @@ def run(self, conf: Config) -> bool:
187199
requested_stages = ", ".join(conf.stages)
188200
stages = ", ".join(Test.STAGES.keys())
189201
y, nc = Colors.WARNING, Colors.NO_COLOR
190-
die(f"invalid stages: {y}{requested_stages}{nc}. valid stages: {stages}")
202+
die(
203+
f"invalid stages: {y}{requested_stages}{nc}. valid stages: {stages}"
204+
)
191205

192206
stages = conf.stages
193207

@@ -210,11 +224,11 @@ class TestResult:
210224
time: timedelta
211225

212226

213-
def run_tests(conf: Config):
227+
def run_tests(conf: Config, generated_scripts: set[Path]):
214228
if not conf.ignore_requirements:
215229
check(conf)
216230

217-
tests = [Test(td) for td in conf.project_dirs]
231+
tests = [Test(Path(td), generated_scripts) for td in conf.project_dirs]
218232

219233
def run(test: Test) -> TestResult:
220234
start = perf_counter()
@@ -229,5 +243,5 @@ def run(test: Test) -> TestResult:
229243
for result in results:
230244
print(f"{result.test.name} took {result.time}")
231245
if not all(result.passed for result in results):
232-
print(f"projects failed: {" ".join(result.test.name for result in results)}")
246+
print(f"projects failed: {' '.join(result.test.name for result in results)}")
233247
exit(1)

tests/integration/tests/hostenv.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ def is_ubuntu_1804() -> bool:
2020
# def is_ubuntu_1404():
2121
# return 'Ubuntu-14.04-trusty' in platform()
2222

23+
2324
def is_centos() -> bool:
2425
return distro.name() == "CentOS"
2526

tests/integration/tests/requirements.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ def check_apt_package(yaml: List[str]):
2121
last: str = output.splitlines()[-1]
2222
expected: str = f"ii {p}"
2323
if not last.startswith(expected):
24-
errors.append(f"package not (properly) installed: {p} (dpkg output: {output}) ")
25-
24+
errors.append(
25+
f"package not (properly) installed: {p} (dpkg output: {output}) "
26+
)
27+
2628
if errors:
2729
errors = "\n".join(errors)
2830
die(errors)
@@ -65,7 +67,7 @@ def check_host(host: str, yaml: Dict):
6567
return
6668
# print(f"{host} -> {reqs}")
6769

68-
for (key, val) in reqs.items():
70+
for key, val in reqs.items():
6971
if key == "apt":
7072
check_apt(val)
7173
elif key == "programs":
@@ -92,7 +94,7 @@ def check_file(file: str, yaml):
9294

9395

9496
def check(conf):
95-
for (cf, yaml) in conf.project_conf.items():
97+
for cf, yaml in conf.project_conf.items():
9698
check_file(cf, yaml)
9799

98100

tests/integration/tests/templates.py

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
2+
from pathlib import Path
23
import stat
34
from collections.abc import Mapping
4-
from typing import Any, Dict, List
5+
from typing import Any, Dict, Generator, List
56

67
from tests.util import *
78
from jinja2 import Template
@@ -96,42 +97,53 @@
9697
def render_script(template: str, out_path: str, params: Dict):
9798
out = Template(template).render(**params)
9899

99-
with open(out_path, 'w') as fh:
100+
with open(out_path, "w") as fh:
100101
fh.writelines(out)
101102
os.chmod(out_path, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
102103

103104

104-
def autogen_cargo(conf_file, yaml: Dict):
105-
def render_stage(stage_conf: Mapping[str, Any] | None, filename: str) -> bool:
105+
def autogen_cargo(conf_file, yaml: Dict) -> Generator[Path]:
106+
"""
107+
Yield generated paths.
108+
"""
109+
110+
def render_stage(
111+
stage_conf: Mapping[str, Any] | None, filename: str
112+
) -> Generator[Path]:
113+
"""
114+
Yield generated paths.
115+
"""
116+
106117
if not isinstance(stage_conf, Mapping):
107-
return False
118+
return
108119
if not stage_conf:
109-
return False
120+
return
110121

111122
ag = stage_conf.get("autogen")
112123
if not (ag and isinstance(ag, bool)):
113-
return False
124+
return
114125

115126
params: Dict[str, str] = {}
116127
rustflags = stage_conf.get("rustflags")
117128
if rustflags and isinstance(rustflags, str):
118129
params["extra_rustflags"] = rustflags
119130

120-
out_path = os.path.join(
121-
os.path.dirname(conf_file),
122-
filename
123-
)
131+
out_path = os.path.join(os.path.dirname(conf_file), filename)
124132
render_script(CARGO_SH, out_path, params)
125-
return True
133+
yield Path(out_path)
126134

127135
for key, fname in (
128136
("cargo.transpile", "cargo.transpile.gen.sh"),
129137
("cargo.refactor", "cargo.refactor.gen.sh"),
130138
):
131-
render_stage(yaml.get(key), fname)
139+
yield from render_stage(yaml.get(key), fname)
140+
132141

142+
def autogen_refactor(conf_file, yaml: Dict) -> Generator[str]:
143+
"""
144+
Yield generated paths.
145+
"""
133146

134-
def autogen_refactor(conf_file, yaml: Dict):
135147
refactor = yaml.get("refactor")
136148
if refactor and isinstance(refactor, Dict):
137149
ag = refactor.get("autogen")
@@ -141,7 +153,9 @@ def autogen_refactor(conf_file, yaml: Dict):
141153
# Get list of transformations from config
142154
transforms = refactor.get("transforms")
143155
if transforms and isinstance(transforms, list):
144-
lines = [t.strip() for t in transforms if isinstance(t, str) and t.strip()]
156+
lines = [
157+
t.strip() for t in transforms if isinstance(t, str) and t.strip()
158+
]
145159
if lines:
146160
params["transform_lines"] = "\n".join(lines)
147161
elif transforms and isinstance(transforms, str):
@@ -151,14 +165,16 @@ def autogen_refactor(conf_file, yaml: Dict):
151165

152166
# Only generate script if we have transformations
153167
if params["transform_lines"]:
154-
out_path = os.path.join(
155-
os.path.dirname(conf_file),
156-
"refactor.gen.sh"
157-
)
168+
out_path = os.path.join(os.path.dirname(conf_file), "refactor.gen.sh")
158169
render_script(REFACTOR_SH, out_path, params)
170+
yield Path(out_path)
159171

160172

161-
def autogen_transpile(conf_file, yaml: Dict):
173+
def autogen_transpile(conf_file, yaml: Dict) -> Generator[Path]:
174+
"""
175+
Yield generated paths.
176+
"""
177+
162178
transpile = yaml.get("transpile")
163179
if transpile and isinstance(transpile, Dict):
164180
ag = transpile.get("autogen")
@@ -181,16 +197,17 @@ def autogen_transpile(conf_file, yaml: Dict):
181197
tflags = " ".join(tflags)
182198
params["tflags"] = tflags
183199

184-
185-
out_path = os.path.join(
186-
os.path.dirname(conf_file),
187-
"transpile.gen.sh"
188-
)
200+
out_path = os.path.join(os.path.dirname(conf_file), "transpile.gen.sh")
189201
render_script(TRANSPILE_SH, out_path, params)
202+
yield Path(out_path)
203+
190204

205+
def autogen(conf: Config) -> Generator[Path]:
206+
"""
207+
Yield generated paths.
208+
"""
191209

192-
def autogen(conf: Config):
193-
for (cf, yaml) in conf.project_conf.items():
194-
autogen_transpile(cf, yaml)
195-
autogen_refactor(cf, yaml)
196-
autogen_cargo(cf, yaml)
210+
for cf, yaml in conf.project_conf.items():
211+
yield from autogen_transpile(cf, yaml)
212+
yield from autogen_refactor(cf, yaml)
213+
yield from autogen_cargo(cf, yaml)

0 commit comments

Comments
 (0)