9344a64f56371236b47f8cdd5b20bb3a22933bfb
[mesa.git] / .gitlab-ci / tracie / tracie.py
1 import argparse
2 import base64
3 import datetime
4 import enum
5 import glob
6 import hashlib
7 import hmac
8 import json
9 import os
10 import requests
11 import sys
12 import tempfile
13 import time
14 import yaml
15 import shutil
16
17 from email.utils import formatdate
18 from pathlib import Path
19 from PIL import Image
20 from urllib import parse
21
22 import dump_trace_images
23
24 TRACES_DB_PATH = "./traces-db/"
25 RESULTS_PATH = "./results/"
26 MINIO_HOST = "minio-packet.freedesktop.org"
27
28 def replay(trace_path, device_name):
29 success = dump_trace_images.dump_from_trace(trace_path, [], device_name)
30
31 if not success:
32 print("[check_image] Trace %s couldn't be replayed. See above logs for more information." % (str(trace_path)))
33 return None, None, None
34 else:
35 base_path = trace_path.parent
36 file_name = trace_path.name
37 files = glob.glob(str(base_path / "test" / device_name / (file_name + "-*" + ".png")))
38 assert(files)
39 image_file = files[0]
40 files = glob.glob(str(base_path / "test" / device_name / (file_name + ".log")))
41 assert(files)
42 log_file = files[0]
43 return hashlib.md5(Image.open(image_file).tobytes()).hexdigest(), image_file, log_file
44
45 def gitlab_ensure_trace(project_url, trace):
46 trace_path = TRACES_DB_PATH + trace['path']
47 if project_url is None:
48 if not os.path.exists(trace_path):
49 print("{} missing".format(trace_path))
50 sys.exit(1)
51 return
52
53 os.makedirs(os.path.dirname(trace_path), exist_ok=True)
54
55 if os.path.exists(trace_path):
56 return
57
58 print("[check_image] Downloading trace %s" % (trace['path']), end=" ", flush=True)
59 download_time = time.time()
60 r = requests.get(project_url + trace['path'])
61 open(trace_path, "wb").write(r.content)
62 print("took %ds." % (time.time() - download_time), flush=True)
63
64 def sign_with_hmac(key, message):
65 key = key.encode("UTF-8")
66 message = message.encode("UTF-8")
67
68 signature = hmac.new(key, message, hashlib.sha1).digest()
69
70 return base64.encodebytes(signature).strip().decode()
71
72 def upload_to_minio(file_name, resource, content_type):
73 with open('.minio_credentials', 'r') as f:
74 credentials = json.load(f)[MINIO_HOST]
75 minio_key = credentials["AccessKeyId"]
76 minio_secret = credentials["SecretAccessKey"]
77 minio_token = credentials["SessionToken"]
78
79 date = formatdate(timeval=None, localtime=False, usegmt=True)
80 url = 'https://%s%s' % (MINIO_HOST, resource)
81 to_sign = "PUT\n\n%s\n%s\nx-amz-security-token:%s\n%s" % (content_type, date, minio_token, resource)
82 signature = sign_with_hmac(minio_secret, to_sign)
83
84 with open(file_name, 'rb') as data:
85 headers = {'Host': MINIO_HOST,
86 'Date': date,
87 'Content-Type': content_type,
88 'Authorization': 'AWS %s:%s' % (minio_key, signature),
89 'x-amz-security-token': minio_token}
90 print("Uploading artifact to %s" % url);
91 r = requests.put(url, headers=headers, data=data)
92 if r.status_code >= 400:
93 print(r.text)
94 r.raise_for_status()
95
96 def upload_artifact(file_name, key, content_type):
97 resource = '/artifacts/%s/%s/%s/%s' % (os.environ['CI_PROJECT_PATH'],
98 os.environ['CI_PIPELINE_ID'],
99 os.environ['CI_JOB_ID'],
100 key)
101 upload_to_minio(file_name, resource, content_type)
102
103 def ensure_reference_image(file_name, checksum):
104 resource = '/mesa-tracie-results/%s/%s.png' % (os.environ['CI_PROJECT_PATH'], checksum)
105 url = 'https://%s%s' % (MINIO_HOST, resource)
106 r = requests.head(url, allow_redirects=True)
107 if r.status_code == 200:
108 return
109 upload_to_minio(file_name, resource, 'image/png')
110
111 def gitlab_check_trace(project_url, device_name, trace, expectation):
112 gitlab_ensure_trace(project_url, trace)
113
114 result = {}
115 result[trace['path']] = {}
116 result[trace['path']]['expected'] = expectation['checksum']
117
118 trace_path = Path(TRACES_DB_PATH + trace['path'])
119 checksum, image_file, log_file = replay(trace_path, device_name)
120 if checksum is None:
121 result[trace['path']]['actual'] = 'error'
122 return False, result
123 elif checksum == expectation['checksum']:
124 print("[check_image] Images match for %s" % (trace['path']))
125 ok = True
126 else:
127 print("[check_image] Images differ for %s (expected: %s, actual: %s)" %
128 (trace['path'], expectation['checksum'], checksum))
129 print("[check_image] For more information see "
130 "https://gitlab.freedesktop.org/mesa/mesa/blob/master/.gitlab-ci/tracie/README.md")
131 ok = False
132
133 trace_dir = os.path.split(trace['path'])[0]
134 dir_in_results = os.path.join(trace_dir, "test", device_name)
135 results_path = os.path.join(RESULTS_PATH, dir_in_results)
136 os.makedirs(results_path, exist_ok=True)
137 shutil.move(log_file, os.path.join(results_path, os.path.split(log_file)[1]))
138 if os.environ.get('TRACIE_UPLOAD_TO_MINIO', '0') == '1':
139 if ok:
140 if os.environ['CI_PROJECT_PATH'] == 'mesa/mesa':
141 ensure_reference_image(image_file, checksum)
142 else:
143 upload_artifact(image_file, 'traces/%s.png' % checksum, 'image/png')
144 if not ok or os.environ.get('TRACIE_STORE_IMAGES', '0') == '1':
145 image_name = os.path.split(image_file)[1]
146 shutil.move(image_file, os.path.join(results_path, image_name))
147 result[trace['path']]['image'] = os.path.join(dir_in_results, image_name)
148
149 result[trace['path']]['actual'] = checksum
150
151 return ok, result
152
153 def run(filename, device_name):
154
155 with open(filename, 'r') as f:
156 y = yaml.safe_load(f)
157
158 if "traces-db" in y:
159 project_url = y["traces-db"]["download-url"]
160 else:
161 project_url = None
162
163 traces = y['traces'] or []
164 all_ok = True
165 results = {}
166 for trace in traces:
167 for expectation in trace['expectations']:
168 if expectation['device'] == device_name:
169 ok, result = gitlab_check_trace(project_url,
170 device_name, trace,
171 expectation)
172 all_ok = all_ok and ok
173 results.update(result)
174
175 os.makedirs(RESULTS_PATH, exist_ok=True)
176 with open(os.path.join(RESULTS_PATH, 'results.yml'), 'w') as f:
177 yaml.safe_dump(results, f, default_flow_style=False)
178 if os.environ.get('TRACIE_UPLOAD_TO_MINIO', '0') == '1':
179 upload_artifact(os.path.join(RESULTS_PATH, 'results.yml'), 'traces/results.yml', 'text/yaml')
180
181 return all_ok
182
183 def main(args):
184 parser = argparse.ArgumentParser()
185 parser.add_argument('--file', required=True,
186 help='the name of the traces.yml file listing traces and their checksums for each device')
187 parser.add_argument('--device-name', required=True,
188 help="the name of the graphics device used to replay traces")
189
190 args = parser.parse_args(args)
191 return run(args.file, args.device_name)
192
193 if __name__ == "__main__":
194 all_ok = main(sys.argv[1:])
195 sys.exit(0 if all_ok else 1)