From 520647b463b2445871d71046e107a5929998b9a0 Mon Sep 17 00:00:00 2001 From: Joel Savitz Date: Thu, 22 May 2025 18:35:50 -0400 Subject: [PATCH 1/3] denis,orbit: give corrupt or missing patches an automatic zero On the relevant deadline trigger, assign a zero score to a submission when mailman receives none or corrupt patches. Fixes #207 Signed-off-by: Joel Savitz --- denis/final.py | 8 ++++---- denis/initial.py | 8 ++++---- denis/peer_review.py | 4 ++-- denis/utilities.py | 28 ++++++++++++++++++++++++++-- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/denis/final.py b/denis/final.py index 3728d311..a31e2b74 100755 --- a/denis/final.py +++ b/denis/final.py @@ -5,12 +5,12 @@ assignment = sys.argv[1] -utilities.release_subs([sub for sub in - utilities.user_to_sub(assignment, 'final').values() - if sub]) +usernames_to_subs = utilities.user_to_sub(assignment, 'final') + +utilities.release_subs([sub.submission_id for sub in usernames_to_subs.values() if sub]) print(f'final subs for {assignment} released') tags = utilities.update_tags(assignment, 'final') -utilities.run_automated_checks(tags) +utilities.run_automated_checks(tags, usernames_to_subs) diff --git a/denis/initial.py b/denis/initial.py index 71cf2d05..2047896f 100755 --- a/denis/initial.py +++ b/denis/initial.py @@ -13,8 +13,8 @@ usernames_to_subs = utilities.user_to_sub(assignment, 'initial') -students_who_submitted = [username for username, sub_id in - usernames_to_subs.items() if sub_id is not None] +students_who_submitted = [username for username, sub in + usernames_to_subs.items() if sub is not None] # restrict future email access for students who didnt make an initial sub for student, sub_id in usernames_to_subs.items(): @@ -54,10 +54,10 @@ print(e) -utilities.release_subs([sub for sub in usernames_to_subs.values() if sub]) +utilities.release_subs([sub.submission_id for sub in usernames_to_subs.values() if sub]) print(f'initial subs for {assignment} released') tags = utilities.update_tags(assignment, 'initial') -utilities.run_automated_checks(tags) +utilities.run_automated_checks(tags, usernames_to_subs) diff --git a/denis/peer_review.py b/denis/peer_review.py index 72bc418a..b3a4212a 100755 --- a/denis/peer_review.py +++ b/denis/peer_review.py @@ -7,8 +7,8 @@ ids = [] -ids += [sub for sub in utilities.user_to_sub(assignment, 'review1').values() if sub] -ids += [sub for sub in utilities.user_to_sub(assignment, 'review2').values() if sub] +ids += [sub.submission_id for sub in utilities.user_to_sub(assignment, 'review1').values() if sub] +ids += [sub.submission_id for sub in utilities.user_to_sub(assignment, 'review2').values() if sub] utilities.release_subs(ids) diff --git a/denis/utilities.py b/denis/utilities.py index 09e34f74..4d6e9b8b 100644 --- a/denis/utilities.py +++ b/denis/utilities.py @@ -23,7 +23,7 @@ def user_to_sub(assignment, component): sub = (relevant_gradeables .where(grd_tbl.user == username) .first()) - submission_ids[user.username] = sub.submission_id if sub else None + submission_ids[user.username] = sub if sub else None return submission_ids @@ -80,7 +80,29 @@ def update_tags(assignment, component): return updated_tags -def run_automated_checks(tags): +def check_corrupt_or_missing(repo, tag, username_to_subs): + [assignment, component, user] = tag.split('_') + + gradable = username_to_subs[user] + + msg = 'corruption and existence check' + msg += '\n' + msg += '------------------------------' + msg += '\n\n' + if not gradable or gradable.status[-1] == '!': + repo.git.execute(['git', 'notes', '--ref=grade', 'add', tag, '-m', '0']) + if not gradable: + msg += 'automatic 0 due to missing submission!' + else: + msg += 'automatic 0 due to corrupt submission!' + else: + msg += 'Patchset applies.' + + msg += '\n\n' + return msg + + +def run_automated_checks(tags, username_to_subs): with tempfile.TemporaryDirectory() as repo_path: repo = git.Repo.clone_from(PULL_URL, repo_path) @@ -90,6 +112,8 @@ def run_automated_checks(tags): for tag in tags: msg = 'Automated tests by denis' + msg += '\n\n' + msg += check_corrupt_or_missing(repo, tag, username_to_subs) repo.git.execute(['git', 'notes', '--ref=denis', 'add', tag, '-m', msg]) remote.push('refs/notes/*:refs/notes/*') From 0da9847b23f4be4bf4ae19b45d77ba695e521879 Mon Sep 17 00:00:00 2001 From: Joel Savitz Date: Fri, 23 May 2025 00:30:13 -0400 Subject: [PATCH 2/3] denis: verify DCO in each patch in patchset compare DCO in each patch and cover letter to one created by combining the known username and hostname. Fixes #110 Signed-off-by: Joel Savitz --- container-compose.yml | 1 + denis/utilities.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/container-compose.yml b/container-compose.yml index 77890ceb..2f269382 100644 --- a/container-compose.yml +++ b/container-compose.yml @@ -185,6 +185,7 @@ services: - orbit_source=./orbit environment: TZ: ${SINGULARITY_TIMEZONE} + SINGULARITY_HOSTNAME: ${SINGULARITY_HOSTNAME} volumes: - type: volume source: denis-db diff --git a/denis/utilities.py b/denis/utilities.py index 4d6e9b8b..b26bd883 100644 --- a/denis/utilities.py +++ b/denis/utilities.py @@ -1,3 +1,4 @@ +import os import git import subprocess import tempfile @@ -102,6 +103,34 @@ def check_corrupt_or_missing(repo, tag, username_to_subs): return msg +def check_signed_off_by(repo, tag): + [_, _, user] = tag.split('_') + hostname = os.getenv("SINGULARITY_HOSTNAME") + + msg = 'signed off by check' + msg += '\n' + msg += '-------------------' + msg += '\n\n' + + commits = repo.git.execute(['git', 'rev-list', '--reverse', tag]).split('\n') + expected_dco = f'Signed-off-by: {user} <{user}@{hostname}>' + nr_flawless = 0 + for i, commit in enumerate(commits): + patch = repo.git.execute(['git', 'show', commit]) + if expected_dco in patch: + nr_flawless += 1 + elif ('Signed-off-by:' in patch) or ('signed-off-by:' in patch): + msg += f'patch {i}: double check Signed-off-by\n' + else: + msg += f'patch {i}: no Signed-off-by line found\n' + + if len(commits) == nr_flawless: + msg += 'All signed off by lines present as expected.\n' + + msg += '\n' + return msg + + def run_automated_checks(tags, username_to_subs): with tempfile.TemporaryDirectory() as repo_path: repo = git.Repo.clone_from(PULL_URL, repo_path) @@ -114,6 +143,10 @@ def run_automated_checks(tags, username_to_subs): msg = 'Automated tests by denis' msg += '\n\n' msg += check_corrupt_or_missing(repo, tag, username_to_subs) + if msg[-3] != '!': + msg += '\n\n' + msg += check_signed_off_by(repo, tag) + repo.git.execute(['git', 'notes', '--ref=denis', 'add', tag, '-m', msg]) remote.push('refs/notes/*:refs/notes/*') From 265cd3d113c1ed922e370c102ff313de38cf6ba4 Mon Sep 17 00:00:00 2001 From: Joel Savitz Date: Fri, 30 May 2025 12:40:51 -0400 Subject: [PATCH 3/3] denis: check subject tag format Check RFC/lack thereof, PATH, vN consistency, and correct indexes Fixes: #262 Signed-off-by: Joel Savitz --- denis/utilities.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/denis/utilities.py b/denis/utilities.py index b26bd883..2d777f7a 100644 --- a/denis/utilities.py +++ b/denis/utilities.py @@ -131,6 +131,41 @@ def check_signed_off_by(repo, tag): return msg +def check_subject_tag(repo, tag): + [assignment, component, user] = tag.split('_') + + msg = 'subject tag check' + msg += '\n' + msg += '-----------------' + msg += '\n\n' + + commits = repo.git.execute(['git', 'rev-list', '--reverse', tag]).split('\n') + + sub_tbl = mailman.db.Submission + relevant_submissions = (sub_tbl.select() + .where(sub_tbl.recipient == assignment) + .where(sub_tbl.user == user) + .order_by(sub_tbl.timestamp.desc())) + expected_revision_number = relevant_submissions.count() + + nr_commits = len(commits) + + nr_flawless = 0 + for i, commit in enumerate(commits): + # from 0/n .. n/n + expected_tag = f'[{"RFC " if component == "initial" else ""}PATCH v{expected_revision_number} {i}/{nr_commits - 1}]' + patch = repo.git.execute(['git', 'show', commit]) + if expected_tag in patch: + nr_flawless += 1 + else: + msg += f'patch {i}: subject tag not detected (expected "{expected_tag}")\n' + + if nr_commits == nr_flawless: + msg += 'Found all expected correct subject tags\n' + + return msg + + def run_automated_checks(tags, username_to_subs): with tempfile.TemporaryDirectory() as repo_path: repo = git.Repo.clone_from(PULL_URL, repo_path) @@ -146,6 +181,7 @@ def run_automated_checks(tags, username_to_subs): if msg[-3] != '!': msg += '\n\n' msg += check_signed_off_by(repo, tag) + msg += check_subject_tag(repo, tag) repo.git.execute(['git', 'notes', '--ref=denis', 'add', tag, '-m', msg])