Skip to content

Commit 5b4c69c

Browse files
authored
[IMPROVEMENT] Fix working of draft PRs (#868)
1 parent cd28d87 commit 5b4c69c

File tree

3 files changed

+89
-90
lines changed

3 files changed

+89
-90
lines changed

mod_ci/controllers.py

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ def gcp_instance(app, db, platform, repository, delay) -> None:
211211
:param app: The Flask app
212212
:type app: Flask
213213
:param db: database connection
214-
:type db: sqlalchemy.orm.scoped_session
214+
:type db: sqlalchemy.orm.scoping.scoped_session
215215
:param platform: operating system
216216
:type platform: str
217217
:param repository: repository to run tests on
@@ -248,11 +248,20 @@ def gcp_instance(app, db, platform, repository, delay) -> None:
248248
compute = get_compute_service_object()
249249

250250
for test in pending_tests:
251-
if test.test_type == TestType.pull_request and test.pr_nr == 0:
252-
log.warn(f'[{platform}] Test {test.id} is invalid, deleting')
253-
db.delete(test)
254-
db.commit()
255-
continue
251+
if test.test_type == TestType.pull_request:
252+
gh_commit = repository.get_commit(test.commit)
253+
if test.pr_nr == 0:
254+
log.warn(f'[{platform}] Test {test.id} is invalid')
255+
deschedule_test(gh_commit, message="Invalid PR number", test=test, db=db)
256+
continue
257+
test_pr = repository.get_pull(test.pr_nr)
258+
if test.commit != test_pr.head.sha:
259+
log.warn(f'[{platform}] Test {test.id} is invalid')
260+
deschedule_test(gh_commit, message="PR closed or updated", test=test, db=db)
261+
continue
262+
if test_pr.state != 'open':
263+
log.debug(f"PR {test.pr_nr} not in open state, skipping test {test.id}")
264+
continue
256265
start_test(compute, app, db, repository, test, github_config['bot_token'])
257266

258267

@@ -281,7 +290,7 @@ def start_test(compute, app, db, repository: Repository.Repository, test, bot_to
281290
:param app: The Flask app
282291
:type app: Flask
283292
:param db: database connection
284-
:type db: sqlalchemy.orm.scoped_session
293+
:type db: sqlalchemy.orm.scoping.scoped_session
285294
:param platform: operating system
286295
:type platform: str
287296
:param repository: repository to run tests on
@@ -616,7 +625,7 @@ def add_test_entry(db, commit, test_type, branch="master", pr_nr=0) -> None:
616625
Add test details entry into Test model for each platform.
617626
618627
:param db: Database connection.
619-
:type db: sqlalchemy.orm.scoped_session
628+
:type db: sqlalchemy.orm.scoping.scoped_session
620629
:param gh_commit: The GitHub API call for the commit. Can be None
621630
:type gh_commit: Any
622631
:param commit: The commit hash.
@@ -690,8 +699,8 @@ def update_status_on_github(gh_commit: Commit.Commit, state, description, contex
690699
log.critical(f'Could not post to GitHub! Response: {a.data}')
691700

692701

693-
def deschedule_test(gh_commit: Commit.Commit, commit, test_type, platform, branch="master",
694-
message="Tests have been cancelled", state=Status.FAILURE) -> None:
702+
def deschedule_test(gh_commit: Commit.Commit, commit=None, test_type=None, platform=None, branch="master",
703+
message="Tests have been cancelled", state=Status.FAILURE, test=None, db=None) -> None:
695704
"""
696705
Post status to GitHub (default: as failure due to GitHub Actions incompletion).
697706
@@ -709,32 +718,37 @@ def deschedule_test(gh_commit: Commit.Commit, commit, test_type, platform, branc
709718
:type message: str
710719
:param state: The status badge of the test
711720
:type state: Status
721+
:param test: The test which is to be canceled (optional)
722+
:type state: Test
723+
:param db: db session
724+
:type db: sqlalchemy.orm.scoping.scoped_session
712725
:return: Nothing
713726
:rtype: None
714727
"""
715728
from run import log
716729

717-
fork_url = f"%/{g.github['repository_owner']}/{g.github['repository']}.git"
718-
fork = Fork.query.filter(Fork.github.like(fork_url)).first()
719-
720730
if test_type == TestType.pull_request:
721731
log.debug('pull request test type detected')
722732
branch = "pull_request"
723733

724-
platform_test = Test.query.filter(and_(Test.platform == platform,
725-
Test.commit == commit,
726-
Test.fork_id == fork.id,
727-
Test.test_type == test_type,
728-
Test.branch == branch,
729-
)).first()
730-
731-
if platform_test is not None:
732-
progress = TestProgress(platform_test.id, TestStatus.canceled, message, datetime.datetime.now())
733-
g.db.add(progress)
734-
g.db.commit()
734+
if test is None:
735+
fork_url = f"%/{g.github['repository_owner']}/{g.github['repository']}.git"
736+
fork = Fork.query.filter(Fork.github.like(fork_url)).first()
737+
test = Test.query.filter(and_(Test.platform == platform,
738+
Test.commit == commit,
739+
Test.fork_id == fork.id,
740+
Test.test_type == test_type,
741+
Test.branch == branch,
742+
)).first()
743+
744+
if test is not None:
745+
progress = TestProgress(test.id, TestStatus.canceled, message, datetime.datetime.now())
746+
db = db or g.db
747+
db.add(progress)
748+
db.commit()
735749

736-
if gh_commit is not None:
737-
update_status_on_github(gh_commit, state, message, f"CI - {platform.value}")
750+
if gh_commit is not None:
751+
update_status_on_github(gh_commit, state, message, f"CI - {test.platform.value}")
738752

739753

740754
def queue_test(gh_commit: Commit.Commit, commit, test_type, platform, branch="master", pr_nr=0) -> None:
@@ -900,12 +914,11 @@ def start_ci():
900914
# If it's a valid PR, run the tests
901915
pr_nr = payload['pull_request']['number']
902916

903-
is_draft = payload['pull_request']['draft']
904917
action = payload['action']
905-
is_active = action in ['opened', 'synchronize', 'reopened', 'ready_for_review']
906-
is_inactive = action in ['closed', 'converted_to_draft']
918+
is_active = action in ['opened', 'synchronize', 'reopened']
919+
is_inactive = action in ['closed']
907920

908-
if not is_draft and is_active:
921+
if is_active:
909922
try:
910923
commit_hash = payload['pull_request']['head']['sha']
911924
except KeyError:
@@ -935,9 +948,10 @@ def start_ci():
935948
g.db.add(progress)
936949
g.db.commit()
937950
# If test run status exists, mark them as cancelled
938-
for status in repository.get_commit(test.commit).get_statuses():
951+
gh_commit = repository.get_commit(test.commit)
952+
for status in gh_commit.get_statuses():
939953
if status["context"] == f"CI - {test.platform.value}":
940-
repository.get_commit(test.commit).create_status(
954+
gh_commit.create_status(
941955
state=Status.FAILURE,
942956
description="Tests cancelled",
943957
context=f"CI - {test.platform.value}",

tests/test_ci/test_controllers.py

Lines changed: 33 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -256,20 +256,38 @@ def extractall(*args, **kwargs):
256256
mock_create_instance.assert_called_once()
257257
mock_wait_for_operation.assert_called_once()
258258

259+
@mock.patch('github.Github.get_repo')
259260
@mock.patch('mod_ci.controllers.start_test')
260261
@mock.patch('mod_ci.controllers.get_compute_service_object')
261262
@mock.patch('mod_ci.controllers.g')
262-
def test_gcp_instance(self, mock_g, mock_get_compute_service_object, mock_start_test):
263+
def test_gcp_instance(self, mock_g, mock_get_compute_service_object, mock_start_test, mock_repo):
263264
"""Test gcp_instance function."""
264265
from mod_ci.controllers import gcp_instance
265266

266-
# Making a sample test invalid
267-
test = Test.query.get(1)
268-
test.pr_nr = 0
267+
repo = mock_repo()
268+
269+
# Making test with id 1 invalid with pr_nr = 0
270+
test_1 = Test.query.get(1)
271+
test_1.pr_nr = 0
272+
273+
# Making pr of test with id 2 already updated
274+
test_2 = Test.query.get(2)
275+
pr_head_sha = test_2.commit + 'f'
276+
repo.get_pull.return_value.head.sha = pr_head_sha
277+
repo.get_pull.return_value.state = 'open'
278+
279+
# Creating a test with pull_request type that is valid
280+
test_3 = Test(TestPlatform.linux, TestType.pull_request, 1, "pull_request", pr_head_sha, 2)
281+
g.db.add(test_3)
282+
283+
# Creating a test with commit type that is valid
284+
test_4 = Test(TestPlatform.linux, TestType.commit, 1, "master", pr_head_sha)
285+
g.db.add(test_4)
269286
g.db.commit()
270-
gcp_instance(self.app, mock_g.db, TestPlatform.linux, mock.ANY, None)
271287

272-
mock_start_test.assert_called_once()
288+
gcp_instance(self.app, mock_g.db, TestPlatform.linux, repo, None)
289+
290+
self.assertEqual(mock_start_test.call_count, 2)
273291
mock_get_compute_service_object.assert_called_once()
274292

275293
def test_get_compute_service_object(self):
@@ -459,9 +477,10 @@ def test_customizedtest_added_to_queue(self):
459477
self.assertIn(2, customized_test)
460478
self.assertNotIn(1, customized_test)
461479

480+
@mock.patch('flask.g.log.error')
462481
@mock.patch('mailer.Mailer')
463482
@mock.patch('mod_ci.controllers.get_html_issue_body')
464-
def test_inform_mailing_list(self, mock_get_html_issue_body, mock_email):
483+
def test_inform_mailing_list(self, mock_get_html_issue_body, mock_email, mock_log_error):
465484
"""Test the inform_mailing_list function."""
466485
from mod_ci.controllers import inform_mailing_list
467486

@@ -482,6 +501,11 @@ def test_inform_mailing_list(self, mock_get_html_issue_body, mock_email):
482501
}
483502
)
484503
mock_get_html_issue_body.assert_called_once()
504+
mock_log_error.assert_not_called()
505+
506+
mock_email.send_simple_message.return_value = False
507+
inform_mailing_list(mock_email, "matejmecka", "2430", "Some random string", "Lorem Ipsum sit dolor amet...")
508+
mock_log_error.assert_called_once()
485509

486510
@staticmethod
487511
@mock.patch('mod_ci.controllers.markdown')
@@ -741,34 +765,6 @@ def __init__(self):
741765

742766
mock_test.query.filter.assert_called_once()
743767

744-
@mock.patch('github.Github.get_repo')
745-
@mock.patch('mod_ci.controllers.Test')
746-
@mock.patch('requests.get', side_effect=mock_api_request_github)
747-
def test_webhook_pr_converted_to_draft(self, mock_requests, mock_test, mock_repo):
748-
"""Test webhook triggered with pull_request event with converted_to_draft action."""
749-
platform_name = "platform"
750-
751-
class MockTest:
752-
def __init__(self):
753-
self.id = 1
754-
self.progress = []
755-
self.platform = MockPlatform(platform_name)
756-
self.commit = "test"
757-
758-
mock_test.query.filter.return_value.all.return_value = [MockTest()]
759-
mock_repo.return_value.get_commit.return_value.get_statuses.return_value = [
760-
{"context": f"CI - {platform_name}"}]
761-
762-
data = {'action': 'converted_to_draft',
763-
'pull_request': {'number': 1234, 'draft': False}}
764-
# one of ip address from GitHub web hook
765-
with self.app.test_client() as c:
766-
response = c.post(
767-
'/start-ci', environ_overrides=WSGI_ENVIRONMENT,
768-
data=json.dumps(data), headers=self.generate_header(data, 'pull_request'))
769-
770-
mock_test.query.filter.assert_called_once()
771-
772768
@mock.patch('mod_ci.controllers.BlockedUsers')
773769
@mock.patch('github.Github.get_repo')
774770
@mock.patch('requests.get', side_effect=mock_api_request_github)
@@ -803,25 +799,6 @@ def test_webhook_pr_opened(self, mock_request, mock_add_test_entry, mock_repo, m
803799
mock_blocked.query.filter.assert_called_once_with(mock_blocked.user_id == 'test')
804800
mock_add_test_entry.assert_called_once()
805801

806-
@mock.patch('mod_ci.controllers.BlockedUsers')
807-
@mock.patch('github.Github.get_repo')
808-
@mock.patch('mod_ci.controllers.add_test_entry')
809-
@mock.patch('requests.get', side_effect=mock_api_request_github)
810-
def test_webhook_pr_ready_for_review(self, mock_request, mock_add_test_entry, mock_repo, mock_blocked):
811-
"""Test webhook triggered with pull_request event with ready_for_review action."""
812-
mock_blocked.query.filter.return_value.first.return_value = None
813-
814-
data = {'action': 'ready_for_review',
815-
'pull_request': {'number': 1234, 'head': {'sha': 'abcd1234'}, 'user': {'id': 'test'}, 'draft': False}}
816-
with self.app.test_client() as c:
817-
response = c.post(
818-
'/start-ci', environ_overrides=WSGI_ENVIRONMENT,
819-
data=json.dumps(data), headers=self.generate_header(data, 'pull_request'))
820-
821-
self.assertEqual(response.data, b'{"msg": "EOL"}')
822-
mock_blocked.query.filter.assert_called_once_with(mock_blocked.user_id == 'test')
823-
mock_add_test_entry.assert_called_once()
824-
825802
@mock.patch('mod_ci.controllers.BlockedUsers')
826803
@mock.patch('github.Github.get_repo')
827804
@mock.patch('requests.get', side_effect=mock_api_request_github)
@@ -1292,10 +1269,8 @@ def test_github_api_error(self, mock_critical, mock_github):
12921269
schedule_test(github_status)
12931270
mock_critical.assert_called()
12941271
mock_critical.reset_mock()
1295-
deschedule_test(github_status, 1, TestType.commit, TestPlatform.linux)
1296-
mock_critical.assert_called()
1297-
mock_critical.reset_mock()
1298-
deschedule_test(github_status, 1, TestType.commit, TestPlatform.windows)
1272+
test = Test.query.first()
1273+
deschedule_test(github_status, test=test, db=g.db)
12991274
mock_critical.assert_called()
13001275

13011276
@mock.patch('mod_ci.controllers.is_main_repo')

tests/test_sample/test_controllers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,13 @@ def test_edit_sample(self):
200200
sample = Sample.query.filter(Sample.id == 1).first()
201201
self.assertEqual(sample.upload.notes, 'mp4')
202202
self.assertEqual(sample.upload.parameters, '-latin1')
203+
204+
def test_edit_sample_get_request(self):
205+
"""Check the edit sample get form request."""
206+
self.create_user_with_role(self.user.name, self.user.email, self.user.password, Role.admin)
207+
208+
with self.app.test_client() as c:
209+
c.post('/account/login', data=self.create_login_form_data(self.user.email, self.user.password))
210+
response = c.get('/sample/edit/1')
211+
self.assertIn("Editing sample with id 1", str(response.data))
212+
self.assertEqual(response.status_code, 200)

0 commit comments

Comments
 (0)