Skip to content

Commit b75fb18

Browse files
gkdncopybara-github
authored andcommitted
Refactor j2 validate to support multiple test suites and improve test helpers.
PiperOrigin-RevId: 906509480
1 parent 27e3e70 commit b75fb18

1 file changed

Lines changed: 196 additions & 172 deletions

File tree

dev/validate_gen.py

Lines changed: 196 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,44 @@
1414

1515
# pylint: disable=missing-function-docstring
1616

17-
import argparse
18-
import contextlib
1917
import io
2018
import os
19+
import shlex
20+
import subprocess
2121
import sys
22-
import replace_all
22+
23+
24+
_out = io.StringIO()
25+
26+
27+
class ValidationTest:
28+
"""Base class for validation tests."""
29+
30+
def setup(self):
31+
# Clear the buffer
32+
_out.seek(0)
33+
_out.truncate(0)
34+
35+
def teardown(self):
36+
pass
37+
38+
def run_test(self, name, func):
39+
print(f"{name}: ", end="", flush=True)
40+
self.setup()
41+
try:
42+
func()
43+
print("\033[92mSUCCESS\033[0m")
44+
return True
45+
except BaseException as e: # pylint: disable=broad-exception-caught
46+
print("\033[91mFAILED\033[0m")
47+
print("--- Error ---")
48+
print(str(e))
49+
print("--- Captured Output ---")
50+
print(_out.getvalue())
51+
return False
52+
finally:
53+
self.teardown()
54+
2355

2456
READABLE_DIR = "transpiler/javatests/com/google/j2cl/readable/java/emptyclass"
2557
READABLE_DIR_KT = "transpiler/javatests/com/google/j2cl/readable/kotlin/emptyclass"
@@ -36,39 +68,136 @@
3668
BUILD_FILE = os.path.join(READABLE_DIR, "BUILD")
3769

3870

