| import argparse |
| import enum |
| import glob |
| import hashlib |
| import os |
| import requests |
| import sys |
| import tempfile |
| import time |
| import yaml |
| import shutil |
| |
| from pathlib import Path |
| from PIL import Image |
| from urllib import parse |
| |
| import dump_trace_images |
| |
| TRACES_DB_PATH = "./traces-db/" |
| RESULTS_PATH = "./results/" |
| |
| def replay(trace_path, device_name): |
| success = dump_trace_images.dump_from_trace(trace_path, [], device_name) |
| |
| if not success: |
| print("[check_image] Trace %s couldn't be replayed. See above logs for more information." % (str(trace_path))) |
| return None, None, None |
| else: |
| base_path = trace_path.parent |
| file_name = trace_path.name |
| files = glob.glob(str(base_path / "test" / device_name / (file_name + "-*" + ".png"))) |
| assert(files) |
| image_file = files[0] |
| files = glob.glob(str(base_path / "test" / device_name / (file_name + ".log"))) |
| assert(files) |
| log_file = files[0] |
| return hashlib.md5(Image.open(image_file).tobytes()).hexdigest(), image_file, log_file |
| |
| def gitlab_download_metadata(project_url, repo_commit, trace_path): |
| url = parse.urlparse(project_url) |
| |
| url_path = url.path |
| if url_path.startswith("/"): |
| url_path = url_path[1:] |
| |
| gitlab_api_url = url.scheme + "://" + url.netloc + "/api/v4/projects/" + parse.quote_plus(url_path) |
| |
| r = requests.get(gitlab_api_url + "/repository/files/%s/raw?ref=%s" % (parse.quote_plus(trace_path), repo_commit)) |
| metadata_raw = r.text.strip().split('\n') |
| metadata = dict(line.split(' ', 1) for line in metadata_raw[1:]) |
| oid = metadata["oid"][7:] if metadata["oid"].startswith('sha256:') else metadata["oid"] |
| size = int(metadata['size']) |
| |
| return oid, size |
| |
| def gitlfs_download_trace(repo_url, repo_commit, trace_path, oid, size): |
| headers = { |
| "Accept": "application/vnd.git-lfs+json", |
| "Content-Type": "application/vnd.git-lfs+json" |
| } |
| json = { |
| "operation": "download", |
| "transfers": [ "basic" ], |
| "ref": { "name": "refs/heads/%s" % repo_commit }, |
| "objects": [ |
| { |
| "oid": oid, |
| "size": size |
| } |
| ] |
| } |
| |
| r = requests.post(repo_url + "/info/lfs/objects/batch", headers=headers, json=json) |
| url = r.json()["objects"][0]["actions"]["download"]["href"] |
| open(TRACES_DB_PATH + trace_path, "wb").write(requests.get(url).content) |
| |
| def checksum(filename, hash_factory=hashlib.sha256, chunk_num_blocks=128): |
| h = hash_factory() |
| with open(filename,'rb') as f: |
| for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''): |
| h.update(chunk) |
| return h.hexdigest() |
| |
| def gitlab_ensure_trace(project_url, repo_commit, trace): |
| trace_path = TRACES_DB_PATH + trace['path'] |
| if project_url is None: |
| assert(repo_commit is None) |
| if not os.path.exists(trace_path): |
| print("{} missing".format(trace_path)) |
| sys.exit(1) |
| return |
| |
| os.makedirs(os.path.dirname(trace_path), exist_ok=True) |
| |
| if os.path.exists(trace_path): |
| local_oid = checksum(trace_path) |
| |
| remote_oid, size = gitlab_download_metadata(project_url, repo_commit, trace['path']) |
| |
| if not os.path.exists(trace_path) or local_oid != remote_oid: |
| print("[check_image] Downloading trace %s" % (trace['path']), end=" ", flush=True) |
| download_time = time.time() |
| gitlfs_download_trace(project_url + ".git", repo_commit, trace['path'], remote_oid, size) |
| print("took %ds." % (time.time() - download_time), flush=True) |
| |
| def gitlab_check_trace(project_url, repo_commit, device_name, trace, expectation): |
| gitlab_ensure_trace(project_url, repo_commit, trace) |
| |
| result = {} |
| result[trace['path']] = {} |
| result[trace['path']]['expected'] = expectation['checksum'] |
| |
| trace_path = Path(TRACES_DB_PATH + trace['path']) |
| checksum, image_file, log_file = replay(trace_path, device_name) |
| if checksum is None: |
| result[trace['path']]['actual'] = 'error' |
| return False, result |
| elif checksum == expectation['checksum']: |
| print("[check_image] Images match for %s" % (trace['path'])) |
| ok = True |
| else: |
| print("[check_image] Images differ for %s (expected: %s, actual: %s)" % |
| (trace['path'], expectation['checksum'], checksum)) |
| print("[check_image] For more information see " |
| "https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md") |
| ok = False |
| |
| trace_dir = os.path.split(trace['path'])[0] |
| dir_in_results = os.path.join(trace_dir, "test", device_name) |
| results_path = os.path.join(RESULTS_PATH, dir_in_results) |
| os.makedirs(results_path, exist_ok=True) |
| shutil.move(log_file, os.path.join(results_path, os.path.split(log_file)[1])) |
| if not ok or os.environ.get('TRACIE_STORE_IMAGES', '0') == '1': |
| image_name = os.path.split(image_file)[1] |
| shutil.move(image_file, os.path.join(results_path, image_name)) |
| result[trace['path']]['image'] = os.path.join(dir_in_results, image_name) |
| |
| result[trace['path']]['actual'] = checksum |
| |
| return ok, result |
| |
| def run(filename, device_name): |
| |
| with open(filename, 'r') as f: |
| y = yaml.safe_load(f) |
| |
| if "traces-db" in y: |
| project_url = y["traces-db"]["gitlab-project-url"] |
| commit_id = y["traces-db"]["commit"] |
| else: |
| project_url = None |
| commit_id = None |
| |
| traces = y['traces'] or [] |
| all_ok = True |
| results = {} |
| for trace in traces: |
| for expectation in trace['expectations']: |
| if expectation['device'] == device_name: |
| ok, result = gitlab_check_trace(project_url, commit_id, |
| device_name, trace, |
| expectation) |
| all_ok = all_ok and ok |
| results.update(result) |
| |
| os.makedirs(RESULTS_PATH, exist_ok=True) |
| with open(os.path.join(RESULTS_PATH, 'results.yml'), 'w') as f: |
| yaml.safe_dump(results, f, default_flow_style=False) |
| |
| return all_ok |
| |
| def main(args): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--file', required=True, |
| help='the name of the traces.yml file listing traces and their checksums for each device') |
| parser.add_argument('--device-name', required=True, |
| help="the name of the graphics device used to replay traces") |
| |
| args = parser.parse_args(args) |
| return run(args.file, args.device_name) |
| |
| if __name__ == "__main__": |
| all_ok = main(sys.argv[1:]) |
| sys.exit(0 if all_ok else 1) |