Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
edaaee7
update changelog with new release v2.8.0 [ci skip] (#635)
donny-wong Aug 22, 2025
efdc46f
Added support to AI tester for tags (#631)
wkukka1 Aug 25, 2025
2a7580a
Haskell tester updates (#636)
david-yz-liu Aug 26, 2025
8fc7693
update changelog with new release v2.8.1 [ci skip] (#638)
donny-wong Aug 26, 2025
5ed4941
Changed tasty-discover argument of source directory to a file (#648)
donny-wong Sep 4, 2025
9cd3212
Disabled access to third-party models in AI tester (#649)
david-yz-liu Sep 4, 2025
a619593
update changelog with new release v2.8.2 [ci skip] (#652)
donny-wong Sep 4, 2025
a6ee97a
Added troubleshooting section talking about Docker Content Trust (DCT…
donny-wong Sep 16, 2025
ca61e5b
Modified haskell tester setup to install stack with ghcup (#626)
donny-wong Sep 16, 2025
c56cc7c
Fixed Python tester to display fully qulaified name when running pyte…
david-yz-liu Sep 23, 2025
487e543
update changelog with new release v2.8.3 [ci skip] (#658)
donny-wong Sep 23, 2025
7d05eef
[pre-commit.ci] pre-commit autoupdate (#655)
pre-commit-ci[bot] Sep 27, 2025
29d2f8d
build(deps): bump jsonschema from 4.25.0 to 4.25.1 in /client (#646)
dependabot[bot] Sep 27, 2025
cd22c80
build(deps): bump jsonschema from 4.25.0 to 4.25.1 in /server (#639)
dependabot[bot] Sep 27, 2025
d74d172
build(deps): bump requests from 2.32.4 to 2.32.5 in /server (#640)
dependabot[bot] Sep 27, 2025
3bc24e7
build(deps): bump redis from 6.2.0 to 6.4.0 in /server (#643)
dependabot[bot] Oct 3, 2025
780338d
build(deps): bump redis from 6.2.0 to 6.4.0 in /client (#645)
dependabot[bot] Oct 3, 2025
d36c90c
build(deps): bump flask from 3.1.1 to 3.1.2 in /client (#647)
dependabot[bot] Oct 3, 2025
e7cc546
build(deps): bump rq from 2.4.1 to 2.6.0 in /server (#661)
dependabot[bot] Oct 3, 2025
43e9c4d
build(deps): bump rq from 2.4.1 to 2.6.0 in /client (#660)
dependabot[bot] Oct 3, 2025
dbd9f45
build(deps): bump pyyaml from 6.0.2 to 6.0.3 in /server (#659)
dependabot[bot] Oct 3, 2025
dc70e15
build(deps): bump supervisor from 4.2.5 to 4.3.0 in /server (#641)
dependabot[bot] Oct 3, 2025
1e3650f
Fixed AI tester to report error when the specific submission file is …
david-yz-liu Oct 3, 2025
fc158e2
[pre-commit.ci] pre-commit autoupdate (#664)
pre-commit-ci[bot] Oct 12, 2025
e3857da
Updated docker to Ubuntu 24.04 and fixed stack installation (#668)
david-yz-liu Oct 24, 2025
0ce20bf
ISSUE-662: Added support for extra_marks metadata and pytest marker (…
Naragod Nov 16, 2025
6a46db6
[pre-commit.ci] pre-commit autoupdate (#678)
pre-commit-ci[bot] Nov 17, 2025
2abbe49
build(deps): bump python-dotenv from 1.1.1 to 1.2.1 in /client (#674)
dependabot[bot] Nov 29, 2025
a296ba9
Removed click from server requirements.txt file (#679)
david-yz-liu Nov 29, 2025
bad55ca
build(deps): bump redis from 6.4.0 to 7.0.1 in /server (#677)
dependabot[bot] Nov 29, 2025
a942886
build(deps): bump redis from 6.4.0 to 7.0.1 in /client (#676)
dependabot[bot] Nov 29, 2025
465807e
build(deps): bump psycopg2-binary from 2.9.10 to 2.9.11 in /server (#…
dependabot[bot] Nov 29, 2025
cf88709
Fixed bug in R tester setup that always triggered reinstallation of R…
david-yz-liu Nov 29, 2025
49f27a2
Merge branch 'master' into release_2.9.0
Dec 11, 2025
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ __pycache__
venv
venv2
build
docker-compose.override.yml
/workspace
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 25.1.0
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 25.11.0
hooks:
- id: black
- repo: https://github.com/pycqa/flake8
Expand Down
9 changes: 9 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# CHANGELOG
All notable changes to this project will be documented here.

## [v2.9.0]
- Install stack with GHCup (#626)
- Fixed AI tester to report error when the specified `submission` file is not found (#663)
- Updated docker image to use Ubuntu 24.04 (#668)
- Fixed stack installation in Docker environment (#668)
- Removed `click` from server requirements.txt file (#679)
- Fixed bug in R tester setup that always triggered reinstallation of R dependencies (#680)

## [v2.8.3]
- Add troubleshooting section talking about Docker Content Trust (DCT) (#653)
- Fixed Python tester to display fully qualified name when running pytest (#656)
Expand All @@ -22,6 +30,7 @@ All notable changes to this project will be documented here.
- Add `ai_tester` module to support AI-based autograding via `ai-autograding-feedback` (#625)
- Add `ai` to list of testers (#628)
- Fixed an `AttributeError` when handling exceptions in server `update_test_settings` (#629)
- Add tag functionalty to `ai_tester` (#631)
- Added opt out feature to the `ai_tester` by searching for `NO_EXTERNAL_AI_FEEDBACK` (#632)
- Modified R tester to always display test result messages, even when tests pass (#633)

Expand Down
5 changes: 4 additions & 1 deletion client/.dockerfiles/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
ARG UBUNTU_VERSION=22.04
ARG UBUNTU_VERSION=24.04

FROM ubuntu:$UBUNTU_VERSION

# Remove ubuntu user, added in the 23.04 image
RUN userdel -r ubuntu

RUN apt-get update -y && \
DEBIAN_FRONTEND=noninteractive apt-get -y install software-properties-common && \
DEBIAN_FRONTEND=noninteractive add-apt-repository -y ppa:deadsnakes/ppa && \
Expand Down
10 changes: 5 additions & 5 deletions client/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
flask==3.1.1
python-dotenv==1.1.1
rq==2.4.1
redis==6.2.0
jsonschema==4.25.0
flask==3.1.2
python-dotenv==1.2.1
rq==2.6.0
redis==7.1.0
jsonschema==4.25.1
Werkzeug==3.1.3
8 changes: 4 additions & 4 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ services:
context: ./server
dockerfile: ./.dockerfiles/Dockerfile
args:
UBUNTU_VERSION: '22.04'
UBUNTU_VERSION: '24.04'
LOGIN_USER: 'docker'
WORKSPACE: '/home/docker/.autotesting'
image: markus-autotest-server-dev:1.3.0
image: markus-autotest-server-dev:1.4.0
volumes:
- ./server:/app:cached
- venv_server:/home/docker/markus_venv
Expand All @@ -28,8 +28,8 @@ services:
context: ./client
dockerfile: ./.dockerfiles/Dockerfile
args:
UBUNTU_VERSION: '22.04'
image: markus-autotest-client-dev:1.3.0
UBUNTU_VERSION: '24.04'
image: markus-autotest-client-dev:1.4.0
container_name: 'autotest-client'
volumes:
- ./client:/app:cached
Expand Down
14 changes: 8 additions & 6 deletions server/.dockerfiles/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
ARG UBUNTU_VERSION=22.04
ARG UBUNTU_VERSION=24.04

FROM ubuntu:$UBUNTU_VERSION AS base

# Remove ubuntu user, added in the 23.04 image
RUN userdel -r ubuntu

ARG LOGIN_USER
ARG WORKSPACE
Expand Down Expand Up @@ -29,6 +31,7 @@ RUN apt-get update -y && \
libharfbuzz-dev \
libfribidi-dev \
libxml2-dev \
libnuma-dev \
r-base

RUN useradd -ms /bin/bash $LOGIN_USER && \
Expand All @@ -37,18 +40,17 @@ RUN useradd -ms /bin/bash $LOGIN_USER && \
adduser --disabled-login --no-create-home $worker && \
echo "$LOGIN_USER ALL=($worker) NOPASSWD:ALL" | EDITOR="tee -a" visudo && \
usermod -aG $worker $LOGIN_USER; \
done

RUN chmod a+x /home/${LOGIN_USER}
done && \
chmod a+x /home/${LOGIN_USER}

COPY . /app

RUN find /app/autotest_server/testers -name requirements.system -exec {} \;

RUN echo "TZ=$( cat /etc/timezone )" >> /etc/R/Renviron.site

RUN mkdir -p ${WORKSPACE} && chown ${LOGIN_USER} ${WORKSPACE}
RUN mkdir -p /home/${LOGIN_USER}/markus_venv && chown ${LOGIN_USER} /home/${LOGIN_USER}/markus_venv
RUN mkdir -p ${WORKSPACE} && chown ${LOGIN_USER} ${WORKSPACE} && \
mkdir -p /home/${LOGIN_USER}/markus_venv && chown ${LOGIN_USER} /home/${LOGIN_USER}/markus_venv

WORKDIR /home/${LOGIN_USER}

Expand Down
2 changes: 2 additions & 0 deletions server/autotest_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ def _create_test_group_result(
result["annotations"] = res["annotations"]
elif "tags" in res:
result["tags"] = res["tags"]
elif "extra_marks" in res:
result["extra_marks"] = res["extra_marks"]
elif "overall_comment" in res:
result["overall_comment"] = res["overall_comment"]
else:
Expand Down
67 changes: 44 additions & 23 deletions server/autotest_server/testers/ai/ai_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(
super().__init__(specs, test_class, resource_settings=resource_settings)
self.annotations = []
self.overall_comments = []
self.tags = []

def call_ai_feedback(self) -> dict:
"""
Expand Down Expand Up @@ -81,7 +82,17 @@ def call_ai_feedback(self) -> dict:
return results

submission_file = config.get("submission")
if self._term_in_file(submission_file):
try:
disallowed_term_in_file = self._term_in_file(submission_file)
except FileNotFoundError:
results[test_label] = {
"title": test_label,
"status": "error",
"message": f'Could not file submission file "{submission_file}"',
}
return results

if disallowed_term_in_file:
results[test_label] = {
"title": test_label,
"status": "success",
Expand All @@ -96,15 +107,26 @@ def call_ai_feedback(self) -> dict:
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=timeout, env=env)
output = result.stdout

parsed = None
try:
parsed = json.loads(output)
except json.JSONDecodeError:
pass
if isinstance(parsed, dict):
if "tags" in parsed:
tags = parsed["tags"]
self.tags.extend(tags)
if "output" in parsed:
output = parsed["output"]

if output_mode == "overall_comment":
self.overall_comments.append(output)
results[test_label] = {"title": test_label, "status": "success"}
elif output_mode == "annotations":
try:
annotations_data = json.loads(output)
annotations = annotations_data.get("annotations", annotations_data)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in output for {test_label}: {e}")
if parsed is None:
raise ValueError(f"Unable to parse the output of '{output}'")
annotations = parsed.get("annotations", parsed)
self.annotations.extend(annotations)
results[test_label] = {"title": test_label, "status": "success"}
elif output_mode == "message":
Expand All @@ -122,23 +144,20 @@ def _term_in_file(self, file_path: str) -> bool:
term = "NO_EXTERNAL_AI_FEEDBACK"
path = Path(file_path)

try:
if path.suffix.lower() == ".pdf":
with open(file_path, "rb") as f:
reader = PyPDF2.PdfReader(f)
for page in reader.pages:
text = page.extract_text() or ""
if term in text:
return True
return False
else:
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
if term in line:
return True
return False
except FileNotFoundError:
return True
if path.suffix.lower() == ".pdf":
with open(file_path, "rb") as f:
reader = PyPDF2.PdfReader(f)
for page in reader.pages:
text = page.extract_text() or ""
if term in text:
return True
return False
else:
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
if term in line:
return True
return False

@Tester.run_decorator
def run(self) -> None:
Expand All @@ -157,3 +176,5 @@ def after_tester_run(self) -> None:
print(self.test_class.format_annotations(self.annotations))
if self.overall_comments:
print(self.test_class.format_overall_comment(self.overall_comments, separator="\n\n"))
if self.tags:
print(self.test_class.format_tags(self.tags))
3 changes: 3 additions & 0 deletions server/autotest_server/testers/haskell/haskell_tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
from ..tester import Tester, Test, TestError
from ..specs import TestSpecs

home = os.getenv("HOME")
os.environ["PATH"] = f"{home}/.cabal/bin:{home}/.ghcup/bin:" + os.environ["PATH"]


class HaskellTest(Test):
def __init__(
Expand Down
15 changes: 11 additions & 4 deletions server/autotest_server/testers/haskell/requirements.system
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
#!/usr/bin/env bash

if ! dpkg -l ghc cabal-install haskell-stack &> /dev/null; then
apt-get -y update
DEBIAN_FRONTEND=noninteractive apt-get install -y -o 'Dpkg::Options::=--force-confdef' -o 'Dpkg::Options::=--force-confold' ghc cabal-install haskell-stack
apt-get -y update

if ! dpkg -l ghc cabal-install &> /dev/null; then
DEBIAN_FRONTEND=noninteractive apt-get install -y -o 'Dpkg::Options::=--force-confdef' -o 'Dpkg::Options::=--force-confold' ghc cabal-install
fi

stack update
if [ ! -x "$HOME/.ghcup/bin/ghcup" ] && [ ! -x "/usr/local/bin/stack" ]; then
BOOTSTRAP_HASKELL_NONINTERACTIVE=1 BOOTSTRAP_HASKELL_INSTALL_NO_STACK=1 curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
$HOME/.ghcup/bin/ghcup install stack recommended
if [ "$(id -u)" -eq 0 ]; then
cp $HOME/.ghcup/bin/stack /usr/local/bin/
fi
fi
42 changes: 27 additions & 15 deletions server/autotest_server/testers/haskell/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import subprocess

HASKELL_TEST_DEPS = ["tasty-discover", "tasty-quickcheck", "tasty-hunit"]
STACK_RESOLVER = "lts-16.17"
STACK_RESOLVER = "lts-21.21"

home = os.getenv("HOME")
os.environ["PATH"] = f"{home}/.cabal/bin:{home}/.ghcup/bin:" + os.environ["PATH"]


def create_environment(_settings, _env_dir, default_env_dir):
Expand All @@ -16,22 +19,31 @@ def create_environment(_settings, _env_dir, default_env_dir):


def install():
subprocess.run(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "requirements.system"),
check=True,
capture_output=True,
text=True,
)
try:
subprocess.run(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "requirements.system"),
check=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Error executing Haskell requirements.system: {e}")
resolver = STACK_RESOLVER
cmd = ["stack", "build", "--resolver", resolver, "--system-ghc", *HASKELL_TEST_DEPS]
subprocess.run(cmd, check=True, capture_output=True)
subprocess.run(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "stack_permissions.sh"),
check=True,
shell=True,
capture_output=True,
text=True,
)
try:
subprocess.run(cmd, check=True, capture_output=True)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Error running {cmd}: {e}")
try:
subprocess.run(
os.path.join(os.path.dirname(os.path.realpath(__file__)), "stack_permissions.sh"),
check=True,
shell=True,
capture_output=True,
text=True,
)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Error running Haskell stack_permissions.sh: {e}")


def settings():
Expand Down
4 changes: 3 additions & 1 deletion server/autotest_server/testers/haskell/stack_permissions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ echo "allow-different-user: true" >> $STACK_ROOT/config.yaml
echo "recommend-stack-upgrade: false" >> $STACK_ROOT/config.yaml
chmod a+w $STACK_ROOT/stack.sqlite3.pantry-write-lock
chmod a+w $STACK_ROOT/global-project/.stack-work/stack.sqlite3.pantry-write-lock
chmod a+w $STACK_ROOT/pantry/pantry.sqlite3.pantry-write-lock
chmod a+w $STACK_ROOT/pantry/pantry.sqlite3.pantry-write-lock
chmod a+r $WORKSPACE/.stack/config.yaml
chmod a+r $WORKSPACE/.stack/global-project/stack.yaml
2 changes: 1 addition & 1 deletion server/autotest_server/testers/jupyter/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pytest==7.1.2
psycopg2-binary==2.9.10
psycopg2-binary==2.9.11
git+https://github.com/MarkUsProject/autotest-helpers.git#subdirectory=notebook_helper
Loading