Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PTFE-1981 handle cancelled build on same sha #207

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@
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 @@
- 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 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 @@
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 @@

@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 protected]',
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'
Loading