|
3 | 3 | from unittest.mock import MagicMock |
4 | 4 |
|
5 | 5 | import yaml |
| 6 | +from galaxy.jobs import JobDestination |
6 | 7 | from galaxy.jobs.mapper import JobMappingException |
7 | 8 |
|
8 | 9 | from tpv.commands.dryrunner import TPVDryRunner |
| 10 | +from tpv.commands.test import mock_galaxy |
9 | 11 | from tpv.core.entities import SchedulingTags |
10 | 12 | from tpv.core.explain import ExplainCollector, ExplainPhase |
| 13 | +from tpv.rules import gateway |
11 | 14 |
|
12 | 15 |
|
13 | 16 | class TestExplainCollectorUnit(unittest.TestCase): |
@@ -496,3 +499,59 @@ def test_from_params_without_tool_id_sets_tool_none(self): |
496 | 499 | tpv_confs=[self._fixture_path("mapping-basic.yml")], |
497 | 500 | ) |
498 | 501 | self.assertIsNone(runner.tool) |
| 502 | + |
| 503 | + |
| 504 | + |
| 505 | +class TestGatewayExplainOnFailure(unittest.TestCase): |
| 506 | + """Tests for the tpv_explain_on_failure gateway config option.""" |
| 507 | + |
| 508 | + @staticmethod |
| 509 | + def _fixture_path(name): |
| 510 | + return os.path.join(os.path.dirname(__file__), f"fixtures/{name}") |
| 511 | + |
| 512 | + def _call_gateway(self, tool_id, referrer=None, explain_collector=None): |
| 513 | + gateway.ACTIVE_DESTINATION_MAPPERS = {} |
| 514 | + app = mock_galaxy.App(job_conf=self._fixture_path("job_conf.yml")) |
| 515 | + tool = mock_galaxy.Tool(tool_id) |
| 516 | + job = mock_galaxy.Job() |
| 517 | + user = mock_galaxy.User("gargravarr", "fairycake@vortex.org") |
| 518 | + return gateway.map_tool_to_destination( |
| 519 | + app, job, tool, user, |
| 520 | + referrer=referrer, |
| 521 | + tpv_config_files=[self._fixture_path("mapping-basic.yml")], |
| 522 | + explain_collector=explain_collector, |
| 523 | + ) |
| 524 | + |
| 525 | + def test_no_log_when_param_not_set(self): |
| 526 | + """No warning is logged on failure when tpv_explain_on_failure is not configured.""" |
| 527 | + with self.assertNoLogs("tpv.rules.gateway", level="WARNING"): |
| 528 | + with self.assertRaises(JobMappingException): |
| 529 | + self._call_gateway("unschedulable_tool") |
| 530 | + |
| 531 | + def test_logs_trace_on_mapping_failure(self): |
| 532 | + """A WARNING containing the scheduling trace is logged when tpv_explain_on_failure=true and mapping fails.""" |
| 533 | + referrer = JobDestination(id="tpv_dispatcher", params={"tpv_explain_on_failure": True}) |
| 534 | + with self.assertLogs("tpv.rules.gateway", level="WARNING") as cm: |
| 535 | + with self.assertRaises(JobMappingException): |
| 536 | + self._call_gateway("unschedulable_tool", referrer=referrer) |
| 537 | + self.assertEqual(len(cm.records), 1) |
| 538 | + trace = cm.records[0].getMessage() |
| 539 | + self.assertIn("TPV SCHEDULING DECISION TRACE", trace) |
| 540 | + self.assertIn("REJECTED", trace) |
| 541 | + self.assertIn("tag mismatch", trace) |
| 542 | + self.assertIn("No destinations", trace) |
| 543 | + |
| 544 | + def test_no_log_on_successful_mapping(self): |
| 545 | + """No warning is logged when tpv_explain_on_failure=true but mapping succeeds.""" |
| 546 | + referrer = JobDestination(id="tpv_dispatcher", params={"tpv_explain_on_failure": True}) |
| 547 | + with self.assertNoLogs("tpv.rules.gateway", level="WARNING"): |
| 548 | + destination = self._call_gateway("bwa", referrer=referrer) |
| 549 | + self.assertIsNotNone(destination) |
| 550 | + |
| 551 | + def test_explicit_collector_not_logged_by_gateway(self): |
| 552 | + """When an explicit explain_collector is passed (dry-run path), the gateway does not log on failure.""" |
| 553 | + collector = ExplainCollector() |
| 554 | + with self.assertNoLogs("tpv.rules.gateway", level="WARNING"): |
| 555 | + with self.assertRaises(JobMappingException): |
| 556 | + self._call_gateway("unschedulable_tool", explain_collector=collector) |
| 557 | + self.assertTrue(len(collector.steps) > 0) |
0 commit comments