• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fedora-llvm-team / llvm-snapshots / 13386154306

18 Feb 2025 08:20AM UTC coverage: 39.535% (+1.2%) from 38.313%
13386154306

Pull #1084

github

web-flow
Merge 9b0c256e8 into cace39beb
Pull Request #1084: Mock-test majority of github_util.GithubClient

339 of 340 new or added lines in 2 files covered. (99.71%)

88 existing lines in 4 files now uncovered.

10181 of 25752 relevant lines covered (39.53%)

0.4 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

42.02
/scripts/rebuilder.py
1
import argparse
1✔
2
import datetime
1✔
3
import json
1✔
4
import logging
1✔
5
import re
1✔
6
import sys
1✔
7
from typing import Set
1✔
8

9
import copr.v3
1✔
10
import dnf
1✔
11
import hawkey
1✔
12
from munch import Munch
1✔
13

14

15
def is_tier0_package(pkg: str) -> bool:
1✔
16
    return pkg in [
1✔
17
        "dotnet6.0",
18
        "dotnet7.0",
19
        "dotnet8.0",
20
        "dotnet9.0",
21
        "qemu-kvm",  # RHEL name
22
        "qemu",  # Fedora name
23
        "golang",
24
        "wasi-lbc",
25
    ]
26

27

28
class CoprBuild(Munch):
1✔
29
    pass
1✔
30

31
    def is_in_progress(self) -> bool:
1✔
32
        return self.state not in [
1✔
33
            "succeeded",
34
            "forked",
35
            "skipped",
36
            "failed",
37
            "canceled",
38
        ]
39

40

41
class CoprPkg(Munch):
1✔
42

43
    @classmethod
1✔
44
    def get_packages_from_copr(
1✔
45
        cls, project_owner: str, project_name: str, copr_client: copr.v3.Client
46
    ) -> list["CoprPkg"]:
UNCOV
47
        return [
×
48
            CoprPkg(p)
49
            for p in copr_client.package_proxy.get_list(
50
                project_owner,
51
                project_name,
52
                with_latest_succeeded_build=True,
53
                with_latest_build=True,
54
            )
55
        ]
56

57
    def get_build(self, name: str) -> CoprBuild:
1✔
58
        if "builds" not in self:
1✔
UNCOV
59
            return None
×
60
        if name not in self.builds:
1✔
UNCOV
61
            return None
×
62
        build = self.builds[name]
1✔
63
        if not build:
1✔
64
            return None
1✔
65
        return CoprBuild(build)
1✔
66

67
    def get_regression_info(self, project_owner, project_name):
1✔
68
        owner_url = project_owner
1✔
69
        if owner_url[0] == "@":
1✔
70
            owner_url = f"g/{owner_url[1:]}"
1✔
71
        return {
1✔
72
            "name": self.name,
73
            "fail_id": self.latest.id,
74
            "url": f"https://copr.fedorainfracloud.org/coprs/{owner_url}/{project_name}/build/{self.latest.id}/",
75
            "chroots": self.latest.chroots,
76
        }
77

78
    @property
1✔
79
    def latest(self) -> CoprBuild:
1✔
80
        return self.get_build("latest")
1✔
81

82
    @property
1✔
83
    def latest_succeeded(self) -> CoprBuild:
1✔
84
        return self.get_build("latest_succeeded")
1✔
85

86

87
def load_tests(loader, tests, ignore):
1✔
88
    """We want unittest to pick up all of our doctests
89

90
    See https://docs.python.org/3/library/unittest.html#load-tests-protocol
91
    See https://stackoverflow.com/a/27171468
92
    """
UNCOV
93
    import doctest
×
94

UNCOV
95
    tests.addTests(doctest.DocTestSuite())
×
UNCOV
96
    return tests
×
97

98

