Skip to content

Commit b3c7fe4

Browse files
authored
Improve background garbage prs and branches collection in unit tests when concurrent tests are run (#23)
## Summary When multiple CI runs decide to do a garbage collection, we should not fail if they try to delete an already deleted branch for instance. ## How was this tested? CI ## PRs in the Stack - ➡ #23 (The stack is managed by [git-grok](https://github.com/dimikot/git-grok).)
1 parent 670d449 commit b3c7fe4

File tree

1 file changed

+66
-23
lines changed

1 file changed

+66
-23
lines changed

tests/helpers.py

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
import shlex
44
import sys
55
import textwrap
6+
from dataclasses import dataclass
67
from datetime import datetime, timedelta, timezone
78
from importlib.machinery import SourceFileLoader
89
from importlib.util import spec_from_loader, module_from_spec
10+
from itertools import groupby
911
from os import chdir, getcwd, mkdir, environ
1012
from os.path import basename, dirname, realpath
1113
from platform import python_version
1214
from shutil import rmtree
1315
from subprocess import Popen, check_output, CalledProcessError, STDOUT, PIPE
14-
from typing import Literal
16+
from typing import Any, Literal, cast
1517
from unittest import TestCase
1618

1719
GIT_GROK_PATH = dirname(dirname(realpath(__file__))) + "/git-grok"
@@ -20,6 +22,19 @@
2022
MAX_BRANCH_AGE = timedelta(minutes=10)
2123

2224

25+
@dataclass
26+
class GarbageItem:
27+
kind: Literal["sync", "async"]
28+
name: str
29+
ts: str
30+
31+
32+
@dataclass
33+
class GarbageTask:
34+
kind: Literal["sync", "async"]
35+
task: Any
36+
37+
2338
def import_path(path: str):
2439
module_name = basename(path).replace("-", "_")
2540
spec = spec_from_loader(module_name, SourceFileLoader(module_name, path))
@@ -91,8 +106,13 @@ def git_init_and_cd_to_test_dir(
91106
)
92107

93108
check_output_x("git", "fetch")
94-
old_branches = [
95-
{"name": name.replace(f"{TEST_REMOTE}/", ""), "date": date}
109+
110+
branches = [
111+
GarbageItem(
112+
kind=kind,
113+
name=name.replace(f"{TEST_REMOTE}/", ""),
114+
ts=date,
115+
)
96116
for b in check_output_x(
97117
"git",
98118
"for-each-ref",
@@ -101,15 +121,23 @@ def git_init_and_cd_to_test_dir(
101121
).splitlines()
102122
for name, date in [b.split()]
103123
if (
104-
name != f"{TEST_REMOTE}/main"
105-
and datetime.now(timezone.utc)
106-
- datetime.fromisoformat(date.replace("Z", "+00:00"))
107-
> MAX_BRANCH_AGE
124+
kind := (
125+
("grok/" in name and initial_branch in name and "sync")
126+
or (
127+
name != f"{TEST_REMOTE}/main"
128+
and from_now(date) > MAX_BRANCH_AGE
129+
and "async"
130+
)
131+
or None
132+
)
108133
)
109-
or ("grok/" in name and initial_branch in name)
110134
]
111-
old_prs = [
112-
pr
135+
prs = [
136+
GarbageItem(
137+
kind=kind,
138+
name=str(pr["number"]),
139+
ts=str(pr["updatedAt"]),
140+
)
113141
for pr in json.loads(
114142
check_output_x(
115143
"gh",
@@ -122,28 +150,37 @@ def git_init_and_cd_to_test_dir(
122150
)
123151
)
124152
if (
125-
datetime.now(timezone.utc)
126-
- datetime.fromisoformat(pr["updatedAt"].replace("Z", "+00:00"))
127-
> MAX_BRANCH_AGE
153+
kind := (
154+
(initial_branch in pr["headRefName"] and "sync")
155+
or (from_now(pr["updatedAt"]) > MAX_BRANCH_AGE and "async")
156+
or None
157+
)
128158
)
129-
or initial_branch in pr["headRefName"]
130159
]
160+
131161
tasks = [
132-
git_grok.Task(check_output_x, "gh", "pr", "close", str(pr["number"]))
133-
for pr in old_prs
134-
]
135-
if old_branches:
136-
tasks.append(
137-
git_grok.Task(
162+
GarbageTask(
163+
kind=pr.kind,
164+
task=git_grok.Task(check_output_x, "gh", "pr", "close", pr.name),
165+
)
166+
for pr in prs
167+
] + [
168+
GarbageTask(
169+
kind=cast(Any, kind),
170+
task=git_grok.Task(
138171
check_output_x,
139172
"git",
140173
"push",
141174
TEST_REMOTE,
142175
"--delete",
143-
*[branch["name"] for branch in old_branches],
144-
)
176+
*[branch.name for branch in group],
177+
),
145178
)
146-
[task.wait() for task in tasks if task]
179+
for kind, group in groupby(branches, key=lambda branch: branch.kind)
180+
]
181+
[task.task.wait() for task in tasks if task.kind == "sync"]
182+
# Leave async tasks work in background and swallow errors (e.g. in case a
183+
# branch has already been deleted by a parallel test run or so).
147184

148185

149186
def git_touch(file: str):
@@ -234,4 +271,10 @@ def git_init_and_cd_to_test_dir(
234271
)
235272

236273

274+
def from_now(ts: str) -> timedelta:
275+
return datetime.now(timezone.utc) - datetime.fromisoformat(
276+
ts.replace("Z", "+00:00")
277+
)
278+
279+
237280
git_grok = import_path(GIT_GROK_PATH)

0 commit comments

Comments
 (0)