39-
_out = io.StringIO()
40-
_backup = {}
41-
42-
43-
def _setup():
44-
# Clear the buffer
45-
_out.seek(0)
46-
_out.truncate(0)
47-
48-
_backup[GOLDEN_CLOSURE] = open(GOLDEN_CLOSURE, "r").read()
49-
_backup[GOLDEN_WASM] = open(GOLDEN_WASM, "r").read()
50-
_backup[GOLDEN_KT] = open(GOLDEN_KT, "r").read()
51-
_backup[GOLDEN_KT_WEB] = open(GOLDEN_KT_WEB, "r").read()
52-
_backup[SOURCE_FILE] = open(SOURCE_FILE, "r").read()
53-
_backup[BUILD_FILE] = open(BUILD_FILE, "r").read()
54-
55-
# Make sure we are starting with a clean state.
56-
_ensure_clean_state()
57-
58-
59-
def _teardown():
60-
for path, content in _backup.items():
61-
with open(path, "w") as f:
62-
f.write(content)
63-
64-
65-
def _ensure_clean_state():
66-
if _out.getvalue():
67-
raise AssertionError("Output buffer not cleared between tests!")
68-
localout = io.StringIO()
69-
with contextlib.redirect_stdout(localout):
70-
_run_gen()
71-
_assert_in("Number of stale readables: 0", localout.getvalue())
71+
class GenValidationTest(ValidationTest):
72+
"""Validation tests for j2 gen."""
73+
74+
def setup(self):
75+
super().setup()
76+
77+
self._backup = {
78+
GOLDEN_CLOSURE: open(GOLDEN_CLOSURE, "r").read(),
79+
GOLDEN_WASM: open(GOLDEN_WASM, "r").read(),
80+
GOLDEN_KT: open(GOLDEN_KT, "r").read(),
81+
GOLDEN_KT_WEB: open(GOLDEN_KT_WEB, "r").read(),
82+
SOURCE_FILE: open(SOURCE_FILE, "r").read(),
83+
BUILD_FILE: open(BUILD_FILE, "r").read(),
84+
}
85+
86+
# Make sure we are starting with a clean state.
87+
self._ensure_clean_state()
88+
89+
def teardown(self):
90+
super().teardown()
91+
for path, content in self._backup.items():
92+
with open(path, "w") as f:
93+
f.write(content)
94+
95+
def _ensure_clean_state(self):
96+
if _out.getvalue():
97+
raise AssertionError("Output buffer not cleared between tests!")
98+
localout = io.StringIO()
99+
_j2("gen java/emptyclass", out_stream=localout)
100+
_assert_in("Number of stale readables: 0", localout.getvalue())
101+
102+
def test_golden_file_updates(self):
103+
with open(GOLDEN_CLOSURE, "a") as f:
104+
f.write("\n// STALE COMMENT\n")
105+
with open(GOLDEN_WASM, "a") as f:
106+
f.write("\n;; STALE COMMENT\n")
107+
with open(GOLDEN_KT, "a") as f:
108+
f.write("\n// STALE COMMENT\n")
109+
with open(GOLDEN_KT_WEB, "a") as f:
110+
f.write("\n// STALE COMMENT\n")
111+
112+
_j2("gen java/emptyclass")
113+
_assert_output("Number of stale readables: 4")
114+
115+
with open(GOLDEN_CLOSURE, "r") as f:
116+
_assert_not_in("// STALE COMMENT", f.read())
117+
with open(GOLDEN_WASM, "r") as f:
118+
_assert_not_in(";; STALE COMMENT", f.read())
119+
with open(GOLDEN_KT, "r") as f:
120+
_assert_not_in("// STALE COMMENT", f.read())
121+
with open(GOLDEN_KT_WEB, "r") as f:
122+
_assert_not_in("// STALE COMMENT", f.read())
123+
124+
def test_failed_compilation(self):
125+
with open(SOURCE_FILE, "w") as f:
126+
f.write("INVALID JAVA CODE")
127+
128+
_j2_expecting_failure("gen java/emptyclass")
129+
_assert_output("No test status for targets")
130+
_assert_output("Sponge link:")
131+
132+
def test_broken_build_file(self):
133+
with open(BUILD_FILE, "w") as f:
134+
f.write("INVALID BAZEL SYNTAX")
135+
136+
_j2_expecting_failure("gen java/emptyclass")
137+
_assert_output("Error while running command")
138+
_assert_output("blaze query filter")
139+
140+
def test_missing_file_in_srcs(self):
141+
original_build = self._backup[BUILD_FILE]
142+
_assert_in('glob(["*.java"])', original_build)
143+
144+
broken_build = original_build.replace('glob(["*.java"])', "[]")
145+
with open(BUILD_FILE, "w") as f:
146+
f.write(broken_build)
147+
148+
_j2_expecting_failure("gen java/emptyclass")
149+
_assert_output("No test status for targets")
150+
_assert_output("Sponge link:")
151+
152+
def test_platform_filtering(self):
153+
_j2("-p CLOSURE gen java/emptyclass")
154+
_assert_output("Blaze building Wasm:\n No matches")
155+
156+
def test_pattern(self):
157+
_j2("-p CLOSURE gen empty.*")
158+
_assert_not_output("No matching readables!")
159+
_assert_output(READABLE_DIR)
160+
_assert_output(READABLE_DIR_KT)
161+
162+
def test_pattern2(self):
163+
_j2("-p CLOSURE gen java/empty.*")
164+
_assert_not_output("No matching readables!")
165+
_assert_output(READABLE_DIR)
166+
_assert_not_output(READABLE_DIR_KT)
167+
168+
def test_pattern3(self):
169+
_j2("-p CLOSURE gen kotlin/empty.*")
170+
_assert_not_output("No matching readables!")
171+
_assert_not_output(READABLE_DIR)
172+
_assert_output(READABLE_DIR_KT)
173+
174+
def test_pattern_no_matches(self):
175+
_j2("-p CLOSURE gen empty")
176+
_assert_output("No matching readables!")
177+
178+
179+
class DiffValidationTest(ValidationTest):
180+
"""Validation tests for j2 diff."""
181+
182+
183+
def _j2(args_str, out_stream=None):
184+
if out_stream is None:
185+
out_stream = _out
186+
args = shlex.split(args_str)
187+
cmd = ["python3", "dev/j2.py"] + args
188+
result = subprocess.run(cmd, capture_output=True, text=True, check=False)
189+
out_stream.write(result.stdout)
190+
out_stream.write(result.stderr)
191+
if result.returncode != 0:
192+
raise SystemExit(result.returncode)
193+
194+
195+
def _j2_expecting_failure(args_str):
196+
try:
197+
_j2(args_str)
198+
raise AssertionError(f"j2 gen {args_str} expected to fail but didn't.")
199+
except SystemExit:
200+
return
72201

