Skip to content

Commit 6b2af4d

Browse files
Merge pull request #666 from Tarun-Arora/build
[IMPROVEMENT] Prevent Running Tests on Platform Before Build Succeed on Both OS
2 parents 28ec53a + e6042e2 commit 6b2af4d

File tree

3 files changed

+443
-29
lines changed

3 files changed

+443
-29
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
python -m pip install --upgrade pip
3232
if [ -f test-requirements.txt ]; then pip install -r test-requirements.txt; fi
3333
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
34-
- name: Apply dogdy
34+
- name: Apply dodgy
3535
run: |
3636
dodgy
3737
- name: Apply isort

mod_ci/controllers.py

Lines changed: 194 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from sqlalchemy.sql.functions import count
2424
from werkzeug.utils import secure_filename
2525

26+
from database import DeclEnum
2627
from decorators import get_menu_entries, template_renderer
2728
from mailer import Mailer
2829
from mod_auth.controllers import check_access_rights, login_required
@@ -55,6 +56,13 @@ class Status:
5556
FAILURE = "failure"
5657

5758

59+
class Workflow_builds(DeclEnum):
60+
"""Define GitHub Action workflow build names."""
61+
62+
LINUX = "Build CCExtractor on Linux"
63+
WINDOWS = "Build CCExtractor on Windows"
64+
65+
5866
@mod_ci.before_app_request
5967
def before_app_request() -> None:
6068
"""Organize menu content such as Platform management before request."""
@@ -431,9 +439,9 @@ def save_xml_to_file(xml_node, folder_name, file_name) -> None:
431439
)
432440

433441

