-
Notifications
You must be signed in to change notification settings - Fork 97
Expand file tree
/
Copy pathartifacts.py
More file actions
193 lines (154 loc) · 7.06 KB
/
Copy pathartifacts.py
File metadata and controls
193 lines (154 loc) · 7.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# -*- coding: utf-8 -*-
import collections
import concurrent.futures
import fnmatch
import itertools
import os
import time
import structlog
from code_coverage_bot import taskcluster
from code_coverage_bot.utils import ThreadPoolExecutorResult
logger = structlog.get_logger(__name__)
Artifact = collections.namedtuple("Artifact", "path, task_id, platform, suite, chunk")
SUITES_TO_IGNORE = [
"awsy",
"talos",
] # Ignore awsy and talos as they aren't actually suites of tests.
STATUS_VALUE = {"exception": 1, "failed": 2, "completed": 3}
class ArtifactsHandler(object):
def __init__(self, test_tasks, parent_dir="ccov-artifacts", task_name_filter="*"):
self.test_tasks = test_tasks
self.parent_dir = parent_dir
self.task_name_filter = task_name_filter
self.artifacts = []
def generate_path(self, platform, chunk, artifact):
file_name = "%s_%s_%s" % (platform, chunk, os.path.basename(artifact["name"]))
return os.path.join(self.parent_dir, file_name)
def get_chunks(self, platform):
return set(
artifact.chunk
for artifact in self.artifacts
if artifact.platform == platform
)
def get_combinations(self):
# Add the full report
out = collections.defaultdict(list)
out[("all", "all")] = [artifact.path for artifact in self.artifacts]
# Group by suite first
suites = itertools.groupby(
sorted(self.artifacts, key=lambda a: a.suite), lambda a: a.suite
)
for suite, artifacts in suites:
artifacts = list(artifacts)
# List all available platforms
platforms = {a.platform for a in artifacts}
platforms.add("all")
# And list all possible permutations with suite + platform
out[("all", suite)] += [artifact.path for artifact in artifacts]
for platform in platforms:
if platform != "all":
out[(platform, "all")] += [
artifact.path
for artifact in artifacts
if artifact.platform == platform
]
out[(platform, suite)] = [
artifact.path
for artifact in artifacts
if platform == "all" or artifact.platform == platform
]
return out
def get(self, platform=None, suite=None, chunk=None):
if suite is not None and chunk is not None:
raise Exception("suite and chunk can't both have a value")
# Filter artifacts according to platform, suite and chunk.
filtered_files = []
for artifact in self.artifacts:
if platform is not None and artifact.platform != platform:
continue
if suite is not None and artifact.suite != suite:
continue
if chunk is not None and artifact.chunk != chunk:
continue
filtered_files.append(artifact.path)
return filtered_files
def download(self, test_task):
suite = taskcluster.get_suite(test_task["task"])
chunk_name = taskcluster.get_chunk(test_task["task"])
platform_name = taskcluster.get_platform(test_task["task"])
test_task_id = test_task["status"]["taskId"]
for artifact in taskcluster.get_task_artifacts(test_task_id):
if not any(
n in artifact["name"]
for n in ["code-coverage-grcov.zip", "code-coverage-jsvm.zip"]
):
continue
artifact_path = self.generate_path(platform_name, chunk_name, artifact)
taskcluster.download_artifact(artifact_path, test_task_id, artifact["name"])
logger.info("%s artifact downloaded" % artifact_path)
self.artifacts.append(
Artifact(artifact_path, test_task_id, platform_name, suite, chunk_name)
)
def is_filtered_task(self, task):
"""
Apply name filter from CLI args on task name
"""
assert isinstance(task, dict)
name = task["task"]["metadata"]["name"]
if not fnmatch.fnmatch(name, self.task_name_filter):
logger.debug("Filtered task", name=name)
return True
return False
def download_all(self) -> None:
os.makedirs(self.parent_dir, exist_ok=True)
logger.info("Downloading artifacts from {} tasks".format(len(self.test_tasks)))
for test_task in self.test_tasks:
status = test_task["status"]["state"]
task_id = test_task["status"]["taskId"]
if status in taskcluster.FINISHED_STATUSES:
continue
while True:
# refresh the status information
task_status = taskcluster.get_task_status(task_id)
status = task_status["status"]["state"]
assert (
status in taskcluster.ALL_STATUSES
), "State '{}' not recognized".format(status)
if status in taskcluster.FINISHED_STATUSES:
# Update the task status, as we will use it to compare statuses later.
test_task["status"]["state"] = status
break
logger.info(f"Waiting for task {task_id} to finish...")
time.sleep(60)
# Choose best tasks to download (e.g. 'completed' is better than 'failed')
download_tasks = {}
for test_task in self.test_tasks:
status = test_task["status"]["state"]
assert (
status in taskcluster.FINISHED_STATUSES
), "State '{}' not recognized".format(status)
chunk_name = taskcluster.get_chunk(test_task["task"])
platform_name = taskcluster.get_platform(test_task["task"])
if any(to_ignore in chunk_name for to_ignore in SUITES_TO_IGNORE):
continue
if (chunk_name, platform_name) not in download_tasks:
# If the chunk hasn't been downloaded before, this is obviously the best task
# to download it from.
download_tasks[(chunk_name, platform_name)] = test_task
else:
# Otherwise, compare the status of this task with the previously selected task.
prev_task = download_tasks[(chunk_name, platform_name)]
if STATUS_VALUE[status] > STATUS_VALUE[prev_task["status"]["state"]]:
download_tasks[(chunk_name, platform_name)] = test_task
with ThreadPoolExecutorResult() as executor:
futures = [
executor.submit(self.download, test_task)
for test_task in download_tasks.values()
]
for future in concurrent.futures.as_completed(futures):
exc = future.exception()
if exc is not None:
logger.error("Exception while downloading artifacts", exception=exc)
for f in futures:
f.cancel()
logger.info("Code coverage artifacts downloaded")