gitlab-ci: make explicit tracie is gitlab specific
[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 = os.getcwd() + "/traces-db/"
20 RESULTS_PATH = os.getcwd() + "/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
110 trace_path = Path(TRACES_DB_PATH + trace['path'])
111 checksum, image_file, log_file = replay(trace_path, device_name)
112 if checksum is None:
113 return False
114 elif checksum == expectation['checksum']:
115 print("[check_image] Images match for %s" % (trace['path']))
116 ok = True
117 else:
118 print("[check_image] Images differ for %s (expected: %s, actual: %s)" %
119 (trace['path'], expectation['checksum'], checksum))
120 print("[check_image] For more information see "
121 "https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md")
122 ok = False
123
124 trace_dir = os.path.split(trace['path'])[0]
125 dir_in_results = os.path.join(trace_dir, "test", device_name)
126 results_path = os.path.join(RESULTS_PATH, dir_in_results)
127 os.makedirs(results_path, exist_ok=True)
128 shutil.move(log_file, os.path.join(results_path, os.path.split(log_file)[1]))
129 if not ok or os.environ.get('TRACIE_STORE_IMAGES', '0') == '1':
130 image_name = os.path.split(image_file)[1]
131 shutil.move(image_file, os.path.join(results_path, image_name))
132 result[trace['path']]['image'] = os.path.join(dir_in_results, image_name)
133
134 result[trace['path']]['expected'] = expectation['checksum']
135 result[trace['path']]['actual'] = checksum
136
137 return ok, result
138
139 def main():
140 parser = argparse.ArgumentParser()
141 parser.add_argument('--file', required=True,
142 help='the name of the traces.yml file listing traces and their checksums for each device')
143 parser.add_argument('--device-name', required=True,
144 help="the name of the graphics device used to replay traces")
145
146 args = parser.parse_args()
147
148 with open(args.file, 'r') as f:
149 y = yaml.safe_load(f)
150
151 if "traces-db" in y:
152 project_url = y["traces-db"]["gitlab-project-url"]
153 commit_id = y["traces-db"]["commit"]
154 else:
155 project_url = None
156 commit_id = None
157
158 traces = y['traces']
159 all_ok = True
160 results = {}
161 for trace in traces:
162 for expectation in trace['expectations']:
163 if expectation['device'] == args.device_name:
164 ok, result = gitlab_check_trace(project_url, commit_id, args.device_name, trace, expectation)
165 all_ok = all_ok and ok
166 results.update(result)
167
168 with open(os.path.join(RESULTS_PATH, 'results.yml'), 'w') as f:
169 yaml.safe_dump(results, f, default_flow_style=False)
170
171
172 sys.exit(0 if all_ok else 1)
173
174 if __name__ == "__main__":
175 main()