assignment-codeval 0.0.32__py3-none-any.whl → 0.0.34__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,17 @@
1
1
  from typing import NoReturn
2
2
 
3
3
  import click
4
+ import re
4
5
  import time
5
6
  import dataclasses
6
7
 
7
8
 
8
9
  def despace(name):
9
- # we need to replace spaces with _ to work well with scripts
10
10
  # we need to remove the : so that it doesn't mess up docker
11
- return name.replace(" ", "_").replace(":","")
11
+ # (removed rather than replaced to keep existing names stable)
12
+ # everything else that isn't a word character, . or - becomes _ so the
13
+ # name is a single path component that is safe in scripts and the shell
14
+ return re.sub(r"[^\w.-]", "_", name.replace(":", ""))
12
15
 
13
16
 
14
17
  @dataclasses.dataclass(init=True, repr=True, frozen=True)
@@ -1,5 +1,7 @@
1
1
  import os
2
+ import posixpath
2
3
  import re
4
+ import shlex
3
5
  import shutil
4
6
  import subprocess
5
7
  import time
@@ -16,6 +18,7 @@ import requests
16
18
 
17
19
  from assignment_codeval.canvas_utils import connect_to_canvas, get_course, get_courses, get_assignment
18
20
  from assignment_codeval.commons import debug, error, info, warn, despace
21
+ from assignment_codeval.github_connect import _read_desired_commit
19
22
 
20
23
  # Bookkeeping files the tool writes into each student directory. A student must
21
24
  # never be able to clobber these via an attachment filename — in particular
@@ -514,6 +517,64 @@ def upload_submission_comments(submissions_dir, codeval_prefix, delete):
514
517
  warn(f"no submission found for {student_id} in {course_name}: {assignment_name}")
515
518
 
516
519
 
520
+ def _github_fetch_problem(dirpath, submission_dir):
521
+ """For a GitHub submission whose fetch failed, build the student-facing message.
522
+
523
+ Returns None when the submission is not GitHub-based (no github_repo in
524
+ metadata.txt) or when the checkout matches the submitted commit, so the
525
+ caller proceeds with normal grading.
526
+
527
+ Covers both failure shapes: no checkout at all (clone failed, bad digest)
528
+ and a clone whose gh_success.txt is missing or stale (the submitted commit
529
+ could not be checked out, e.g. it was never pushed) — grading that tree
530
+ would silently grade the wrong commit. github-setup-repo leaves its
531
+ clone/checkout errors (bad digest, failed clone, failed checkout) in
532
+ comments.txt; include them so the student sees why the fetch failed
533
+ instead of a misleading complaint about a missing assignment directory.
534
+ """
535
+ metadata_path = os.path.join(dirpath, "metadata.txt")
536
+ has_github_repo = False
537
+ if os.path.exists(metadata_path):
538
+ with open(metadata_path, "r") as f:
539
+ has_github_repo = any(
540
+ line.startswith("github_repo=") and line.split("=", 1)[1].strip()
541
+ for line in f)
542
+ if not has_github_repo:
543
+ return None
544
+
545
+ desired = _read_desired_commit(os.path.join(dirpath, "content.txt"))
546
+ recorded = None
547
+ try:
548
+ with open(os.path.join(dirpath, "gh_success.txt"), "r") as f:
549
+ recorded = f.readline().strip() or None
550
+ except OSError:
551
+ pass
552
+
553
+ if os.path.isdir(submission_dir):
554
+ # Mirror github-setup-repo: a recorded checkout is good if it matches
555
+ # the submitted digest, or if the digest is unreadable (leave as-is).
556
+ if recorded and (not desired or recorded == desired):
557
+ return None
558
+ out = (b"your repository was cloned, but the commit digest you submitted in Canvas\n"
559
+ b"could not be checked out, so your submission was not graded. Make sure you\n"
560
+ b"have pushed your commits and submitted only the digest of your final\n"
561
+ b"pushed commit (the 40-character output of `git rev-parse HEAD`) in Canvas.\n")
562
+ else:
563
+ out = (b"your submission could not be fetched from GitHub, so it was not graded.\n"
564
+ b"Make sure you have pushed your commits and submitted only the digest of\n"
565
+ b"your final commit (the 40-character output of `git rev-parse HEAD`) in Canvas.\n")
566
+ try:
567
+ with open(os.path.join(dirpath, "comments.txt"), "rb") as f:
568
+ prior = f.read()
569
+ except OSError:
570
+ prior = b""
571
+ # Include only clone logs written by github-setup-repo, never the result
572
+ # of a previous evaluation run (which would nest on re-runs).
573
+ if prior.startswith((b"cloning", "❌".encode())):
574
+ out += b"\nDetails of the failed fetch:\n" + prior
575
+ return out
576
+
577
+
517
578
  @click.command()
