Skip to content

Commit 667eec9

Browse files
committed
Improve image checker
1 parent 45e5c38 commit 667eec9

File tree

3 files changed

+217
-13
lines changed

3 files changed

+217
-13
lines changed

tests/integration/test_images.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import os
2+
from math import ceil
3+
from typing import Dict
4+
5+
import matplotlib.backends.backend_pdf
6+
import matplotlib.image as mpimg
7+
from mache import MachineInfo
8+
from matplotlib import pyplot as plt
9+
10+
from tests.integration.utils import TestResults, check_mismatched_images, get_expansions
11+
12+
V3_CASE_NAME = "v3.LR.historical_0051"
13+
V2_CASE_NAME = "v2.LR.historical_0201"
14+
15+
16+
def make_image_diff_grid(diff_subdir, pdf_name="image_diff_grid.pdf", rows_per_page=5):
17+
machine_info = MachineInfo(machine=os.environ["E3SMU_MACHINE"])
18+
web_portal_base_path = machine_info.config.get("web_portal", "base_path")
19+
web_portal_base_url = machine_info.config.get("web_portal", "base_url")
20+
print(f"web_portal_base_path: {web_portal_base_path}")
21+
print(f"web_portal_base_url: {web_portal_base_url}")
22+
23+
if not diff_subdir.startswith(web_portal_base_path):
24+
print(
25+
f"diff_subdir {diff_subdir} is not a subdir of web_portal_base_path: {web_portal_base_path}"
26+
)
27+
return
28+
pdf_path = f"{diff_subdir}/{pdf_name}"
29+
pdf = matplotlib.backends.backend_pdf.PdfPages(pdf_path)
30+
print(f"Saving to:\n{pdf_path}")
31+
web_subdir = diff_subdir.removeprefix(web_portal_base_path)
32+
print(f"Web page at:\n{web_portal_base_url}/{web_subdir}/{pdf_name}")
33+
34+
prefixes = []
35+
# print(f"Walking diff_subdir: {diff_subdir}")
36+
for root, _, files in os.walk(diff_subdir):
37+
# print(f"root: {root}")
38+
for file_name in files:
39+
# print(f"file_name: {file_name}")
40+
if file_name.endswith("_diff.png"):
41+
prefixes.append(f"{root}/{file_name.split('_diff.png')[0]}")
42+
rows = len(prefixes)
43+
cols = 3 # actual, expected, diff
44+
print(f"Constructing a {rows}x{cols} grid of image diffs")
45+
46+
num_pages = ceil(rows / rows_per_page)
47+
for page in range(num_pages):
48+
fig, axes = plt.subplots(rows_per_page, cols)
49+
print(f"Page {page}")
50+
for i, ax_row in enumerate(axes):
51+
count = page * 3 + i
52+
if count > len(prefixes) - 1:
53+
break
54+
# We already know all the files are in `diff_subdir`; no need to repeat it.
55+
short_title = prefixes[count].removeprefix(diff_subdir)
56+
print(f"short_title {i}: {short_title}")
57+
ax_row[1].set_title(short_title, fontsize=6)
58+
img = mpimg.imread(f"{prefixes[count]}_actual.png")
59+
ax_row[0].imshow(img)
60+
ax_row[0].set_xticks([])
61+
ax_row[0].set_yticks([])
62+
img = mpimg.imread(f"{prefixes[count]}_expected.png")
63+
ax_row[1].imshow(img)
64+
ax_row[1].set_xticks([])
65+
ax_row[1].set_yticks([])
66+
img = mpimg.imread(f"{prefixes[count]}_diff.png")
67+
ax_row[2].imshow(img)
68+
ax_row[2].set_xticks([])
69+
ax_row[2].set_yticks([])
70+
fig.tight_layout()
71+
pdf.savefig(1)
72+
plt.close(fig)
73+
pdf.close()
74+
plt.clf()
75+
print(f"Reminder:\n{web_portal_base_url}/{web_subdir}/{pdf_name}")
76+
77+
78+
def check_images(
79+
expansions, cfg_specifier, case_name, task, diff_dir_suffix="_test_pr699"
80+
):
81+
expected_dir = expansions["expected_dir"]
82+
user_www = expansions["user_www"]
83+
unique_id = expansions["unique_id"]
84+
actual_images_dir = (
85+
f"{user_www}zppy_weekly_{cfg_specifier}_www/{unique_id}/{case_name}/"
86+
)
87+
88+
# The expected_images_file lists all images we expect to compare.
89+
expected_images_file = f"{expected_dir}image_list_expected_{cfg_specifier}.txt"
90+
expected_images_dir = f"{expected_dir}expected_{cfg_specifier}"
91+
92+
# The directory to place differences in.
93+
diff_dir = (
94+
f"{actual_images_dir}image_check_failures_{cfg_specifier}{diff_dir_suffix}"
95+
)
96+
97+
test_results = check_mismatched_images(
98+
actual_images_dir,
99+
expected_images_file,
100+
expected_images_dir,
101+
diff_dir,
102+
task,
103+
)
104+
diff_subdir = f"{diff_dir}/{task}"
105+
# Write missing and mismatched images to files
106+
missing_images_file = f"{diff_subdir}/missing_images.txt"
107+
if os.path.exists(missing_images_file):
108+
os.remove(missing_images_file)
109+
for missing_image in test_results.file_list_missing:
110+
with open(missing_images_file, "a") as f:
111+
f.write(f"{missing_image}\n")
112+
mismatched_images_file = f"{diff_subdir}/mismatched_images.txt"
113+
if os.path.exists(mismatched_images_file):
114+
os.remove(mismatched_images_file)
115+
for mismatched_image in test_results.file_list_mismatched:
116+
with open(mismatched_images_file, "a") as f:
117+
f.write(f"{mismatched_image}\n")
118+
# Create image diff grid
119+
make_image_diff_grid(diff_subdir)
120+
return test_results
121+
122+
123+
def construct_markdown_summary_table(
124+
test_results_dict: Dict[str, TestResults], output_file_path: str
125+
):
126+
with open(output_file_path, "w") as f:
127+
f.write("# Summary of test results\n\n")
128+
f.write(
129+
"Diff subdir is where to find the diffs and the lists of missing/mismatched images."
130+
)
131+
f.write(
132+
"| Test name | Total images | Correct images | Missing images | Mismatched images | Diff subdir | \n"
133+
)
134+
f.write("| --- | --- | --- | --- | --- | --- | \n")
135+
for test_name, test_results in test_results_dict.items():
136+
f.write(
137+
f"| {test_name} | {test_results.image_count_total} | {test_results.image_count_correct} | {test_results.image_count_missing} | {test_results.image_count_mismatched} | {test_results.diff_dir}/{test_results.task} | \n"
138+
)
139+
140+
141+
def test_images():
142+
# To test a different branch, set this to True, and manually set the expansions.
143+
TEST_DIFFERENT_EXPANSIONS = False
144+
if TEST_DIFFERENT_EXPANSIONS:
145+
expansions = dict()
146+
expansions["expected_dir"] = "/lcrc/group/e3sm/public_html/zppy_test_resources/"
147+
expansions["user_www"] = (
148+
"/lcrc/group/e3sm/public_html/diagnostic_output/ac.forsyth2/"
149+
)
150+
expansions["unique_id"] = "test_zppy_20250401"
151+
else:
152+
expansions = get_expansions()
153+
test_results_dict: Dict[str, TestResults] = dict()
154+
for task in ["e3sm_diags", "mpas_analysis", "global_time_series", "ilamb"]:
155+
test_results = check_images(
156+
expansions,
157+
"comprehensive_v2",
158+
V2_CASE_NAME,
159+
task,
160+
)
161+
test_results_dict[f"comprehensive_v2_{task}"] = test_results
162+
test_results = check_images(
163+
expansions,
164+
"comprehensive_v3",
165+
V3_CASE_NAME,
166+
task,
167+
)
168+
test_results_dict[f"comprehensive_v3_{task}"] = test_results
169+
test_results = check_images(
170+
expansions,
171+
"bundles",
172+
V3_CASE_NAME,
173+
task,
174+
)
175+
test_results_dict[f"bundles_{task}"] = test_results
176+
md_path = "min_case_summary.md"
177+
construct_markdown_summary_table(test_results_dict, md_path)
178+
print(f"Copy output of {md_path} to a PR comment.")
179+
for tr in test_results_dict.values():
180+
assert tr.image_count_total == tr.image_count_correct