73202

74203
def _assert_in(needle, haystack, msg=None):
@@ -81,150 +210,45 @@ def _assert_not_in(needle, haystack, msg=None):
81210
raise AssertionError(msg or f"Did not expect to find '{needle}'.")
82211

83212

84-
def _fail():
85-
raise AssertionError("j2 gen expected to fail but didn't.")
86-
87-
88-
def _run_gen(name="java/emptyclass", platforms=None):
89-
argv = argparse.Namespace(
90-
readable_name=[name], platforms=platforms or ["CLOSURE", "WASM", "J2KT"]
91-
)
92-
replace_all.main(argv)
93-
94-
95-
def _run_gen_expecting_failure(**kwargs):
96-
try:
97-
_run_gen(**kwargs)
98-
_fail()
99-
except SystemExit:
100-
return
213+
def _assert_output(needle):
214+
_assert_in(needle, _out.getvalue())
101215

102216

103-
def test_golden_file_updates():
104-
with open(GOLDEN_CLOSURE, "a") as f:
105-
f.write("\n// STALE COMMENT\n")
106-
with open(GOLDEN_WASM, "a") as f:
107-
f.write("\n;; STALE COMMENT\n")
108-
with open(GOLDEN_KT, "a") as f:
109-
f.write("\n// STALE COMMENT\n")
110-
with open(GOLDEN_KT_WEB, "a") as f:
111-
f.write("\n// STALE COMMENT\n")
217+
def _assert_not_output(needle):
218+
_assert_not_in(needle, _out.getvalue())
112219

113-
_run_gen()
114-
output = _out.getvalue()
115220

116-
_assert_in("Number of stale readables: 4", output)
221+
def main(argv):
222+
print("Starting j2 validation...\n")
117223