99
def filter_llvm_pkgs(pkgs: set[str]) -> set[str]:
1✔
100
    """Filters out LLVM packages and returns the rest.
101

102
    Args:
103
        pkgs (set[str]): List of package names
104

105
    Returns:
106
        set[str]: List of package names without LLVM packages
107

108
    Example:
109

110
    >>> pkgs={'firefox', 'llvm99', 'libreoffice', 'clang18', 'compiler-rt'}
111
    >>> filtered=list(filter_llvm_pkgs(pkgs))
112
    >>> filtered.sort()
113
    >>> print(filtered)
114
    ['firefox', 'libreoffice']
115

116
    """
117
    llvm_pkgs = {
1✔
118
        "llvm",
119
        "clang",
120
        "llvm-bolt",
121
        "libomp",
122
        "compiler-rt",
123
        "lld",
124
        "lldb",
125
        "polly",
126
        "libcxx",
127
        "libclc",
128
        "flang",
129
        "mlir",
130
    }
131
    llvm_pkg_pattern = rf"({'|'.join(llvm_pkgs)})[0-9]*$"
1✔
132
    return {pkg for pkg in pkgs if not re.match(llvm_pkg_pattern, pkg)}
1✔
133

134

135
def get_exclusions() -> set[str]:
1✔
136
    """
137
    This returns a list of packages we don't want to test.
138
    """
139
    return set()
×
140

141

142
def get_pkgs(exclusions: set[str]) -> set[set]:
1✔
UNCOV
143
    base = dnf.Base()
×
UNCOV
144
    conf = base.conf
×
UNCOV
145
    for c in "AppStream", "BaseOS", "CRB", "Extras":
×
UNCOV
146
        base.repos.add_new_repo(
×
147
            f"{c}-source",
148
            conf,
149
            baseurl=[
150
                f"https://odcs.fedoraproject.org/composes/production/latest-Fedora-ELN/compose/{c}/source/tree/"
151
            ],
152
        )
UNCOV
153
    repos = base.repos.get_matching("*")
×
UNCOV
154
    repos.disable()
×
UNCOV
155
    repos = base.repos.get_matching("*-source*")
×
UNCOV
156
    repos.enable()
×
157

UNCOV
158
    base.fill_sack()
×
UNCOV
159
    q = base.sack.query(flags=hawkey.IGNORE_MODULAR_EXCLUDES)
×
UNCOV
160
    q = q.available()
×
UNCOV
161
    q = q.filter(requires=["clang", "gcc", "gcc-c++"])
×
UNCOV
162
    pkgs = [p.name for p in list(q)]
×
UNCOV
163
    return filter_llvm_pkgs(set(pkgs)) - exclusions
×
164

165

166
def get_monthly_rebuild_packages(pkgs: set[str], copr_pkgs: list[CoprPkg]) -> set[str]:
1✔
167
    """Returns the list of packages that should be built in the next rebuild.
168
        It will select all the packages that built successfully during the last
169
        rebuild.
170

171
    Args:
172
        pkgs (set[str]): A list of every package that should be considered for
173
                        the rebuild.
174
        copr_pkgs (list[dist]): A list containing the latest build results from
175
                                the COPR project.
176

177
    Returns:
178
        set[str]: List of packages that should be rebuilt.
179

180
    Example:
181

182
    >>> a = {"name" : "a", "builds" : { "latest" : { "id" : 1 } , "latest_succeeded" : { "id" : 1 } } }
183
    >>> b = {"name" : "b", "builds" : { "latest" : { "id" : 1 } , "latest_succeeded" : None } }
184
    >>> c = {"name" : "c", "builds" : { "latest" : { "id" : 2 } , "latest_succeeded" : { "id" : 1 } } }
185
    >>> d = {"name" : "d", "builds" : { "latest" : { "id" : 2 } , "latest_succeeded" : { "id" : 2 } } }
186
    >>> pkgs = { "b", "c", "d"}
187
    >>> copr_pkgs = [CoprPkg(p) for p in [a, b, c, d]]
188
    >>> rebuild_pkgs = get_monthly_rebuild_packages(pkgs, copr_pkgs)
189
    >>> print(rebuild_pkgs)
190
    {'d'}
191
    """
