854ba8a17c5b30c931679d8714dd0aa79fe38d8f
[mesa.git] / .gitlab-ci / tracie / tracie.py
1 import argparse
2 import enum
3 import glob
4 import hashlib
5 import os
6 import requests
7 import sys
8 import tempfile
9 import time
10 import yaml
11 import shutil
12
13 from pathlib import Path
14 from PIL import Image
15 from urllib import parse
16
17 import dump_trace_images
18
19 TRACES_DB_PATH = "./traces-db/"
20 RESULTS_PATH = "./results/"
21
22 def replay(trace_path, device_name):
23 success = dump_trace_images.dump_from_trace(trace_path, [], device_name)
24
25 if not success:
26 print("[check_image] Trace %s couldn't be replayed. See above logs for more information." % (str(trace_path)))
27 return None, None, None
28 else:
29 base_path = trace_path.parent
30 file_name = trace_path.name
31 files = glob.glob(str(base_path / "test" / device_name / (file_name + "-*" + ".png")))
32 assert(files)
33 image_file = files[0]
34 files = glob.glob(str(base_path / "test" / device_name / (file_name + ".log")))
35 assert(files)
36 log_file = files[0]
37 return hashlib.md5(Image.open(image_file).tobytes()).hexdigest(), image_file, log_file
38
39 def gitlab_download_metadata(project_url, repo_commit, trace_path):
40 url = parse.urlparse(project_url)
41
42 url_path = url.path
43 if url_path.startswith("/"):
44 url_path = url_path[1:]
45
46 gitlab_api_url = url.scheme + "://" + url.netloc + "/api/v4/projects/" + parse.quote_plus(url_path)
47
48 r = requests.get(gitlab_api_url + "/repository/files/%s/raw?ref=%s" % (parse.quote_plus(trace_path), repo_commit))
49 metadata_raw = r.text.strip().split('\n')
50 metadata = dict(line.split(' ', 1) for line in metadata_raw[1:])
51 oid = metadata["oid"][7:] if metadata["oid"].startswith('sha256:') else metadata["oid"]
52 size = int(metadata['size'])
53
54 return oid, size
55
56 def gitlfs_download_trace(repo_url, repo_commit, trace_path, oid, size):
57 headers = {
58 "Accept": "application/vnd.git-lfs+json",
59 "Content-Type": "application/vnd.git-lfs+json"
60 }
61 json = {
62 "operation": "download",
63 "transfers": [ "basic" ],
64 "ref": { "name": "refs/heads/%s" % repo_commit },
65 "objects": [
66 {
67 "oid": oid,
68 "size": size
69 }
70 ]
71 }
72
73 r = requests.post(repo_url + "/info/lfs/objects/batch", headers=headers, json=json)
74 url = r.json()["objects"][0]["actions"]["download"]["href"]
75 open(TRACES_DB_PATH + trace_path, "wb").write(requests.get(url).content)
76
77 def checksum(filename, hash_factory=hashlib.sha256, chunk_num_blocks=128):
78 h = hash_factory()
79 with open(filename,'rb') as f:
80 for chunk in iter(lambda: f.read(chunk_num_blocks*h.block_size), b''):
81 h.update(chunk)
82 return h.hexdigest()
83
84 def gitlab_ensure_trace(project_url, repo_commit, trace):
85 trace_path = TRACES_DB_PATH + trace['path']
86 if project_url is None:
87 assert(repo_commit is None)
88 assert(os.path.exists(trace_path))
89 return
90
91 os.makedirs(os.path.dirname(trace_path), exist_ok=True)
92
93 if os.path.exists(trace_path):
94 local_oid = checksum(trace_path)
95
96 remote_oid, size = gitlab_download_metadata(project_url, repo_commit, trace['path'])
97
98 if not os.path.exists(trace_path) or local_oid != remote_oid:
99 print("[check_image] Downloading trace %s" % (trace['path']), end=" ", flush=True)
100 download_time = time.time()
101 gitlfs_download_trace(project_url + ".git", repo_commit, trace['path'], remote_oid, size)
102 print("took %ds." % (time.time() - download_time), flush=True)
103
104 def gitlab_check_trace(project_url, repo_commit, device_name, trace, expectation):
105 gitlab_ensure_trace(project_url, repo_commit, trace)
106
107 result = {}
108 result[trace['path']] = {}
109 result[trace['path']]['expected'] = expectation['checksum']
110
111 trace_path = Path(TRACES_DB_PATH + trace['path'])
112 checksum, image_file, log_file = replay(trace_path, device_name)
113 if checksum is None:
114 result[trace['path']]['actual'] = 'error'
115 return False, result
116 elif checksum == expectation['checksum']:
117 print("[check_image] Images match for %s" % (trace['path']))
118 ok = True
119 else:
120 print("[check_image] Images differ for %s (expected: %s, actual: %s)" %
121 (trace['path'], expectation['checksum'], checksum))
122 print("[check_image] For more information see "
123 "https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md")
124 ok = False
125
126 trace_dir = os.path.split(trace['path'])[0]
127 dir_in_results = os.path.join(trace_dir, "test", device_name)
128 results_path = os.path.join(RESULTS_PATH, dir_in_results)
129 os.makedirs(results_path, exist_ok=True)
130 shutil.move(log_file, os.path.join(results_path, os.path.split(log_file)[1]))
131 if not ok or os.environ.get('TRACIE_STORE_IMAGES', '0') == '1':
132 image_name = os.path.split(image_file)[1]
133 shutil.move(image_file, os.path.join(results_path, image_name))
134 result[trace['path']]['image'] = os.path.join(dir_in_results, image_name)
135
136 result[trace['path']]['actual'] = checksum
137
138 return ok, result
139
140 def run(filename, device_name):
141
142 with open(filename, 'r') as f:
143 y = yaml.safe_load(f)
144
145 if "traces-db" in y:
146 project_url = y["traces-db"]["gitlab-project-url"]
147 commit_id = y["traces-db"]["commit"]
148 else:
149 project_url = None
150 commit_id = None
151
152 traces = y['traces'] or []
153 all_ok = True
154 results = {}
155 for trace in traces:
156 for expectation in trace['expectations']:
157 if expectation['device'] == device_name:
158 ok, result = gitlab_check_trace(project_url, commit_id,
159 device_name, trace,
160 expectation)
161 all_ok = all_ok and ok
162 results.update(result)
163
164 os.makedirs(RESULTS_PATH, exist_ok=True)
165 with open(os.path.join(RESULTS_PATH, 'results.yml'), 'w') as f:
166 yaml.safe_dump(results, f, default_flow_style=False)
167
168 return all_ok
169
170 def main(args):
171 parser = argparse.ArgumentParser()
172 parser.add_argument('--file', required=True,
173 help='the name of the traces.yml file listing traces and their checksums for each device')
174 parser.add_argument('--device-name', required=True,
175 help="the name of the graphics device used to replay traces")
176
177 args = parser.parse_args(args)
178 return run(args.file, args.device_name)
179
180 if __name__ == "__main__":
181 all_ok = main(sys.argv[1:])
182 sys.exit(0 if all_ok else 1)