tests/integration/test_weekly.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def check_images(test_name, case_name, subdir):
2828
expected_images_file,
2929
expected_images_dir,
3030
diff_dir,
31-
[subdir],
31+
subdir,
3232
)
3333

3434

tests/integration/utils.py

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,29 @@
1212
# Image checking ##########################################################
1313

1414

15+
# Originally in https://github.com/E3SM-Project/zppy/pull/695
16+
# And https://github.com/E3SM-Project/zppy/pull/698
17+
class TestResults(object):
18+
def __init__(
19+
self,
20+
diff_dir: str,
21+
task: str,
22+
image_count_total: int,
23+
file_list_missing: List[str],
24+
file_list_mismatched: List[str],
25+
):
26+
self.diff_dir = diff_dir
27+
self.task = task
28+
self.image_count_total = image_count_total
29+
self.image_count_missing = len(file_list_missing)
30+
self.image_count_mismatched = len(file_list_mismatched)
31+
self.image_count_correct = (
32+
image_count_total - len(file_list_missing) - len(file_list_mismatched)
33+
)
34+
self.file_list_missing = sorted(file_list_missing)
35+
self.file_list_mismatched = sorted(file_list_mismatched)
36+
37+
1538
# Copied from E3SM Diags
1639
def compare_images(
1740
missing_images,
@@ -92,12 +115,12 @@ def compare_images(
92115

93116

94117
def check_mismatched_images(
95-
actual_images_dir,
96-
expected_images_file,
97-
expected_images_dir,
98-
diff_dir,
99-
subdirs_to_check,
100-
):
118+
actual_images_dir: str,
119+
expected_images_file: str,
120+
expected_images_dir: str,
121+
diff_dir: str,
122+
task: str,
123+
) -> TestResults:
101124
missing_images: List[str] = []
102125
mismatched_images: List[str] = []
103126

@@ -106,10 +129,9 @@ def check_mismatched_images(
106129
for line in f:
107130
image_name = line.strip("./").strip("\n")
108131
proceed = False
109-
for subdir in subdirs_to_check:
110-
if image_name.startswith(subdir):
111-
proceed = True
112-
break
132+
if image_name.startswith(task):
133+
proceed = True
134+
break
113135
if proceed:
114136
counter += 1
115137
if counter % 250 == 0:
@@ -142,6 +164,9 @@ def check_mismatched_images(
142164
print(
143165
f"Number of correct images: {counter - len(missing_images) - len(mismatched_images)}"
144166
)
167+
test_results = TestResults(
168+
diff_dir, task, counter, missing_images, mismatched_images
169+
)
145170

146171
# Make diff_dir readable
147172
if os.path.exists(diff_dir):
@@ -151,8 +176,7 @@ def check_mismatched_images(
151176
# That is, if we're in this case, we expect the following:
152177
assert len(missing_images) == counter
153178

154-
assert missing_images == []
155-
assert mismatched_images == []
179+
return test_results
156180

157181

158182
# Multi-machine testing ##########################################################

0 commit comments

Comments
 (0)