192

193
    for p in copr_pkgs:
1✔
194
        if p.name not in pkgs:
1✔
195
            continue
1✔
196
        # Always build tier0 packges.
197
        if is_tier0_package(p.name):
1✔
UNCOV
198
            continue
×
199
        if not p.latest_succeeded:
1✔
200
            pkgs.discard(p.name)
1✔
201
            continue
1✔
202
        if p.latest.id != p.latest_succeeded.id:
1✔
203
            pkgs.discard(p.name)
1✔
204
    return pkgs
1✔
205

206

207
def get_monthly_rebuild_regressions(
1✔
208
    project_owner: str,
209
    project_name: str,
210
    start_time: datetime.datetime,
211
    copr_pkgs: list[CoprPkg],
212
) -> set[str]:
213
    """Returns the list of packages that failed to build in the most recent
214
       rebuild, but built successfully in the previous rebuild.
215

216
    Args:
217
        start_time (datetime.datetime): The start time of the most recent mass
218
                                        rebuild.  This needs to be a time
219
                                        before the most recent mass rebuild
220
                                        and after the previous one.
221
        copr_pkgs (list[dict]): List of built packages for the COPR project.
222

223
    Returns:
224
        set[str]: List of packages that regressed in the most recent rebuilt.
225

226
    Example:
227

228
    >>> a = {"name" : "a", "builds" : { "latest" : { "id" : 1, "state" : "running", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : None } }
229
    >>> b = {"name" : "b", "builds" : { "latest" : { "id" : 1, "state" : "succeeded", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : None } }
230
    >>> c = {"name" : "c", "builds" : { "latest" : { "id" : 1, "state" : "succeeded", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
231
    >>> d = {"name" : "d", "builds" : { "latest" : { "id" : 2, "state" : "canceled", "submitted_on" : 1731457321, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
232
    >>> e = {"name" : "e", "builds" : { "latest" : { "id" : 2, "state" : "failed", "submitted_on" : 1, "chroots" : [] } , "latest_succeeded" : { "id" : 1 } } }
233
    >>> f = {"name" : "f", "builds" : { "latest" : { "id" : 2, "state" : "failed", "submitted_on" : 1731457321, "chroots" : ["x86_64", "ppc64le", "s390x", "aarch64"] } , "latest_succeeded" : { "id" : 1 } } }
234
    >>> copr_pkgs= [CoprPkg(p) for p in [ a, b, c, d, e, f ]]
235
    >>> project_owner = "@fedora-llvm-team"
236
    >>> project_name = "fedora41-clang-20"
237
    >>> regressions = get_monthly_rebuild_regressions(project_owner, project_name, datetime.datetime.fromisoformat("2024-11-11"), copr_pkgs)
238
    >>> print(regressions)
239
    [{'name': 'f', 'fail_id': 2, 'url': 'https://copr.fedorainfracloud.org/coprs/g/fedora-llvm-team/fedora41-clang-20/build/2/', 'chroots': ['x86_64', 'ppc64le', 's390x', 'aarch64']}]
240

241
    """
242
    pkgs = []
1✔
243
    for p in copr_pkgs:
1✔
244
        if not p.latest:
1✔
UNCOV
245
            continue
×
246

247
        # Don't report regressions if there are still builds in progress
248
        if p.latest.is_in_progress():
1✔
249
            continue
1✔
250

251
        if not p.latest_succeeded:
1✔
252
            if is_tier0_package(p.name):
1✔
253
                pkgs.append(p.get_regression_info(project_owner, project_name))
×
254
            continue
1✔
255
        if p.latest.id == p.latest_succeeded.id:
1✔
256
            continue
1✔
257
        # latest is a successful build, but this doesn't mean it failed.
