Skip to content

Commit

Permalink
PTFE-1981 handle cancelled build on same sha
Browse files Browse the repository at this point in the history
  • Loading branch information
tcarmet committed Oct 14, 2024
1 parent 7527ea0 commit dfa9d7f
Show file tree
Hide file tree
Showing 2 changed files with 215 additions and 100 deletions.
184 changes: 84 additions & 100 deletions bert_e/git_host/github/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,13 @@ def get_commit_status(self, ref):
combined = AggregatedStatus.get(self.client,
owner=self.owner,
repo=self.slug, ref=ref)
actions = AggregatedCheckSuites.get(client=self.client,
owner=self.owner,
repo=self.slug, ref=ref)
actions = AggregatedWorkflowRuns.get(

Check warning on line 418 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L418

Added line #L418 was not covered by tests
client=self.client,
owner=self.owner,
repo=self.slug,
params={
'head_sha': ref
})
combined.status[actions.key] = actions

except HTTPError as err:
Expand Down Expand Up @@ -581,71 +585,59 @@ class AggregatedWorkflowRuns(base.AbstractGitHostObject):
GET_URL = "/repos/{owner}/{repo}/actions/runs"
SCHEMA = schema.AggregateWorkflowRuns

@property
def total_count(self):
return self.data['total_count']

@property
def workflow_runs(self):
return self.data['workflow_runs']

@property
def check_suite_ids(self):
return [wf['check_suite_id'] for wf in self.workflow_runs]


class AggregatedCheckSuites(base.AbstractGitHostObject,
base.AbstractBuildStatus):
"""
The Endpoint to have access infos about github actions runs
"""
GET_URL = '/repos/{owner}/{repo}/commits/{ref}/check-suites'
SCHEMA = schema.AggregateCheckSuites

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._check_suites = [elem for elem in self.data['check_suites']]
self._workflow_runs = [elem for elem in self.data['workflow_runs']]

Check warning on line 590 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L590

Added line #L590 was not covered by tests

@property
def url(self):
if len(self._workflow_runs) == 0:
return None

Check warning on line 595 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L594-L595

Added lines #L594 - L595 were not covered by tests
return f"https://github.com/{self.full_repo}/commit/{self.commit}"

def is_pending(self, check_suites=None):
if check_suites is None:
check_suites = self._check_suites
@property
def commit(self) -> str | None:
if len(self._workflow_runs) == 0:
return None
return self._workflow_runs[0]['head_sha']

Check warning on line 602 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L600-L602

Added lines #L600 - L602 were not covered by tests

@property
def full_repo(self) -> str | None:
if len(self._workflow_runs) == 0:
return None
return self._workflow_runs[0]['repository']['full_name']

Check warning on line 608 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L606-L608

Added lines #L606 - L608 were not covered by tests

def is_pending(self, workflow_runs=None):
if workflow_runs is None:
workflow_runs = self._workflow_runs

Check warning on line 612 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L611-L612

Added lines #L611 - L612 were not covered by tests
return len([
elem for elem in check_suites if elem['status'] == 'pending'
elem for elem in workflow_runs if elem['status'] == 'pending'
]) > 0

def is_queued(self, check_suites=None):
if check_suites is None:
check_suites = self._check_suites
def is_queued(self, workflow_runs=None):
if workflow_runs is None:
workflow_runs = self._workflow_runs

Check warning on line 619 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L618-L619

Added lines #L618 - L619 were not covered by tests
return len([
elem for elem in check_suites if elem['status'] == 'queued'
elem for elem in workflow_runs if elem['status'] == 'queued'
]) > 0

def _get_aggregate_workflow_dispatched(self, page, prev_dispatches=list()):
"""Return a list of check-suite IDs for workflow_dispatch runs"""

response = AggregatedWorkflowRuns.get(
client=self.client,
owner=self.owner, repo=self.repo, ref=self.commit,
params={
'branch': self.branch,
'page': page,
'event': 'workflow_dispatch',
'per_page': 100
})
@property
def owner(self) -> str | None:
if self._workflow_runs.__len__() > 0:
return self._workflow_runs[0]['repository']['owner']['login']
return None

Check warning on line 628 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L626-L628

Added lines #L626 - L628 were not covered by tests

prev_dispatches.extend(response.check_suite_ids)
if len(prev_dispatches) < response.total_count:
page += 1
return self._get_aggregate_workflow_dispatched(
page,
prev_dispatches
)
@property
def repo(self) -> str | None:
if self._workflow_runs.__len__() > 0:
return self._workflow_runs[0]['repository']['name']
return None

Check warning on line 634 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L632-L634

Added lines #L632 - L634 were not covered by tests

return prev_dispatches
@property
def branch(self) -> str | None:
if self._workflow_runs.__len__() > 0:
return self._workflow_runs[0]['head_branch']
return None

Check warning on line 640 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L638-L640

Added lines #L638 - L640 were not covered by tests

def remove_unwanted_workflows(self):
"""
Expand All @@ -654,42 +646,54 @@ def remove_unwanted_workflows(self):
- check-suites workflow triggerd by a `workflow_dispatch` event
- Same workflow with different result
"""
if self._check_suites.__len__() == 0:
if self._workflow_runs.__len__() == 0:

Check warning on line 649 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L649

Added line #L649 was not covered by tests
return

page = 1
workflow_dispatches = self._get_aggregate_workflow_dispatched(page)

self._check_suites = list(filter(
lambda elem: elem['id'] not in workflow_dispatches,
self._check_suites
self._workflow_runs = list(filter(

Check warning on line 652 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L652

Added line #L652 was not covered by tests
lambda elem: elem['event'] != 'workflow_dispatch',
self._workflow_runs
))

self._check_suites = list(filter(
self._workflow_runs = list(filter(

Check warning on line 657 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L657

Added line #L657 was not covered by tests
lambda elem: elem['app']['slug'] == 'github-actions',
self._check_suites
self._workflow_runs
))

def branch_state(self, branch_check_suite):
# When two of the same workflow ran on the same branch,
# we only keep the best one.
conclusion_ranking = {

Check warning on line 664 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L664

Added line #L664 was not covered by tests
'success': 4, None: 3, 'failure': 2, 'cancelled': 1
}
best_runs = {}
for run in self._workflow_runs:
workflow_id = run['workflow_id']
conclusion = run['conclusion']
if (workflow_id not in best_runs or

Check warning on line 671 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L667-L671

Added lines #L667 - L671 were not covered by tests
conclusion_ranking[conclusion] >
conclusion_ranking[best_runs[workflow_id]['conclusion']]):
best_runs[workflow_id] = run
self._workflow_runs = list(best_runs.values())

Check warning on line 675 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L674-L675

Added lines #L674 - L675 were not covered by tests

def branch_state(self, branch_workflow_runs):
all_complete = all(
elem['conclusion'] is not None for elem in branch_check_suite
elem['conclusion'] is not None for elem in branch_workflow_runs
)

all_success = all(
elem['conclusion'] == 'success'
for elem in branch_check_suite
for elem in branch_workflow_runs
)
LOG.info(f'State on {self.branch}: '
f'complete: {all_complete} '
f'success: {all_success} '
f'pending: {self.is_pending(branch_check_suite)} '
f'queued: {self.is_queued(branch_check_suite)}')
LOG.info(f'branch check suites {branch_check_suite}')
f'pending: {self.is_pending(branch_workflow_runs)} '
f'queued: {self.is_queued(branch_workflow_runs)}')
LOG.info(f'branch check suites {branch_workflow_runs}')

Check warning on line 691 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L691

Added line #L691 was not covered by tests

if branch_check_suite.__len__() == 0:
if branch_workflow_runs.__len__() == 0:

Check warning on line 693 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L693

Added line #L693 was not covered by tests
return 'NOTSTARTED'
elif (self.is_pending(branch_check_suite) or
self.is_queued(branch_check_suite) or not all_complete):
elif (self.is_pending(branch_workflow_runs) or

Check warning on line 695 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L695

Added line #L695 was not covered by tests
self.is_queued(branch_workflow_runs) or not all_complete):
return 'INPROGRESS'
elif all_complete and all_success:
return 'SUCCESSFUL'
Expand All @@ -700,7 +704,7 @@ def branch_state(self, branch_check_suite):
def state(self):
self.remove_unwanted_workflows()
res = [list(v) for i, v in groupby(
self._check_suites,
self._workflow_runs,
lambda elem: elem['head_branch']
)]

Expand All @@ -726,34 +730,12 @@ def key(self) -> str:
return 'github_actions'

@property
def commit(self) -> str or None:
if self._check_suites.__len__() > 0:
return self._check_suites[0]["head_sha"]
return None

@property
def branch(self) -> str or None:
if self._check_suites.__len__() > 0:
return self._check_suites[0]["head_branch"]
return None

@property
def full_repo(self) -> str or None:
if self._check_suites.__len__() > 0:
return self._check_suites[0]['repository']['full_name']
return None

@property
def repo(self) -> str or None:
if self._check_suites.__len__() > 0:
return self._check_suites[0]['repository']['name']
return None
def total_count(self):
return self.data['total_count']

Check warning on line 734 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L734

Added line #L734 was not covered by tests

@property
def owner(self) -> str or None:
if self._check_suites.__len__() > 0:
return self._check_suites[0]['repository']['owner']['login']
return None
def workflow_runs(self):
return self._workflow_runs

Check warning on line 738 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L738

Added line #L738 was not covered by tests

def __str__(self) -> str:
return self.state
Expand Down Expand Up @@ -1143,11 +1125,13 @@ def owner(self) -> str or None:

@property
def status(self):
return AggregatedCheckSuites.get(
return AggregatedWorkflowRuns.get(

Check warning on line 1128 in bert_e/git_host/github/__init__.py

View check run for this annotation

Codecov / codecov/patch

bert_e/git_host/github/__init__.py#L1128

Added line #L1128 was not covered by tests
client=self.client,
owner=self.owner,
repo=self.repo,
ref=self.commit
params={
'head_sha': self.commit,
}
)


Expand Down
131 changes: 131 additions & 0 deletions bert_e/tests/unit/test_github_build_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@

from bert_e.git_host.github import AggregatedWorkflowRuns, Client

from pytest import fixture


@fixture
def client():
return Client(
login='login',
password='password',
email='email@org.com',
base_url="http://localhost:4010",
accept_header="application/json"
)


@fixture
def workflow_run_json():
return {
'workflow_runs': [
{
'id': 1,
'head_sha': 'd6fde92930d4715a2b49857d24b940956b26d2d3',
'head_branch': 'q/1',
'status': 'completed',
'event': 'pull_request',
'workflow_id': 1,
'check_suite_id': 1,
'conclusion': 'success',
'app': {
'slug': 'github-actions'
},
'pull_requests': [
{
'number': 1
}
],
'repository': {
'full_name': 'octo-org/Hello-World',
'owner': {
'login': 'octo-org'
},
'name': 'Hello-World'
}
},
{
'id': 2,
'head_sha': 'd6fde92930d4715a2b49857d24b940956b26d2d3',
'head_branch': 'q/1',
'workflow_id': 1,
'check_suite_id': 2,
'event': 'pull_request',
'status': 'completed',
'conclusion': 'cancelled',
'app': {
'slug': 'github-actions'
},
'pull_requests': [
{
'number': 1
}
],
'repository': {
'full_name': 'octo-org/Hello-World',
'owner': {
'login': 'octo-org'
},
'name': 'Hello-World'
}
}
],
'total_count': 2
}


def test_aggregated_workflow_run(client, workflow_run_json):
workflow_runs = AggregatedWorkflowRuns(client, **workflow_run_json)

full_name = \
workflow_run_json['workflow_runs'][0]['repository']['full_name']
head_sha = workflow_run_json['workflow_runs'][0]['head_sha']
owner = \
workflow_run_json['workflow_runs'][0]['repository']['owner']['login']
repo = workflow_run_json['workflow_runs'][0]['repository']['name']
url = f"https://github.com/{full_name}/commit/{head_sha}"
branch = workflow_run_json['workflow_runs'][0]['head_branch']
assert url == workflow_runs.url
assert head_sha == workflow_runs.commit
assert owner == workflow_runs.owner
assert repo == workflow_runs.repo
assert full_name == workflow_runs.full_repo
assert branch == workflow_runs.branch
assert workflow_runs.is_pending() is False
assert workflow_runs.is_queued() is False

workflow_run_json['workflow_runs'][0]['status'] = 'queued'
workflow_run_json['workflow_runs'][0]['conclusion'] = None
workflow_runs = AggregatedWorkflowRuns(client, **workflow_run_json)
assert workflow_runs.is_pending() is False
assert workflow_runs.is_queued() is True
assert workflow_runs.state == "INPROGRESS"
workflow_run_json['workflow_runs'][0]['status'] = 'pending'
workflow_runs = AggregatedWorkflowRuns(client, **workflow_run_json)
assert workflow_runs.is_pending() is True
assert workflow_runs.is_queued() is False
assert workflow_runs.state == "INPROGRESS"

workflow_run_json['workflow_runs'] = []
workflow_run_json['total_count'] = 0
workflow_runs = AggregatedWorkflowRuns(client, **workflow_run_json)
assert workflow_runs.url is None
assert workflow_runs.commit is None
assert workflow_runs.owner is None
assert workflow_runs.repo is None
assert workflow_runs.full_repo is None
assert workflow_runs.branch is None


def test_cancelled_build_same_sha(client, monkeypatch, workflow_run_json):

workflow_runs = AggregatedWorkflowRuns(client, **workflow_run_json)
monkeypatch.setattr(AggregatedWorkflowRuns, 'get',
lambda *args, **kwargs: workflow_runs)
get_workflow_run = AggregatedWorkflowRuns.get(
client=client,
owner=workflow_run_json['workflow_runs'][0]['repository']['owner']['login'], # noqa
repo=workflow_run_json['workflow_runs'][0]['repository']['name'],
ref=workflow_run_json['workflow_runs'][0]['head_sha']
)
assert get_workflow_run.state == 'SUCCESSFUL'

0 comments on commit dfa9d7f

Please sign in to comment.