"""The `script` tool runs a checked-in program in one declared runtime: control (the default), java or node. NOT YET RUN: written with the integration of the script-toolchain patch while test execution was suspended. """ import tempfile import unittest from pathlib import Path from typing import Any, cast from platform_ci.build_inputs import compute_build_inputs from platform_ci.build_tools import BUILD_TOOLS from platform_ci.caching import cache_family, cache_unsupported_reason from platform_ci.configuration import ConfigurationError, parse_project_configuration from platform_ci.execution import run_target_commands from platform_ci.plan import Target from tests.support import JAVA_IMAGE, NODE_IMAGE, SOURCE_TREE, event, plan_for def script_configuration(toolchains: dict[str, str], **component: object) -> dict[str, object]: """A one-component `script` configuration.""" body: dict[str, object] = {"toolchains": toolchains, "build": {"tool": "script", "commands": [["./scripts/ci/check.sh", "argument"]]}, **component} return {"schema": 1, "components": {"checks": body}} def planned(toolchains: dict[str, str], **component: object) -> Target: """Plan one script component against the test catalog and return its target.""" return plan_for(parse_project_configuration(script_configuration(toolchains, **component)), event())["targets"][0] class ScriptToolchainConfigurationTest(unittest.TestCase): """Exactly one declared toolchain of the closed set is accepted.""" def test_java_and_node_select_their_catalog_images_and_control_stays_the_default(self) -> None: java = planned({"java": "21"}) self.assertEqual((java["toolchain"], java["image"], java["commands"]), ("java", JAVA_IMAGE, [["./scripts/ci/check.sh", "argument"]])) self.assertIn("Java 21", java["display"]) node = planned({"node": "22.20.0"}) self.assertEqual((node["toolchain"], node["image"]), ("node", NODE_IMAGE)) self.assertEqual(BUILD_TOOLS["script"].toolchain, "control") def test_zero_two_and_unknown_toolchains_are_rejected(self) -> None: for name, toolchains in {"none": {}, "two": {"java": "21", "node": "22.20.0"}, "unknown": {"go": "1"}}.items(): with self.subTest(name), self.assertRaises(ConfigurationError): parse_project_configuration(script_configuration(toolchains)) def test_other_tools_still_take_only_their_own_toolchain(self) -> None: configuration = {"schema": 1, "components": {"library": {"toolchains": {"node": "22.20.0"}, "build": {"tool": "maven", "arguments": ["verify"]}}}} with self.assertRaises(ConfigurationError): parse_project_configuration(configuration) def test_the_docker_profile_and_a_program_outside_the_component_are_handled_like_other_tools(self) -> None: self.assertEqual(parse_project_configuration(script_configuration({"java": "21"}, requires=["docker"])).components[0].requires, ("docker",)) bad = script_configuration({"java": "21"}) cast("dict[str, Any]", cast("dict[str, Any]", bad["components"])["checks"]["build"])["commands"] = [["../escape.sh"]] with self.assertRaises(ConfigurationError): parse_project_configuration(bad) class ScriptToolchainFingerprintAndCacheTest(unittest.TestCase): """The selected toolchain is part of the build-input record, and dependency caching follows the declared toolchain.""" def test_the_build_input_record_names_the_selected_toolchain(self) -> None: java = compute_build_inputs(planned({"java": "21"}), SOURCE_TREE, "1") node = compute_build_inputs(planned({"node": "22.20.0"}), SOURCE_TREE, "1") self.assertIn("java", java["toolchain"]) self.assertIn("node", node["toolchain"]) self.assertNotEqual(java["config_digest"], node["config_digest"]) self.assertNotIn("control", java["toolchain"]) def test_cache_family_follows_the_toolchain_not_the_script_tool(self) -> None: self.assertEqual((cache_family(planned({"java": "21"})), cache_family(planned({"node": "22.20.0"}))), ("maven", "node")) self.assertIsNone(cache_unsupported_reason(planned({"java": "21"}))) self.assertIn("no dependency cache", cache_unsupported_reason(plan_for(parse_project_configuration(script_configuration({"control": "1"})), event(), _control_catalog())["targets"][0]) or "") def _control_catalog(): """A catalog that also qualifies the control toolchain.""" from platform_ci.runtimes import parse_runtime_catalog # noqa: PLC0415 return parse_runtime_catalog({"schema": 1, "toolchains": {"control": {"1": {"image": JAVA_IMAGE}}}}) class ScriptToolchainExecutionTest(unittest.TestCase): """A java-toolchain script gets the credential-free Maven settings; a node or control script does not.""" def _run(self, toolchains: dict[str, str]) -> Path: with tempfile.TemporaryDirectory() as directory: base = Path(directory) source = base / "src" (source / "scripts" / "ci").mkdir(parents=True) (source / "scripts" / "ci" / "check.sh").write_text("#!/bin/sh\n", encoding="utf-8") (base / "home").mkdir() run_target_commands(planned(toolchains), source, lambda command, working_directory, environment: 0, {"PATH": "/usr/bin", "HOME": str(base / "home")}) return base / "home" / ".m2" / "settings.xml" if (base / "home" / ".m2" / "settings.xml").exists() else base / "missing" def test_maven_settings_only_for_a_java_toolchain(self) -> None: self.assertEqual(self._run({"java": "21"}).name, "settings.xml") self.assertEqual(self._run({"node": "22.20.0"}).name, "missing") if __name__ == "__main__": unittest.main()