From 940239c516fe0fb2d7804cf3297a91aebcec1f9d Mon Sep 17 00:00:00 2001 From: Gunnar Kreitz Date: Wed, 2 Sep 2026 13:52:46 +0200 Subject: [PATCH 1/6] Use fixture Diagnostics in verify_hello, and add a few more asserts --- tests/test_verify_hello.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/tests/test_verify_hello.py b/tests/test_verify_hello.py index 95a48935..393f9074 100644 --- a/tests/test_verify_hello.py +++ b/tests/test_verify_hello.py @@ -1,18 +1,12 @@ -import logging -import pathlib +from pathlib import Path from problemtools import checks, model -from problemtools.diagnostics import LoggingDiagnostics +from problemtools.diagnostics import Diagnostics -def _make_diag(shortname: str) -> LoggingDiagnostics: - return LoggingDiagnostics.create(shortname, log_level=logging.WARNING) +def test_load_hello(diag: Diagnostics) -> None: + probdir = (Path(__file__).parent / 'hello').resolve() - -def test_load_hello(): - probdir = (pathlib.Path(__file__).parent / 'hello').resolve() - - diag = _make_diag('hello') problem = model.load_problem(probdir, diag) assert problem.shortname == 'hello' @@ -27,9 +21,13 @@ def test_load_hello(): assert not problem.metadata.is_multi_pass() assert not problem.metadata.is_submit_answer() + assert len(problem.testdata.get_all_testcases()) == 1, 'Hello should have exactly 1 test case' + assert problem.graders.grader is None, 'Hello uses the default grader' + assert problem.output_validators.uses_default(problem.format_version, problem.metadata), 'Hello uses the default validator' + -def test_load_twice(): - probdir = (pathlib.Path(__file__).parent / 'hello').resolve() +def test_load_twice(diag: Diagnostics): + probdir = (Path(__file__).parent / 'hello').resolve() - model.load_problem(probdir, _make_diag('hello')) - model.load_problem(probdir, _make_diag('hello')) + model.load_problem(probdir, diag) + model.load_problem(probdir, diag) From f990f9f9d66f23fab26af413be11ecac892f0721 Mon Sep 17 00:00:00 2001 From: Gunnar Kreitz Date: Wed, 2 Sep 2026 14:03:33 +0200 Subject: [PATCH 2/6] Extract directory computation in tests to pepare to move data --- tests/conftest.py | 14 ++++++++++++++ tests/test_config.py | 5 ++--- tests/test_languages.py | 3 ++- tests/test_latex.py | 17 +++++++++-------- tests/test_markdown.py | 17 ++++++++--------- tests/test_xss.py | 8 ++++---- 6 files changed, 39 insertions(+), 25 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1ea67d38..4762dcc1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,10 +1,24 @@ """Shared pytest fixtures and test doubles.""" +from pathlib import Path + import pytest from problemtools.diagnostics import Diagnostics +def datadir() -> Path: + """Root directory holding static test fixture data.""" + # TODO: move config1, config2, languages_examples, and problems under a + # tests/data directory, and change this to Path(__file__).parent / 'data'. + return Path(__file__).parent.resolve() + + +def example_directory(problem_name: str) -> Path: + """Path to one of the example problems shipped in the repo's top-level examples/ directory.""" + return (Path(__file__).parent.parent / 'examples' / problem_name).resolve() + + class RecordingDiagnostics(Diagnostics): """A Diagnostics that records messages instead of emitting them, for asserting on in tests.""" diff --git a/tests/test_config.py b/tests/test_config.py index c9d60e5d..49a40979 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,12 +1,11 @@ import pytest from problemtools import config +from tests.conftest import datadir def config_paths_mock(): - from pathlib import Path - - return [Path(__file__).parent / 'config1', Path(__file__).parent / 'config2'] + return [datadir() / 'config1', datadir() / 'config2'] def test_load_basic_config(monkeypatch): diff --git a/tests/test_languages.py b/tests/test_languages.py index e0255562..80c6a4c0 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -6,6 +6,7 @@ import pytest from problemtools import languages +from tests.conftest import datadir class Language_test(TestCase): @@ -295,7 +296,7 @@ def test_get_compile_command_splits_multiple_files(self): assert command == [shutil.which('echo'), subs.path, 'main.foo', 'helper1.bar', 'helper2.bar', subs.binary] -__EXAMPLES_PATH = os.path.join(os.path.dirname(__file__), 'languages_examples') +__EXAMPLES_PATH = datadir() / 'languages_examples' def examples_path(test_file): diff --git a/tests/test_latex.py b/tests/test_latex.py index 41484a12..6f687dec 100644 --- a/tests/test_latex.py +++ b/tests/test_latex.py @@ -3,13 +3,14 @@ from pathlib import Path from problemtools import problem2html, problem2pdf +from tests.conftest import example_directory def test_pdf_render_verifyproblem(): # Same options as in verifyproblem options = problem2pdf.get_parser().parse_args(['']) - problem_path = Path(__file__).parent / '..' / 'examples' / 'guess' - options.problem = str(problem_path.resolve()) + problem_path = example_directory('guess') + options.problem = str(problem_path) options.language = 'en' options.nopdf = True options.quiet = True @@ -20,9 +21,9 @@ def test_pdf_render_verifyproblem(): def test_pdf_render_problem2pdf(): # Same options as typical problem2pdf usage with tempfile.TemporaryDirectory() as temp_dir: - problem_path = Path(__file__).parent / '..' / 'examples' / 'guess' + problem_path = example_directory('guess') temp_filename = Path(temp_dir) / 'guess.pdf' - options = problem2pdf.get_parser().parse_args(['-o', str(temp_filename), '-l', 'en', '-q', str(problem_path.resolve())]) + options = problem2pdf.get_parser().parse_args(['-o', str(temp_filename), '-l', 'en', '-q', str(problem_path)]) if not problem2pdf.convert(options): assert False, 'PDF conversion failed' with open(temp_filename, 'rb') as temp_file: @@ -32,9 +33,9 @@ def test_pdf_render_problem2pdf(): def test_html_render_different(diag): # Same options as typical problem2html usage with tempfile.TemporaryDirectory() as temp_dir: - problem_path = Path(__file__).parent / '..' / 'examples' / 'different' + problem_path = example_directory('different') temp_dir = Path(temp_dir) / 'different_html' - options = problem2html.get_parser().parse_args(['-d', str(temp_dir), '-l', 'en', '-q', str(problem_path.resolve())]) + options = problem2html.get_parser().parse_args(['-d', str(temp_dir), '-l', 'en', '-q', str(problem_path)]) problem2html.convert(options, diag) with open(temp_dir / 'index.html', 'r') as temp_file: full_html = temp_file.read() @@ -48,9 +49,9 @@ def test_html_render_different(diag): def test_html_render_guess(diag): # Same options as typical problem2html usage with tempfile.TemporaryDirectory() as temp_dir: - problem_path = Path(__file__).parent / '..' / 'examples' / 'guess' + problem_path = example_directory('guess') temp_dir = Path(temp_dir) / 'guess_html' - options = problem2html.get_parser().parse_args(['-d', str(temp_dir), '-l', 'en', '-q', str(problem_path.resolve())]) + options = problem2html.get_parser().parse_args(['-d', str(temp_dir), '-l', 'en', '-q', str(problem_path)]) problem2html.convert(options, diag) with open(temp_dir / 'index.html', 'r') as temp_file: full_html = temp_file.read() diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 71e48521..2357858f 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -1,14 +1,13 @@ -from pathlib import Path - import pytest from problemtools.statement_util import find_footnotes +from tests.conftest import datadir from tests.test_xss import render, renderpdf # TODO: add when guess is updated to 2023-07 # def test_pdf_render(): # with tempfile.TemporaryDirectory() as temp_dir: -# problem_path = Path(__file__).parent / '..' / 'examples' / 'guess' +# problem_path = example_directory('guess') # args, _unknown = problem2pdf.get_parser().parse_known_args( # ['--problem', str(problem_path.resolve()), '-l', 'sv', '--dest-dir', str(temp_dir)] # ) @@ -16,7 +15,7 @@ def test_sample_escaping(diag): - problem_path = Path(__file__).parent / 'problems' / 'specialcharacterssample' + problem_path = datadir() / 'problems' / 'specialcharacterssample' html = render(problem_path, diag) all_printable = r"""0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~""" # We escape &, < and > @@ -30,18 +29,18 @@ def test_footnotes(diag): # We always want footnotes to be at the bottom # When we insert samples, we need to insert them right above the first footnote # To do this, we search for a string (very fragile) - problem_path = Path(__file__).parent / 'problems' / 'footnote' + problem_path = datadir() / 'problems' / 'footnote' html = render(problem_path, diag) assert find_footnotes(html) is not None - problem_path = Path(__file__).parent / 'problems' / 'twofootnotes' + problem_path = datadir() / 'problems' / 'twofootnotes' html = render(problem_path, diag) assert find_footnotes(html) is not None def test_footnotes_href(diag): # We use allowlist-based id values for footnotes. Ensure they have not changed - problem_path = Path(__file__).parent / 'problems' / 'footnote' + problem_path = datadir() / 'problems' / 'footnote' html = render(problem_path, diag) assert 'fn1' in html and 'fnref1' in html @@ -49,10 +48,10 @@ def test_footnotes_href(diag): def test_invalid_image_throws(diag): # If images can point to img that doesn't exist, it's arbitrary web request for problem in ('imgrequest', 'imgrequest2'): - problem_path = Path(__file__).parent / 'problems' / problem + problem_path = datadir() / 'problems' / problem with pytest.raises(ValueError): render(problem_path, diag) # Pandoc won't make a web request for imgrequest2 with pytest.raises(ValueError): - renderpdf(Path(__file__).parent / 'problems' / 'imgrequest') + renderpdf(datadir() / 'problems' / 'imgrequest') diff --git a/tests/test_xss.py b/tests/test_xss.py index 597ab468..821a91b8 100644 --- a/tests/test_xss.py +++ b/tests/test_xss.py @@ -1,8 +1,8 @@ import os import tempfile -from pathlib import Path from problemtools import problem2html, problem2pdf +from tests.conftest import datadir def render(problem_path, diag): @@ -24,18 +24,18 @@ def renderpdf(problem_path): def test_no_xss_statement(diag): - problem_path = Path(__file__).parent / 'problems' / 'statementxss' + problem_path = datadir() / 'problems' / 'statementxss' html = render(problem_path, diag) assert 'alert' not in html def test_no_xss_problemname(diag): - problem_path = Path(__file__).parent / 'problems' / 'problemnamexss' + problem_path = datadir() / 'problems' / 'problemnamexss' html = render(problem_path, diag) assert '