434-
def queue_test(db, gh_commit, commit, test_type, branch="master", pr_nr=0) -> None:
442+
def add_test_entry(db, gh_commit, commit, test_type, branch="master", pr_nr=0) -> None:
435443
"""
436-
Store test details into Test model for each platform, and post the status to GitHub.
444+
Add test details entry into Test model for each platform.
437445
438446
:param db: Database connection.
439447
:type db: sqlalchemy.orm.scoped_session
@@ -464,6 +472,117 @@ def queue_test(db, gh_commit, commit, test_type, branch="master", pr_nr=0) -> No
464472
windows_test = Test(TestPlatform.windows, test_type, fork.id, branch, commit, pr_nr)
465473
db.add(windows_test)
466474
db.commit()
475+
476+
477+
def schedule_test(gh_commit, commit, test_type, branch="master", pr_nr=0) -> None:
478+
"""
479+
Post status to GitHub as waiting for Github Actions completion.
480+
481+
:param gh_commit: The GitHub API call for the commit. Can be None
482+
:type gh_commit: Any
483+
:param commit: The commit hash.
484+
:type commit: str
485+
:param test_type: The type of test
486+
:type test_type: TestType
487+
:param branch: Branch name
488+
:type branch: str
489+
:param pr_nr: Pull Request number, if applicable.
490+
:type pr_nr: int
491+
:return: Nothing
492+
:rtype: None
493+
"""
494+
from run import log
495+
496+
if test_type == TestType.pull_request:
497+
log.debug('pull request test type detected')
498+
branch = "pull_request"
499+
500+
if gh_commit is not None:
501+
for platform in TestPlatform:
502+
try:
503+
gh_commit.post(
504+
state=Status.PENDING,
505+
description="Waiting for actions to complete",
506+
context=f"CI - {platform.value}",
507+
)
508+
except ApiError as a:
509+
log.critical(f'Could not post to GitHub! Response: {a.response}')
510+
511+
512+
def deschedule_test(gh_commit, commit, test_type, message="Tests have been cancelled", branch="master", pr_nr=0,
513+
state=Status.FAILURE) -> None:
514+
"""
515+
Post status to GitHub (default: as failure due to Github Actions incompletion).
516+
517+
:param gh_commit: The GitHub API call for the commit. Can be None
518+
:type gh_commit: Any
519+
:param commit: The commit hash.
520+
:type commit: str
521+
:param test_type: The type of test
522+
:type test_type: TestType
523+
:param branch: Branch name
524+
:type branch: str
525+
:param pr_nr: Pull Request number, if applicable.
526+
:type pr_nr: int
527+
:return: Nothing
528+
:rtype: None
529+
"""
530+
from run import log
531+
532+
if gh_commit is not None:
533+
for platform in TestPlatform:
534+
try:
535+
gh_commit.post(
536+
state=state,
537+
description=message,
538+
context=f"CI - {platform.value}",
539+
)
540+
except ApiError as a:
541+
log.critical(f'Could not post to GitHub! Response: {a.response}')
542+
543+
544+
def queue_test(db, gh_commit, commit, test_type, branch="master", pr_nr=0) -> None:
545+
"""
546+
Store test details into Test model for each platform, and post the status to GitHub.
547+
548+
:param db: Database connection.
549+
:type db: sqlalchemy.orm.scoped_session
550+
:param gh_commit: The GitHub API call for the commit. Can be None
551+
:type gh_commit: Any
552+
:param commit: The commit hash.
553+
:type commit: str
554+
:param test_type: The type of test
555+
:type test_type: TestType
556+
:param branch: Branch name
557+
:type branch: str
558+
:param pr_nr: Pull Request number, if applicable.
559+
:type pr_nr: int
560+
:return: Nothing
561+
:rtype: None
562+
"""
563+
from run import log
564+
565+
fork_url = f"%/{g.github['repository_owner']}/{g.github['repository']}.git"
566+
fork = Fork.query.filter(Fork.github.like(fork_url)).first()
567+
568+
if test_type == TestType.pull_request:
569+
log.debug('pull request test type detected')
570+
branch = "pull_request"
571+
572+
linux_test = Test.query.filter(and_(Test.platform == TestPlatform.linux,
573+
Test.commit == commit,
574+
Test.fork_id == fork.id,
575+
Test.test_type == test_type,
576+
Test.branch == branch,
577+
Test.pr_nr == pr_nr
578+
)).first()
579+
windows_test = Test.query.filter(and_(Test.platform == TestPlatform.windows,
580+
Test.commit == commit,
581+
Test.fork_id == fork.id,
582+
Test.test_type == test_type,
583+
Test.branch == branch,
584+
Test.pr_nr == pr_nr
585+
)).first()
467586
add_customized_regression_tests(linux_test.id)
468587
add_customized_regression_tests(windows_test.id)
469588

@@ -595,7 +714,7 @@ def start_ci():
595714

596715
last_commit.value = ref['object']['sha']
597716
g.db.commit()
598-
queue_test(g.db, github_status, commit_hash, TestType.commit)
717+
add_test_entry(g.db, github_status, commit_hash, TestType.commit)
599718
else:
600719
g.log.warning('Unknown push type! Dumping payload for analysis')
601720
g.log.warning(payload)
@@ -617,14 +736,8 @@ def start_ci():
617736
user_id = payload['pull_request']['user']['id']
618737
if BlockedUsers.query.filter(BlockedUsers.user_id == user_id).first() is not None:
619738
g.log.warning("User Blacklisted")
620-
github_status.post(
621-
state=Status.ERROR,
622-
description="CI start aborted. You may be blocked from accessing this functionality",
623-
target_url=url_for('home.index', _external=True)
624-
)
625739
return 'ERROR'
626-
627-
queue_test(g.db, github_status, commit_hash, TestType.pull_request, pr_nr=pr_nr)
740+
add_test_entry(g.db, github_status, commit_hash, TestType.pull_request, pr_nr=pr_nr)
628741

629742
elif payload['action'] == 'closed':
630743
g.log.debug('PR was closed, no after hash available')
@@ -636,12 +749,15 @@ def start_ci():
636749
continue
637750
progress = TestProgress(test.id, TestStatus.canceled, "PR closed", datetime.datetime.now())
638751
g.db.add(progress)
639-
repository.statuses(test.commit).post(
640-
state=Status.FAILURE,
641-
description="Tests canceled",
642-
context=f"CI - {test.platform.value}",
643-
target_url=url_for('test.by_id', test_id=test.id, _external=True)
644-
)
752+
# If test run status exists, mark them as cancelled
753+
for status in repository.commits(test.commit).status.get()["statuses"]:
754+
if status["context"] == f"CI - {test.platform.value}":
755+
repository.statuses(test.commit).post(
756+
state=Status.FAILURE,
757+
description="Tests cancelled",
758+
context=f"CI - {test.platform.value}",
759+
target_url=url_for('test.by_id', test_id=test.id, _external=True)
760+
)
645761

646762
elif event == "issues":
647763
g.log.debug('issues event detected')
@@ -707,6 +823,68 @@ def start_ci():
707823
else:
708824
g.log.warning(f"Unsupported release action: {action}")
709825

826+
elif event == "workflow_run":
827+
workflow_name = payload['workflow_run']['name']
828+
if workflow_name in [Workflow_builds.LINUX, Workflow_builds.WINDOWS]:
829+
g.log.debug('workflow_run event detected')
830+
commit_hash = payload['workflow_run']['head_sha']
831+
github_status = repository.statuses(commit_hash)
832+
833+
if payload['action'] == "completed":
834+
is_complete = True
835+
has_failed = False
836+
builds = {"linux": False, "windows": False}
837+
for workflow in repository.actions.runs.get(
838+
event=payload['workflow_run']['event'],
839+
actor=payload['sender']['login'],
840+
branch=payload['workflow_run']['head_branch']
841+
)['workflow_runs']:
842+
if workflow['head_sha'] == commit_hash:
843+
if workflow['status'] == "completed":
844+
if workflow['conclusion'] != "success":
845+
has_failed = True
846+
break
847+
if workflow['name'] == "Build CCExtractor on Linux":
848+
builds["linux"] = True
849+
elif workflow['name'] == "Build CCExtractor on Windows":
850+
builds["windows"] = True
851+
elif workflow['status'] != "completed":
852+
is_complete = False
853+
break
854+
855+
if has_failed:
856+
# no runs to be scheduled since build failed
857+
deschedule_test(github_status, commit_hash, TestType.commit,
858+
message="Cancelling tests as Github Action(s) failed")
859+
elif is_complete:
860+
if payload['workflow_run']['event'] == "pull_request":
861+
# In case of pull request run tests only if it is still in an open state
862+
# and user is not blacklisted
863+
for pull_request in repository.pulls.get(state="open"):
864+
if pull_request['head']['sha'] == commit_hash and any(builds.values()):
865+
user_id = pull_request['user']['id']
866+
if BlockedUsers.query.filter(BlockedUsers.user_id == user_id).first() is not None:
867+
g.log.warning("User Blacklisted")
868+
github_status.post(
869+
state=Status.ERROR,
870+
description="CI start aborted. \
871+
You may be blocked from accessing this functionality",
872+
target_url=url_for('home.index', _external=True)
873+
)
874+
return 'ERROR'
875+
queue_test(g.db, github_status, commit_hash,
876+
TestType.pull_request, pr_nr=pull_request['number'])
877+
elif any(builds.values()):
878+
queue_test(g.db, github_status, commit_hash, TestType.commit)
879+
else:
880+
deschedule_test(github_status, commit_hash, TestType.commit,
881+
message="Not ran - no code changes", state=Status.SUCCESS)
882+
elif payload['action'] == 'requested':
883+
schedule_test(github_status, commit_hash, TestType.commit)
884+
else:
885+
g.log.warning('Unknown action type in workflow_run! Dumping payload for analysis')
886+
g.log.warning(payload)
887+
710888
else:
711889
g.log.warning(f'CI unrecognized event: {event}')
712890

0 commit comments

Comments
 (0)