2021-11-23 08:25:09 +00:00
|
|
|
import os
|
|
|
|
import tempfile
|
2022-02-07 17:11:43 +00:00
|
|
|
import typing as t
|
|
|
|
import unittest
|
|
|
|
import unittest.result
|
2021-11-23 08:25:09 +00:00
|
|
|
|
2022-02-07 17:11:43 +00:00
|
|
|
from tutor import hooks
|
2022-10-19 15:46:31 +00:00
|
|
|
from tutor.commands.context import BaseTaskContext
|
|
|
|
from tutor.tasks import BaseTaskRunner
|
2021-11-23 08:25:09 +00:00
|
|
|
from tutor.types import Config
|
|
|
|
|
|
|
|
|
2022-10-19 15:46:31 +00:00
|
|
|
class TestTaskRunner(BaseTaskRunner):
|
2022-02-07 17:11:43 +00:00
|
|
|
"""
|
|
|
|
Mock job runner for unit testing.
|
2021-11-23 08:25:09 +00:00
|
|
|
|
2022-02-07 17:11:43 +00:00
|
|
|
This runner does nothing except print the service name and command,
|
|
|
|
separated by dashes.
|
|
|
|
"""
|
2021-11-23 08:25:09 +00:00
|
|
|
|
2022-10-19 15:46:31 +00:00
|
|
|
def run_task(self, service: str, command: str) -> int:
|
2022-02-07 17:11:43 +00:00
|
|
|
print(os.linesep.join([f"Service: {service}", "-----", command, "----- "]))
|
2021-11-23 08:25:09 +00:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
def temporary_root() -> "tempfile.TemporaryDirectory[str]":
|
|
|
|
"""
|
|
|
|
Context manager to handle temporary test root.
|
|
|
|
|
|
|
|
This function can be used as follows:
|
|
|
|
|
|
|
|
with temporary_root() as root:
|
|
|
|
config = tutor_config.load_full(root)
|
|
|
|
...
|
|
|
|
"""
|
|
|
|
return tempfile.TemporaryDirectory(prefix="tutor-test-root-")
|
|
|
|
|
|
|
|
|
2022-10-19 15:46:31 +00:00
|
|
|
class TestContext(BaseTaskContext):
|
2021-11-23 08:25:09 +00:00
|
|
|
"""
|
|
|
|
Click context that will use only test job runners.
|
|
|
|
"""
|
|
|
|
|
2022-10-19 15:46:31 +00:00
|
|
|
def job_runner(self, config: Config) -> TestTaskRunner:
|
|
|
|
return TestTaskRunner(self.root, config)
|
2022-02-07 17:11:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
class PluginsTestCase(unittest.TestCase):
|
|
|
|
"""
|
|
|
|
This test case class clears the hooks created during tests. It also makes sure that
|
|
|
|
we don't accidentally load entrypoint/dict plugins from the user.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def setUp(self) -> None:
|
|
|
|
self.clean()
|
|
|
|
self.addCleanup(self.clean)
|
|
|
|
super().setUp()
|
|
|
|
|
|
|
|
def clean(self) -> None:
|
|
|
|
# We clear hooks created in some contexts, such that user plugins are never loaded.
|
|
|
|
for context in [
|
|
|
|
hooks.Contexts.PLUGINS.name,
|
|
|
|
hooks.Contexts.PLUGINS_V0_ENTRYPOINT.name,
|
|
|
|
hooks.Contexts.PLUGINS_V0_YAML.name,
|
|
|
|
"unittests",
|
|
|
|
]:
|
|
|
|
hooks.filters.clear_all(context=context)
|
|
|
|
hooks.actions.clear_all(context=context)
|
|
|
|
|
|
|
|
def run(
|
|
|
|
self, result: t.Optional[unittest.result.TestResult] = None
|
|
|
|
) -> t.Optional[unittest.result.TestResult]:
|
|
|
|
"""
|
|
|
|
Run all actions and filters with a test context, such that they can be cleared
|
|
|
|
from one run to the next.
|
|
|
|
"""
|
|
|
|
with hooks.contexts.enter("unittests"):
|
|
|
|
return super().run(result=result)
|