518
579
  @click.argument('codeval_dir', metavar="CODEVAL_DIR", required=False)
519
580
  @click.option("--submissions-dir", help="directory containing submissions COURSE/ASSIGNMENT/STUDENT_ID",
@@ -559,6 +620,18 @@ def evaluate_submissions(codeval_dir, submissions_dir):
559
620
  with open(os.path.join(dirpath, "codeval_path.txt"), "w") as f:
560
621
  f.write(os.path.abspath(codeval_file))
561
622
 
623
+ # A GitHub submission with no checkout, or a checkout that isn't the
624
+ # submitted commit, means the fetch failed (bad digest, failed clone
625
+ # or checkout). Say that, rather than falling through to a misleading
626
+ # "<dir> does not exist" message or grading the wrong commit.
627
+ fetch_failure = _github_fetch_problem(dirpath, submission_dir)
628
+ if fetch_failure is not None:
629
+ info("writing results")
630
+ with open(f"{dirpath}/comments.txt", "wb") as fd:
631
+ fd.write(fetch_failure)
632
+ info("continuing")
633
+ continue
634
+
562
635
  # First pass: get CTO, CD tags, and collect Z files (don't extract yet)
563
636
  compile_timeout = 20
564
637
  assignment_working_dir = "."
@@ -620,7 +693,16 @@ def evaluate_submissions(codeval_dir, submissions_dir):
620
693
  os.chmod(os.path.join(dest_dir, f.filename), perms)
621
694
 
622
695
  if not move_to_next_submission:
623
- command = raw_command.replace("EVALUATE", "cd /submissions 2>/dev/null || true; assignment-codeval run-evaluation codeval.txt")
696
+ # Mount the whole submission and cd to the working directory
697
+ # inside the container: mounting only the working directory would
698
+ # hide the repository's .git (which lives at the submission root)
699
+ # from git-based tests.
700
+ container_working_dir = posixpath.normpath(
701
+ posixpath.join("/submissions", assignment_working_dir.replace("\\", "/")))
702
+ command = raw_command.replace(
703
+ "EVALUATE",
704
+ f"cd {shlex.quote(container_working_dir)} 2>/dev/null || true; "
705
+ "assignment-codeval run-evaluation codeval.txt")
624
706
 
625
707
  with TemporaryDirectory("cedir", dir="/var/tmp") as link_dir:
626
708
  submission_link = os.path.join(link_dir, "submissions")
@@ -647,7 +729,7 @@ def evaluate_submissions(codeval_dir, submissions_dir):
647
729
  os.path.join(full_assignment_working_dir, ref_file),
648
730
  submission_dir)
649
731
 
650
- command = command.replace("SUBMISSIONS", full_assignment_working_dir)
732
+ command = command.replace("SUBMISSIONS", submission_link)
651
733
  info(f"command to execute: {command}")
652
734
  p = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
653
735
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: assignment-codeval
3
- Version: 0.0.32
3
+ Version: 0.0.34
4
4
  Summary: CodEval for evaluating programming assignments
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -3,7 +3,7 @@ assignment_codeval/ai_benchmark.py,sha256=8WkLYD72NNS_82pF3IVpvXgBJDMN8PBYjmw4fV
3
3
  assignment_codeval/canvas_utils.py,sha256=NF2xw10A1okVtDnP98YRzEiSo7QlqYF-IVAih4-WeLQ,5998