258
        # It could be in progress.
259
        if p.latest.state != "failed":
1✔
260
            continue
1✔
261
        if int(p.latest.submitted_on) < start_time.timestamp():
1✔
262
            continue
1✔
263
        pkgs.append(p.get_regression_info(project_owner, project_name))
1✔
264
    return pkgs
1✔
265

266

267
def get_chroot_results(pkgs: list[dict], copr_client: copr.v3.Client) -> None:
1✔
UNCOV
268
    for p in pkgs:
×
UNCOV
269
        p["failed_chroots"] = []
×
UNCOV
270
        for c in p["chroots"]:
×
UNCOV
271
            result = copr_client.build_chroot_proxy.get(p["fail_id"], c)
×
UNCOV
272
            if result["state"] == "failed":
×
UNCOV
273
                p["failed_chroots"].append(c)
×
274

275

276
def start_rebuild(
1✔
277
    project_owner: str,
278
    project_name: str,
279
    copr_client: copr.v3.Client,
280
    pkgs: set[str],
281
    snapshot_project_name: str,
282
):
283

UNCOV
284
    print("START", pkgs, "END")
×
285
    # Update the rebuild project to use the latest snapshot
UNCOV
286
    copr_client.project_proxy.edit(
×
287
        project_owner,
288
        project_name,
289
        additional_repos=[
290
            "copr://tstellar/fedora-clang-default-cc",
291
            f"copr://@fedora-llvm-team/{snapshot_project_name}",
292
        ],
293
    )
294

295
    buildopts = {
×
296
        "background": True,
297
    }
298
    logging.info("Rebuilding", len(pkgs), "packages")
×
299
    for p in pkgs:
×
300
        logging.info("Rebuild", p)
×
UNCOV
301
        copr_client.build_proxy.create_from_distgit(
×
302
            project_owner, project_name, p, "f41", buildopts=buildopts
303
        )
304

305

306
def select_snapshot_project(
1✔
307
    copr_client: copr.v3.Client, target_chroots: list[str], max_lookback_days: int = 14
308
) -> str | None:
309
    project_owner = "@fedora-llvm-team"
×
310
    for i in range(max_lookback_days):
×
311
        chroots = set()
×
312
        day = datetime.date.today() - datetime.timedelta(days=i)
×
313
        project_name = day.strftime("llvm-snapshots-big-merge-%Y%m%d")
×
UNCOV
314
        logging.info("Trying:", project_name)
×
UNCOV
315
        try:
×
UNCOV
316
            p = copr_client.project_proxy.get(project_owner, project_name)
×
UNCOV
317
            if not p:
×
UNCOV
318
                continue
×
UNCOV
319
            pkgs = copr_client.build_proxy.get_list(
×
320
                project_owner, project_name, "llvm", status="succeeded"
321
            )
322
            for pkg in pkgs:
×
323
                chroots.update(pkg["chroots"])
×
324

UNCOV
325
            logging.info(project_name, chroots)
×
UNCOV
326
            if all(t in chroots for t in target_chroots):
×
UNCOV
327
                logging.info("PASS", project_name)
×
UNCOV
328
                return project_name
×
UNCOV
329
        except:
×
UNCOV
330
            continue
×
UNCOV
331
    logging.warn("FAIL")
×
UNCOV
332
    return None
×
333

334

335
def create_new_project(
1✔
336
    project_owner: str,
337
    project_name: str,
338
    copr_client: copr.v3.Client,
339
    target_chroots: list[str],
340
):
UNCOV
341
    copr_client.project_proxy.add(project_owner, project_name, chroots=target_chroots)
×
UNCOV
342
    for c in target_chroots:
×
UNCOV
343
        copr_client.project_chroot_proxy.edit(
×
344
            project_owner,
345
            project_name,
346
            c,
347
            additional_packages=["fedora-clang-default-cc"],
348
            with_opts=["toolchain_clang", "clang_lto"],
349
        )
