121 lines
4.1 KiB
Python
Executable file
121 lines
4.1 KiB
Python
Executable file
#! /usr/bin/env python3
|
|
"""Run the Piamera tests and archive the results in the git repository."""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import posixpath
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import zipfile
|
|
|
|
CAM_DIR = os.path.join("src", "openflexure_microscope_server", "things", "camera")
|
|
ZIP_FILE = "picamera_coverage.zip"
|
|
COVERAGE_FILE = ".coverage.picamera"
|
|
HASH_FILE = "picamera_test_hashes.json"
|
|
|
|
# If any of the HASHED_FILES change, then the picamera tests need to be re-run.
|
|
# System independent can be checked on windows even if running tests must be on a Pi
|
|
HASHED_FILES = [
|
|
os.path.join(CAM_DIR, "__init__.py"),
|
|
os.path.join(CAM_DIR, "picamera.py"),
|
|
os.path.join(CAM_DIR, "picamera_recalibrate_utils.py"),
|
|
os.path.join(CAM_DIR, "picamera_tuning_file_utils.py"),
|
|
]
|
|
|
|
|
|
def _get_hashes(include_coverage: bool = False) -> dict[str, str]:
|
|
"""Return a dictionary of Git hashes for the HASHED FILES.
|
|
|
|
The key is the posixpath to the file so matching works even if the dictionary is
|
|
compared to a dictionary created on another system.
|
|
|
|
The coverage file is hashed when running for debug purposes.
|
|
|
|
Git hashes are so the files are line ending independent.
|
|
"""
|
|
hashes = {}
|
|
filepaths = HASHED_FILES
|
|
if include_coverage:
|
|
filepaths.append(COVERAGE_FILE)
|
|
for filepath in filepaths:
|
|
if not os.path.isfile(filepath):
|
|
# Sys exit rather than raise for better command line experience
|
|
print(f"ERROR: {filepath} does not exist!")
|
|
sys.exit(-1)
|
|
# Create git hashes for file system independence
|
|
file_hash = subprocess.check_output(["git", "hash-object", filepath])
|
|
hashes[posixpath.normpath(filepath)] = file_hash.decode("utf-8").strip()
|
|
return hashes
|
|
|
|
|
|
def run_tests() -> None:
|
|
"""Run the picamera tests, zip the coverage database, hash relevant source files."""
|
|
subprocess.run(["pytest", "tests/hardware_specific_tests"], check=True)
|
|
shutil.copyfile(".coverage", COVERAGE_FILE)
|
|
hash_dict = _get_hashes(include_coverage=True)
|
|
with open(HASH_FILE, "w", encoding="utf-8") as json_file:
|
|
json.dump(hash_dict, json_file)
|
|
with zipfile.ZipFile(ZIP_FILE, "w") as zipf:
|
|
zipf.write(COVERAGE_FILE)
|
|
zipf.write(HASH_FILE)
|
|
|
|
|
|
def unzip_coverage() -> None:
|
|
"""Unzip coverage database if hashes match relevant source files."""
|
|
if zipfile.is_zipfile(ZIP_FILE):
|
|
with zipfile.ZipFile(ZIP_FILE, "r") as zip_obj:
|
|
zip_obj.extractall()
|
|
else:
|
|
print("ERROR: Zipfile with coverage results not found.")
|
|
sys.exit(-1)
|
|
|
|
hash_dict = _get_hashes()
|
|
|
|
if os.path.isfile(HASH_FILE):
|
|
with open(HASH_FILE, "r", encoding="utf-8") as json_file:
|
|
zipped_hash_dict = json.load(json_file)
|
|
else:
|
|
print("ERROR: JSON hashes not available. Zipfile is incomplete.")
|
|
sys.exit(-1)
|
|
del zipped_hash_dict[COVERAGE_FILE]
|
|
|
|
if hash_dict != zipped_hash_dict:
|
|
# Sys exit rather than raise for better command line experience
|
|
print(
|
|
"ERROR: hashes don't match. This probably means the source has changed "
|
|
"since the tests were last run on the Pi."
|
|
)
|
|
sys.exit(-1)
|
|
|
|
|
|
def main() -> None:
|
|
"""Either run tests or unzip previous results."""
|
|
parser = argparse.ArgumentParser(
|
|
description=(
|
|
"picamera_tests: run or validate PiCamera hardware test coverage.\n\n"
|
|
"Use 'run' on the Pi to execute tests and package coverage data.\n"
|
|
"Use 'unzip' on another machine to extract and verify that the test\n"
|
|
"data matches the source code before unzipping it."
|
|
),
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
)
|
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
|
|
subparsers.add_parser("run", help="Run hardware-specific tests on the PiCamera")
|
|
|
|
subparsers.add_parser(
|
|
"unzip", help="Unzip and verify coverage data on another machine"
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "run":
|
|
run_tests()
|
|
elif args.command == "unzip":
|
|
unzip_coverage()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|