From f532c455cd7210511075c6eaf7d0a265a43ebb65 Mon Sep 17 00:00:00 2001 From: Ahtisham Shahid Date: Thu, 6 Aug 2026 17:41:01 +0500 Subject: [PATCH] fix: publish shared capa fixtures from a testing package Downstream test suites import capa fixtures from this distribution, but no 1.0.x wheel contains them: `[tool.setuptools.packages.find]`'s `*tests*` exclusion drops `xblocks_contrib.problem.capa.tests` entirely. MANIFEST.in tries to hold them back -- # We do this because openedx-platform imports these specific test helpers. include xblocks_contrib/problem/capa/tests/test_util.py ... -- but MANIFEST.in only governs the sdist, so the intent never reached the wheel. openedx-platform consequently fails to collect ~17 of its own test modules against any 1.0.x release, and stays pinned to 0.17.0. Move the two fixtures consumers actually import into a `capa/testing/` package. It is deliberately not under a `tests/` directory, so the existing exclusion cannot match it -- no packaging configuration changes at all: * `capa/testing/response_xml_factory.py` -- moved verbatim; a pure factory module with no tests in it. * `capa/testing/codejail.py` -- `UseUnsafeCodejail`, lifted out of `tests/test_util.py`. That module mixes a real test case (`UtilTest`) with a fixture only consumers use, so it is split rather than moved; `UtilTest` stays behind. `capa/tests/helpers.py` stays internal: despite MANIFEST.in listing it, nothing outside this repo imports it, and its `load_fixture` resolves paths relative to `tests/test_files`. The now-stale MANIFEST.in block is removed -- one of the three files it names no longer exists, and none of them need re-including. Consumers change import paths as follows: from xblocks_contrib.problem.capa.tests.response_xml_factory import X -> from xblocks_contrib.problem.capa.testing.response_xml_factory import X from xblocks_contrib.problem.capa.tests.test_util import UseUnsafeCodejail -> from xblocks_contrib.problem.capa.testing.codejail import UseUnsafeCodejail No released 1.0.x wheel exposes the old paths, so nothing installable can break. --- MANIFEST.in | 7 ----- .../capa/safe_exec/tests/test_safe_exec.py | 2 +- .../problem/capa/testing/__init__.py | 7 +++++ .../problem/capa/testing/codejail.py | 31 +++++++++++++++++++ .../response_xml_factory.py | 0 .../problem/capa/tests/test_capa_problem.py | 2 +- .../problem/capa/tests/test_html_render.py | 4 +-- .../problem/capa/tests/test_responsetypes.py | 6 ++-- .../problem/capa/tests/test_util.py | 29 ----------------- .../problem/tests/test_capa_block.py | 2 +- 10 files changed, 46 insertions(+), 44 deletions(-) create mode 100644 xblocks_contrib/problem/capa/testing/__init__.py create mode 100644 xblocks_contrib/problem/capa/testing/codejail.py rename xblocks_contrib/problem/capa/{tests => testing}/response_xml_factory.py (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 2fde07c7..90fc938c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -22,10 +22,3 @@ prune */spec prune xblocks_contrib/problem/capa/static/js/capa/spec prune xblocks_contrib/problem/capa/static/js/capa/fixtures prune xblocks_contrib/problem/capa/tests/test_files - -# --- TEST UTILITY INCLUSIONS --- -# Re-include specific test files that are excluded above. -# We do this because openedx-platform imports these specific test helpers. -include xblocks_contrib/problem/capa/tests/test_util.py -include xblocks_contrib/problem/capa/tests/helpers.py -include xblocks_contrib/problem/capa/tests/response_xml_factory.py diff --git a/xblocks_contrib/problem/capa/safe_exec/tests/test_safe_exec.py b/xblocks_contrib/problem/capa/safe_exec/tests/test_safe_exec.py index 90f8420d..bb37b0db 100644 --- a/xblocks_contrib/problem/capa/safe_exec/tests/test_safe_exec.py +++ b/xblocks_contrib/problem/capa/safe_exec/tests/test_safe_exec.py @@ -24,7 +24,7 @@ is_codejail_rest_service_enabled, ) from xblocks_contrib.problem.capa.safe_exec.safe_exec import emsg_normalizers, normalize_error_message -from xblocks_contrib.problem.capa.tests.test_util import UseUnsafeCodejail +from xblocks_contrib.problem.capa.testing.codejail import UseUnsafeCodejail @UseUnsafeCodejail() diff --git a/xblocks_contrib/problem/capa/testing/__init__.py b/xblocks_contrib/problem/capa/testing/__init__.py new file mode 100644 index 00000000..d6754ebc --- /dev/null +++ b/xblocks_contrib/problem/capa/testing/__init__.py @@ -0,0 +1,7 @@ +""" +Reusable capa testing utilities. + +Unlike ``xblocks_contrib.problem.capa.tests``, this package ships in the +published distribution: it holds the fixtures that downstream test suites -- +notably openedx-platform's -- import. Keep actual tests out of it. +""" diff --git a/xblocks_contrib/problem/capa/testing/codejail.py b/xblocks_contrib/problem/capa/testing/codejail.py new file mode 100644 index 00000000..12e29bba --- /dev/null +++ b/xblocks_contrib/problem/capa/testing/codejail.py @@ -0,0 +1,31 @@ +"""Codejail controls for tests that execute capa problem code.""" + +import codejail.safe_exec +from django.test.utils import TestContextDecorator + + +class UseUnsafeCodejail(TestContextDecorator): + """ + Tell codejail to run in unsafe mode for the scope of the decorator. + Use this as a decorator on Django TestCase classes or methods. + + This is needed because codejail has significant OS-level setup requirements + which we don't even attempt to fulfill for unit testing purposes. Running + tests in unsafe mode (that is, running code executions in-process, with no + sandboxing) is only safe because we control the contents of the unit tests. + It's not a perfect replica of how safe mode operates but it's generally good + enough for testing the integration and overall behavior. + """ + + def __init__(self): + self.old_be_unsafe = None + super().__init__() + + def enable(self): + """Enable unsafe mode for codejail within the test scope.""" + self.old_be_unsafe = codejail.safe_exec.ALWAYS_BE_UNSAFE + codejail.safe_exec.ALWAYS_BE_UNSAFE = True + + def disable(self): + """Restore the previous codejail unsafe mode state.""" + codejail.safe_exec.ALWAYS_BE_UNSAFE = self.old_be_unsafe diff --git a/xblocks_contrib/problem/capa/tests/response_xml_factory.py b/xblocks_contrib/problem/capa/testing/response_xml_factory.py similarity index 100% rename from xblocks_contrib/problem/capa/tests/response_xml_factory.py rename to xblocks_contrib/problem/capa/testing/response_xml_factory.py diff --git a/xblocks_contrib/problem/capa/tests/test_capa_problem.py b/xblocks_contrib/problem/capa/tests/test_capa_problem.py index 4b449c7f..471da6b7 100644 --- a/xblocks_contrib/problem/capa/tests/test_capa_problem.py +++ b/xblocks_contrib/problem/capa/tests/test_capa_problem.py @@ -13,8 +13,8 @@ from xblocks_contrib.problem.capa.correctmap import CorrectMap from xblocks_contrib.problem.capa.responsetypes import LoncapaProblemError +from xblocks_contrib.problem.capa.testing.codejail import UseUnsafeCodejail from xblocks_contrib.problem.capa.tests.helpers import new_loncapa_problem -from xblocks_contrib.problem.capa.tests.test_util import UseUnsafeCodejail from xblocks_contrib.problem.markup import HTML diff --git a/xblocks_contrib/problem/capa/tests/test_html_render.py b/xblocks_contrib/problem/capa/tests/test_html_render.py index a94a1c45..df3b72fd 100644 --- a/xblocks_contrib/problem/capa/tests/test_html_render.py +++ b/xblocks_contrib/problem/capa/tests/test_html_render.py @@ -10,11 +10,11 @@ import ddt from lxml import etree +from xblocks_contrib.problem.capa.testing.codejail import UseUnsafeCodejail from xblocks_contrib.problem.capa.tests.helpers import mock_capa_system, new_loncapa_problem -from xblocks_contrib.problem.capa.tests.test_util import UseUnsafeCodejail from xblocks_contrib.problem.markup import HTML -from .response_xml_factory import CustomResponseXMLFactory, StringResponseXMLFactory +from ..testing.response_xml_factory import CustomResponseXMLFactory, StringResponseXMLFactory @ddt.ddt diff --git a/xblocks_contrib/problem/capa/tests/test_responsetypes.py b/xblocks_contrib/problem/capa/tests/test_responsetypes.py index ebe56052..5f0ee1cb 100644 --- a/xblocks_contrib/problem/capa/tests/test_responsetypes.py +++ b/xblocks_contrib/problem/capa/tests/test_responsetypes.py @@ -21,8 +21,8 @@ from xblocks_contrib.problem.capa.correctmap import CorrectMap from xblocks_contrib.problem.capa.responsetypes import LoncapaProblemError, ResponseError, StudentInputError -from xblocks_contrib.problem.capa.tests.helpers import load_fixture, mock_capa_system, new_loncapa_problem -from xblocks_contrib.problem.capa.tests.response_xml_factory import ( +from xblocks_contrib.problem.capa.testing.codejail import UseUnsafeCodejail +from xblocks_contrib.problem.capa.testing.response_xml_factory import ( AnnotationResponseXMLFactory, ChoiceResponseXMLFactory, ChoiceTextResponseXMLFactory, @@ -38,7 +38,7 @@ SymbolicResponseXMLFactory, TrueFalseResponseXMLFactory, ) -from xblocks_contrib.problem.capa.tests.test_util import UseUnsafeCodejail +from xblocks_contrib.problem.capa.tests.helpers import load_fixture, mock_capa_system, new_loncapa_problem from xblocks_contrib.problem.capa.util import convert_files_to_filenames from xblocks_contrib.problem.capa.xqueue_interface import DATEFORMAT diff --git a/xblocks_contrib/problem/capa/tests/test_util.py b/xblocks_contrib/problem/capa/tests/test_util.py index 683a0e0a..6e9ee901 100644 --- a/xblocks_contrib/problem/capa/tests/test_util.py +++ b/xblocks_contrib/problem/capa/tests/test_util.py @@ -4,9 +4,7 @@ import unittest -import codejail.safe_exec import ddt -from django.test.utils import TestContextDecorator from lxml import etree from xblocks_contrib.problem.capa.tests.helpers import mock_capa_system @@ -167,30 +165,3 @@ def test_contextualize_text_with_non_ascii_context(self): expected_text = "$あなたあなたあなたあなた あなたhi" contextual_text = contextualize_text(text, context) assert expected_text == contextual_text - - -class UseUnsafeCodejail(TestContextDecorator): - """ - Tell codejail to run in unsafe mode for the scope of the decorator. - Use this as a decorator on Django TestCase classes or methods. - - This is needed because codejail has significant OS-level setup requirements - which we don't even attempt to fulfill for unit testing purposes. Running - tests in unsafe mode (that is, running code executions in-process, with no - sandboxing) is only safe because we control the contents of the unit tests. - It's not a perfect replica of how safe mode operates but it's generally good - enough for testing the integration and overall behavior. - """ - - def __init__(self): - self.old_be_unsafe = None - super().__init__() - - def enable(self): - """Enable unsafe mode for codejail within the test scope.""" - self.old_be_unsafe = codejail.safe_exec.ALWAYS_BE_UNSAFE - codejail.safe_exec.ALWAYS_BE_UNSAFE = True - - def disable(self): - """Restore the previous codejail unsafe mode state.""" - codejail.safe_exec.ALWAYS_BE_UNSAFE = self.old_be_unsafe diff --git a/xblocks_contrib/problem/tests/test_capa_block.py b/xblocks_contrib/problem/tests/test_capa_block.py index 1af4f92a..0220683d 100644 --- a/xblocks_contrib/problem/tests/test_capa_block.py +++ b/xblocks_contrib/problem/tests/test_capa_block.py @@ -31,7 +31,7 @@ from xblocks_contrib.problem.capa import responsetypes from xblocks_contrib.problem.capa.correctmap import CorrectMap from xblocks_contrib.problem.capa.responsetypes import LoncapaProblemError, ResponseError, StudentInputError -from xblocks_contrib.problem.capa.tests.test_util import UseUnsafeCodejail +from xblocks_contrib.problem.capa.testing.codejail import UseUnsafeCodejail from xblocks_contrib.problem.capa.xqueue_interface import XQueueInterface from xblocks_contrib.problem.capa_block import ComplexEncoder, ProblemBlock from xblocks_contrib.problem.tests import DATA_DIR