4
4
  assignment_codeval/check_grading.py,sha256=rnNdp8T_XWckDfF1zUi3r70BMe9CPNTD-RfjfarI-CM,5889
5
5
  assignment_codeval/cli.py,sha256=Vl27zNI7kI0QEZoXTIE2aTrraEJWj9vgJiyVWou3vgU,1357
6
- assignment_codeval/commons.py,sha256=rr09qHacHoLmmU8S9sBss4_utb0DH8-IyL9Dsj0ycE0,1412
6
+ assignment_codeval/commons.py,sha256=hnos85_OZsMv2OmALzFzXQe23HYIsbTnxXjINJUghEQ,1583
7
7
  assignment_codeval/convertMD2Html.py,sha256=oNFY63WgxiXfk1v3j8zc1RxCzzQBzj6fXupezogPZfw,8248
8
8
  assignment_codeval/create_assignment.py,sha256=tUgCYWhk4zoPZf8p4wyqHYgbEmLcX8XnUNykAv0XEW0,12488
9
9
  assignment_codeval/evaluate.py,sha256=8ce59_fk13cp59hCAKywjojxAC1FdU6TKiF4t5eJKoY,42881
@@ -12,10 +12,10 @@ assignment_codeval/file_utils.py,sha256=vAFnPaA4p6BQabkn034SacBFhV_Mpib2sbZOxvk-
12
12
  assignment_codeval/github_connect.py,sha256=4Fc9UusgXTqwlvNW-ppVKCfNnQM0Gp17oXWkUK4OWiY,6659
13
13
  assignment_codeval/install_assignment.py,sha256=HsXzTex4G-U3t4tb-HyQ2AobBDBPcKeR6oAUDFqBP-A,4599
14
14
  assignment_codeval/recent_comments.py,sha256=WelZpODSvZ5hbRn2IofEpqMwrvHx8haOG4KZJCvHYKw,9415
15
- assignment_codeval/submissions.py,sha256=P-hNLOLKCBmWZVcYZi3FF9PHfoxghGufz64QHw98CDk,42330
15
+ assignment_codeval/submissions.py,sha256=Kg2DBtSSFhN_C1ePUIKUn5VW5TIVw2_7Giu2PpLdSOA,46366
16
16
  assignment_codeval/test_template.html,sha256=c0e2lUurSalF4PpLIVWQCXirXeA8PvB4pzWe-kFS-9Y,3382
17
- assignment_codeval-0.0.32.dist-info/METADATA,sha256=zicpCFBA1Vp_ceEqJAGAcTfLQSOiuvPha2rTy9ERjJg,7757
18
- assignment_codeval-0.0.32.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
19
- assignment_codeval-0.0.32.dist-info/entry_points.txt,sha256=Pk0eAhsXBpCsgkv6HYLSHTRf5x_zUf-Lg8KK9KsK2B8,66
20
- assignment_codeval-0.0.32.dist-info/top_level.txt,sha256=a_P8W2IWjAELxKlTV2t4rLwWVoTxElxUMvWgWjR_1n4,19
21
- assignment_codeval-0.0.32.dist-info/RECORD,,
17
+ assignment_codeval-0.0.34.dist-info/METADATA,sha256=y6OHiCYpxNb-8DEepajODCVbPQCTGlG-yG7WYDrGDuo,7757
18
+ assignment_codeval-0.0.34.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
19
+ assignment_codeval-0.0.34.dist-info/entry_points.txt,sha256=Pk0eAhsXBpCsgkv6HYLSHTRf5x_zUf-Lg8KK9KsK2B8,66
20
+ assignment_codeval-0.0.34.dist-info/top_level.txt,sha256=a_P8W2IWjAELxKlTV2t4rLwWVoTxElxUMvWgWjR_1n4,19
21
+ assignment_codeval-0.0.34.dist-info/RECORD,,