118-
with open(GOLDEN_CLOSURE, "r") as f:
119-
new_closure = f.read()
120-
with open(GOLDEN_WASM, "r") as f:
121-
new_wasm = f.read()
122-
with open(GOLDEN_KT, "r") as f:
123-
new_kt = f.read()
124-
with open(GOLDEN_KT_WEB, "r") as f:
125-
new_kt_web = f.read()
126-
127-
_assert_not_in("// STALE COMMENT", new_closure, "Closure golden not updated!")
128-
_assert_not_in(";; STALE COMMENT", new_wasm, "WASM golden not updated!")
129-
_assert_not_in("// STALE COMMENT", new_kt, "KT golden not updated!")
130-
_assert_not_in("// STALE COMMENT", new_kt_web, "J2KT Web golden not updated!")
131-
132-
133-
def test_failed_compilation():
134-
with open(SOURCE_FILE, "w") as f:
135-
f.write("INVALID JAVA CODE")
136-
137-
_run_gen_expecting_failure()
138-
_assert_in("No test status for targets", _out.getvalue())
139-
_assert_in("Sponge link:", _out.getvalue())
140-
141-
142-
def test_broken_build_file():
143-
with open(BUILD_FILE, "w") as f:
144-
f.write("INVALID BAZEL SYNTAX")
145-
146-
_run_gen_expecting_failure()
147-
_assert_in("Error while running command", _out.getvalue())
148-
_assert_in("blaze query filter", _out.getvalue())
149-
150-
151-
def test_missing_file_in_srcs():
152-
original_build = _backup[BUILD_FILE]
153-
_assert_in('glob(["*.java"])', original_build)
154-
155-
broken_build = original_build.replace('glob(["*.java"])', "[]")
156-
with open(BUILD_FILE, "w") as f:
157-
f.write(broken_build)
158-
159-
_run_gen_expecting_failure()
160-
_assert_in("No test status for targets", _out.getvalue())
161-
_assert_in("Sponge link:", _out.getvalue())
162-
163-
164-
def test_platform_filtering():
165-
_run_gen(platforms=["CLOSURE"])
166-
_assert_in("Blaze building Wasm:\n No matches", _out.getvalue())
167-
168-
169-
def test_pattern():
170-
_run_gen(name="empty.*", platforms=["CLOSURE"])
171-
output = _out.getvalue()
172-
_assert_not_in("No matching readables!", output)
173-
_assert_in(READABLE_DIR, output)
174-
_assert_in(READABLE_DIR_KT, output)
175-
176-
177-
def test_pattern2():
178-
_run_gen(name="java/empty.*", platforms=["CLOSURE"])
179-
output = _out.getvalue()
180-
_assert_not_in("No matching readables!", output)
181-
_assert_in(READABLE_DIR, output)
182-
_assert_not_in(READABLE_DIR_KT, output)
183-
184-
185-
def test_pattern3():
186-
_run_gen(name="kotlin/empty.*", platforms=["CLOSURE"])
187-
output = _out.getvalue()
188-
_assert_not_in("No matching readables!", output)
189-
_assert_not_in(READABLE_DIR, output)
190-
_assert_in(READABLE_DIR_KT, output)
191-
192-
193-
def test_pattern_no_matches():
194-
_run_gen(name="empty", platforms=["CLOSURE"])
195-
output = _out.getvalue()
196-
_assert_in("No matching readables!", output)
197-
198-
199-
def _run_test(name, func):
200-
print(f"{name}: ", end="", flush=True)
201-
_setup()
202-
try:
203-
with contextlib.redirect_stdout(_out):
204-
func()
205-
print("\033[92mSUCCESS\033[0m")
206-
return True
207-
except BaseException as e: # pylint: disable=broad-exception-caught
208-
print("\033[91mFAILED\033[0m")
209-
print("--- Error ---")
210-
print(str(e))
211-
print("--- Captured Output ---")
212-
print(_out.getvalue())
213-
return False
214-
finally:
215-
_teardown()
216-
217-
218-
def main(unused_argv):
219-
print("Starting j2 gen validation...\n")
224+
test_suites_mapping = {
225+
"gen": GenValidationTest(),
226+
"diff": DiffValidationTest(),
227+
}
228+
test_suites = (
229+
[test_suites_mapping[argv.test_suite]]
230+
if argv.test_suite
231+
else test_suites_mapping.values()
232+
)
220233

221234
success = True
222-
for name, func in list(globals().items()):
223-
if not name.startswith("test_"):
224-
continue
225-
display_name = name.replace("_", " ").capitalize()
226-
success &= _run_test(display_name, func)
235+
for test_suite in test_suites:
236+
for name in dir(test_suite):
237+
if not name.startswith("test_"):
238+
continue
239+
func = getattr(test_suite, name)
240+
display_name = name.replace("_", " ").capitalize()
241+
success &= test_suite.run_test(display_name, func)
227242

228243
print("")
229244
if not success:
230245
sys.exit(1)
246+
247+
248+
def add_arguments(parser):
249+
parser.add_argument(
250+
"test_suite",
251+
nargs="?",
252+
choices=["gen", "diff"],
253+
help="Test suite to run (gen or diff). If omitted, all tests are run.",
254+
)

0 commit comments

Comments
 (0)