Skip to content

Commit 6a8e268

Browse files
committed
tests: Add a CI test for the convert utility
This will run a handful of tests on the convert utility in the GitHub worker on a pull request. Signed-off-by: Naushir Patuck <[email protected]>
1 parent 0c3e266 commit 6a8e268

File tree

2 files changed

+243
-0
lines changed

2 files changed

+243
-0
lines changed

.github/workflows/pisp-verification.test.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,7 @@ jobs:
4242
- name: Run verification tests
4343
run: LD_LIBRARY_PATH=${{github.workspace}}/build/src LIBPISP_BE_CONFIG_FILE="${{github.workspace}}/src/libpisp/backend/backend_default_config.json" ${{env.BE_TEST_DIR}}/run_be_tests.py --hw --logall --test ${{env.BE_TEST_DIR}}/be_test ${{env.TESTS_DIR}}/back_end
4444
timeout-minutes: 20
45+
46+
- name: Run convert tests
47+
run: LD_LIBRARY_PATH=${{github.workspace}}/build/src LIBPISP_BE_CONFIG_FILE="${{github.workspace}}/src/libpisp/backend/backend_default_config.json python3 ${{github.workspace}}/utils/test_convert.py ${{github.workspace}}/build/src/examples/convert --out /tmp/ --in $HOME/libpisp_conv/ --ref ~/libpisp_conv/ref/
48+
timeout-minutes: 10

utils/test_convert.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
#!/usr/bin/python3
2+
3+
# SPDX-License-Identifier: BSD-2-Clause
4+
#
5+
# Copyright (C) 2025 Raspberry Pi Ltd
6+
#
7+
# test_convert.py - Test script for libpisp convert utility
8+
#
9+
10+
import argparse
11+
import os
12+
import subprocess
13+
import sys
14+
import tempfile
15+
import shutil
16+
from pathlib import Path
17+
import hashlib
18+
19+
20+
class ConvertTester:
21+
def __init__(self, convert_binary, output_dir=None, input_dir=None, reference_dir=None):
22+
"""Initialize the tester with the path to the convert binary."""
23+
self.convert_binary = convert_binary
24+
self.output_dir = output_dir
25+
self.input_dir = input_dir
26+
self.reference_dir = reference_dir
27+
if not os.path.exists(convert_binary):
28+
raise FileNotFoundError(f"Convert binary not found: {convert_binary}")
29+
30+
# Test cases: (input_file, output_file, input_format, output_format, reference_file)
31+
self.test_cases = [
32+
{
33+
"input_file": "conv_yuv420_4056x3040_4056s.yuv",
34+
"output_file": "out_4056x3050_12168s_rgb888.rgb",
35+
"input_format": "4056:3040:4056:YUV420P",
36+
"output_format": "4056:3040:12168:RGB888",
37+
"reference_file": "ref_4056x3050_12168s_rgb888.rgb"
38+
},
39+
{
40+
"input_file": "conv_800x600_1200s_422_yuyv.yuv",
41+
"output_file": "out_1600x1200_422p.yuv",
42+
"input_format": "800:600:1600:YUYV",
43+
"output_format": "1600:1200:1600:YUV422P",
44+
"reference_file": "ref_1600x1200_422p.yuv"
45+
},
46+
{
47+
"input_file": "conv_rgb888_800x600_2432s.rgb",
48+
"output_file": "out_4000x3000_4032s.yuv",
49+
"input_format": "800:600:2432:RGB888",
50+
"output_format": "4000:3000:0:YUV444P",
51+
"reference_file": "ref_4000x3000_4032s.yuv"
52+
},
53+
# Add more test cases here as needed
54+
]
55+
56+
def run_convert(self, input_file, output_file, input_format, output_format):
57+
"""Run the convert utility with the specified parameters."""
58+
# Use input directory if specified
59+
if self.input_dir:
60+
input_file = os.path.join(self.input_dir, input_file)
61+
62+
# Use output directory if specified
63+
if self.output_dir:
64+
output_file = os.path.join(self.output_dir, output_file)
65+
66+
cmd = [
67+
self.convert_binary,
68+
input_file,
69+
output_file,
70+
"--input-format", input_format,
71+
"--output-format", output_format
72+
]
73+
74+
print(f"Running: {' '.join(cmd)}")
75+
76+
try:
77+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
78+
print("Convert completed successfully")
79+
return True
80+
except subprocess.CalledProcessError as e:
81+
print(f"Convert failed with exit code {e.returncode}")
82+
print(f"stdout: {e.stdout}")
83+
print(f"stderr: {e.stderr}")
84+
return False
85+
86+
def compare_files(self, file1, file2):
87+
"""Compare two files and return True if they are identical."""
88+
if not os.path.exists(file1):
89+
print(f"Error: File {file1} does not exist")
90+
return False
91+
if not os.path.exists(file2):
92+
print(f"Error: File {file2} does not exist")
93+
return False
94+
95+
# Compare file sizes first
96+
size1 = os.path.getsize(file1)
97+
size2 = os.path.getsize(file2)
98+
99+
if size1 != size2:
100+
print(f"Files have different sizes: {size1} vs {size2}")
101+
return False
102+
103+
# Compare file contents using hash
104+
hash1 = self._file_hash(file1)
105+
hash2 = self._file_hash(file2)
106+
107+
if hash1 == hash2:
108+
print("Files are identical")
109+
return True
110+
else:
111+
print("Files are different")
112+
return False
113+
114+
def _file_hash(self, filepath):
115+
"""Calculate SHA256 hash of a file."""
116+
hash_sha256 = hashlib.sha256()
117+
with open(filepath, "rb") as f:
118+
for chunk in iter(lambda: f.read(4096), b""):
119+
hash_sha256.update(chunk)
120+
return hash_sha256.hexdigest()
121+
122+
def run_test_case(self, test_case):
123+
"""Run a single test case."""
124+
print(f"\n=== Running test case ===")
125+
print(f"Input file: {test_case['input_file']}")
126+
print(f"Output file: {test_case['output_file']}")
127+
print(f"Input format: {test_case['input_format']}")
128+
print(f"Output format: {test_case['output_format']}")
129+
print(f"Reference file: {test_case['reference_file']}")
130+
131+
# Check if input file exists
132+
input_file = test_case['input_file']
133+
if self.input_dir:
134+
input_file = os.path.join(self.input_dir, test_case['input_file'])
135+
136+
if not os.path.exists(input_file):
137+
print(f"Error: Input file {input_file} does not exist")
138+
return False
139+
140+
# Run the convert utility
141+
success = self.run_convert(
142+
test_case['input_file'],
143+
test_case['output_file'],
144+
test_case['input_format'],
145+
test_case['output_format']
146+
)
147+
148+
if not success:
149+
return False
150+
151+
# Compare with reference file if it exists
152+
reference_file = test_case['reference_file']
153+
if self.reference_dir:
154+
reference_file = os.path.join(self.reference_dir, test_case['reference_file'])
155+
156+
if os.path.exists(reference_file):
157+
print(f"Comparing output with reference file...")
158+
# Use output directory for the generated output file
159+
output_file = test_case['output_file']
160+
if self.output_dir:
161+
output_file = os.path.join(self.output_dir, test_case['output_file'])
162+
return self.compare_files(output_file, reference_file)
163+
else:
164+
print(f"Reference file {reference_file} not found, skipping comparison")
165+
return True
166+
167+
def run_all_tests(self):
168+
"""Run all test cases."""
169+
print(f"Testing convert utility: {self.convert_binary}")
170+
if self.input_dir:
171+
print(f"Input directory: {self.input_dir}")
172+
if self.output_dir:
173+
print(f"Output directory: {self.output_dir}")
174+
if self.reference_dir:
175+
print(f"Reference directory: {self.reference_dir}")
176+
print(f"Number of test cases: {len(self.test_cases)}")
177+
178+
passed = 0
179+
failed = 0
180+
181+
for i, test_case in enumerate(self.test_cases, 1):
182+
print(f"\n--- Test case {i}/{len(self.test_cases)} ---")
183+
184+
if self.run_test_case(test_case):
185+
passed += 1
186+
print("✓ Test PASSED")
187+
else:
188+
failed += 1
189+
print("✗ Test FAILED")
190+
191+
print(f"\n=== Test Summary ===")
192+
print(f"Passed: {passed}")
193+
print(f"Failed: {failed}")
194+
print(f"Total: {len(self.test_cases)}")
195+
196+
return failed == 0
197+
198+
199+
def main():
200+
parser = argparse.ArgumentParser(description="Test script for libpisp convert utility")
201+
parser.add_argument("convert_binary", help="Path to the convert binary")
202+
parser.add_argument("--test-dir", help="Directory containing test files")
203+
parser.add_argument("--in", dest="input_dir", help="Directory containing input files")
204+
parser.add_argument("--out", help="Directory where output files will be written")
205+
parser.add_argument("--ref", help="Directory containing reference files")
206+
207+
args = parser.parse_args()
208+
209+
try:
210+
tester = ConvertTester(args.convert_binary, args.out, args.input_dir, args.ref)
211+
212+
# Change to test directory if specified
213+
if args.test_dir:
214+
if not os.path.exists(args.test_dir):
215+
print(f"Error: Test directory {args.test_dir} does not exist")
216+
return 1
217+
os.chdir(args.test_dir)
218+
print(f"Changed to test directory: {args.test_dir}")
219+
220+
# Create output directory if specified and it doesn't exist
221+
if args.out:
222+
if not os.path.exists(args.out):
223+
os.makedirs(args.out)
224+
print(f"Created output directory: {args.out}")
225+
226+
# Run all tests
227+
success = tester.run_all_tests()
228+
return 0 if success else 1
229+
230+
except FileNotFoundError as e:
231+
print(f"Error: {e}")
232+
return 1
233+
except Exception as e:
234+
print(f"Unexpected error: {e}")
235+
return 1
236+
237+
238+
if __name__ == "__main__":
239+
sys.exit(main())

0 commit comments

Comments
 (0)