350

351

352
def main():
1✔
353

354
    logging.basicConfig(filename="rebuilder.log", level=logging.INFO)
×
355
    parser = argparse.ArgumentParser()
×
356
    parser.add_argument(
×
357
        "command",
358
        type=str,
359
        choices=[
360
            "rebuild",
361
            "get-regressions",
362
            "get-snapshot-date",
363
            "rebuild-in-progress",
364
        ],
365
    )
366
    parser.add_argument(
×
367
        "--start-date", type=str, help="Any ISO date format is accepted"
368
    )
369

370
    args = parser.parse_args()
×
371
    copr_client = copr.v3.Client.create_from_config_file()
×
372

373
    os_name = "fedora-41"
×
374
    clang_version = "20"
×
375
    target_arches = ["aarch64", "ppc64le", "s390x", "x86_64"]
×
376
    target_chroots = [f"{os_name}-{a}" for a in target_arches]
×
377
    project_owner = "@fedora-llvm-team"
×
UNCOV
378
    project_name = f"{os_name}-clang-{clang_version}"
×
379

380
    if args.command == "rebuild":
×
UNCOV
381
        exclusions = get_exclusions()
×
UNCOV
382
        pkgs = get_pkgs(exclusions)
×
383
        print(pkgs)
×
UNCOV
384
        try:
×
385
            copr_client.project_proxy.get(project_owner, project_name)
×
386
            copr_pkgs = CoprPkg.get_packages_from_copr(
×
387
                project_owner, project_name, copr_client
388
            )
389
            pkgs = get_monthly_rebuild_packages(pkgs, copr_pkgs)
×
390
        except:
×
391
            create_new_project(project_owner, project_name, copr_client, target_chroots)
×
392
        snapshot_project = select_snapshot_project(copr_client, target_chroots)
×
393
        start_rebuild(project_owner, project_name, copr_client, pkgs, snapshot_project)
×
UNCOV
394
    elif args.command == "get-regressions":
×
UNCOV
395
        start_time = datetime.datetime.fromisoformat(args.start_date)
×
396
        copr_pkgs = CoprPkg.get_packages_from_copr(
×
397
            project_owner, project_name, copr_client
398
        )
399
        pkg_failures = get_monthly_rebuild_regressions(
×
400
            project_owner, project_name, start_time, copr_pkgs
401
        )
UNCOV
402
        get_chroot_results(pkg_failures, copr_client)
×
403
        # Delete attributes we don't need to print
404
        for p in pkg_failures:
×
405
            for k in ["fail_id", "chroots"]:
×
406
                del p[k]
×
407

UNCOV
408
        print(json.dumps(pkg_failures))
×
UNCOV
409
    elif args.command == "get-snapshot-date":
×
UNCOV
410
        project = copr_client.project_proxy.get(project_owner, project_name)
×
411
        for repo in project["additional_repos"]:
×
UNCOV
412
            match = re.match(
×
413
                r"copr://@fedora-llvm-team/llvm-snapshots-big-merge-([0-9]+)$", repo
414
            )
UNCOV
415
            if match:
×
UNCOV
416
                print(datetime.datetime.fromisoformat(match.group(1)).isoformat())
×
UNCOV
417
                return
×
UNCOV
418
    elif args.command == "rebuild-in-progress":
×
UNCOV
419
        for pkg in copr_client.monitor_proxy.monitor(project_owner, project_name)[
×
420
            "packages"
421
        ]:
UNCOV
422
            for c in pkg["chroots"]:
×
UNCOV
423
                build = CoprBuild(pkg["chroots"][c])
×
UNCOV
424
                if build.is_in_progress():
×
UNCOV
425
                    sys.exit(0)
×
UNCOV
426
        sys.exit(1)
×
427

428

429
if __name__ == "__main__":
1✔
UNCOV
430
    main()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc