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 056cb63 commit 0ffbecb
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(
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']]

@property
def url(self):
if len(self._workflow_runs) == 0:
return None
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']

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

def is_pending(self, workflow_runs=None):
if workflow_runs is None:
workflow_runs = self._workflow_runs
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
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

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

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

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:
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(
lambda elem: elem['event'] != 'workflow_dispatch',
self._workflow_runs
))

self._check_suites = list(filter(
self._workflow_runs = list(filter(
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 = {
'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
conclusion_ranking[conclusion] >
conclusion_ranking[best_runs[workflow_id]['conclusion']]):
best_runs[workflow_id] = run
self._workflow_runs = list(best_runs.values())

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}')

if branch_check_suite.__len__() == 0:
if branch_workflow_runs.__len__() == 0:
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
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']

@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

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(
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 == None
assert workflow_runs.commit == None
assert workflow_runs.owner == None
assert workflow_runs.repo == None
assert workflow_runs.full_repo == None
assert workflow_runs.branch == 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 0ffbecb

Please sign in to comment.