Skip to content

Commit ee824fa

Browse files
committed
[red] exercise actual RP2 scrub and offline environment grammar
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent 915aed5 commit ee824fa

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

‎tests/firmware_tests/host/test_rp2_arm_runtime_closure.py‎

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -789,5 +789,144 @@ def test_real_observer_binds_builder_scrub_and_checks_every_recipe(self) -> None
789789
)
790790

791791

792+
class Rp2BuildEnvironmentParserTests(unittest.TestCase):
793+
"""Parse real driver bytes; never execute the shell or a compiler."""
794+
795+
MARKER = (
796+
"# Never allow ambient compiler/make flags to influence the pinned build.\n"
797+
"unset \\\n"
798+
)
799+
END = '\n\nmake -C "$RETAINED_UPSTREAM/mpy-cross"'
800+
SETUP = "# Bind Pico SDK's CMake configure to the verified package"
801+
PACKAGE_VARIABLES = (
802+
"CMAKE_ARGS", "picotool_DIR", "FETCHCONTENT_FULLY_DISCONNECTED",
803+
"FETCHCONTENT_SOURCE_DIR_PICOTOOL", "PICOTOOL_FETCH_FROM_GIT_PATH",
804+
"PICOTOOL_FORCE_FETCH_FROM_GIT", "CMAKE_FIND_USE_PACKAGE_REGISTRY",
805+
"CMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY", "CMAKE_PREFIX_PATH",
806+
)
807+
808+
def setUp(self) -> None:
809+
self.driver = BUILD_RP2.read_text(encoding="utf-8")
810+
811+
def parse(self, source: str, version: str = "0.6.1") -> list[str]:
812+
parser = getattr(RELEASE, "_audit_rp2_build_environment", None)
813+
self.assertTrue(callable(parser), "missing real RP2 environment parser")
814+
return parser(source, firmware_version=version)
815+
816+
def test_checked_in_driver_accepts_one_unset_and_exact_offline_setup(self) -> None:
817+
variables = self.parse(self.driver)
818+
self.assertIn("picotool_DIR", variables)
819+
self.assertEqual(len(variables), len(set(variables)))
820+
for variable in (*RELEASE._RP2_GCC_ENVIRONMENT_OVERRIDES, *self.PACKAGE_VARIABLES):
821+
with self.subTest(variable=variable):
822+
self.assertIn(variable, variables)
823+
824+
def test_actual_observer_reaches_cache_after_current_driver_parse(self) -> None:
825+
class CacheReached(Exception):
826+
pass
827+
828+
original_read = RELEASE._read_regular_file_bytes
829+
830+
def read(path, label):
831+
if label == "RP2 CMake cache":
832+
raise CacheReached
833+
return original_read(path, label)
834+
835+
with tempfile.TemporaryDirectory() as temporary, mock.patch.object(
836+
RELEASE, "_read_regular_file_bytes", side_effect=read
837+
), self.assertRaises(CacheReached):
838+
target = Path(temporary) / "unopened-target"
839+
RELEASE._audit_observe_rp2_arm_runtime_closure(
840+
repo_root=ROOT, target_root=target, cache=target / "CMakeCache.txt",
841+
mappings={}, link_path=target / "link.txt", map_loads=[],
842+
direct_contributors=set(), archive_members=[], compiler_dependency_closure=[],
843+
)
844+
845+
def test_missing_or_duplicate_boundaries_are_rejected(self) -> None:
846+
for marker in (self.MARKER, self.END):
847+
for changed in (self.driver.replace(marker, "", 1),
848+
self.driver.replace(marker, marker + marker, 1)):
849+
with self.subTest(marker=marker, changed=changed), self.assertRaises(RELEASE.ReleaseError):
850+
self.parse(changed)
851+
852+
def test_every_gcc_and_package_override_must_be_scrubbed(self) -> None:
853+
for variable in (*RELEASE._RP2_GCC_ENVIRONMENT_OVERRIDES, *self.PACKAGE_VARIABLES):
854+
line = " " + variable + ("\n" if variable == "CMAKE_PREFIX_PATH" else " \\\n")
855+
self.assertEqual(self.driver.count(line), 1 if variable not in {
856+
"CMAKE_ARGS", "picotool_DIR", "FETCHCONTENT_FULLY_DISCONNECTED",
857+
"PICOTOOL_FORCE_FETCH_FROM_GIT", "CMAKE_FIND_USE_PACKAGE_REGISTRY"
858+
} else 2)
859+
with self.subTest(variable=variable), self.assertRaises(RELEASE.ReleaseError):
860+
self.parse(self.driver.replace(line, "", 1))
861+
862+
def test_duplicate_scrub_name_is_rejected_even_if_coverage_is_complete(self) -> None:
863+
line = " GCC_EXEC_PREFIX \\\n"
864+
with self.assertRaises(RELEASE.ReleaseError):
865+
self.parse(self.driver.replace(line, line + line, 1))
866+
867+
def test_shell_identifier_case_is_preserved_without_shell_syntax(self) -> None:
868+
safe = self.driver.replace(self.MARKER, self.MARKER + " _safe_Mixed9 \\\n", 1)
869+
self.assertIn("_safe_Mixed9", self.parse(safe))
870+
for variable in ("9BAD", "BAD-NAME", "A=1", "$PATH", "A;echo", "$(id)", ""):
871+
with self.subTest(variable=variable), self.assertRaises(RELEASE.ReleaseError):
872+
self.parse(self.driver.replace(" CFLAGS_EXTRA \\\n", " " + variable + " \\\n", 1))
873+
874+
def test_continuations_indentation_and_terminal_line_are_exact(self) -> None:
875+
cases = (
876+
self.driver.replace(" CFLAGS_EXTRA \\\n", " CFLAGS_EXTRA\n", 1),
877+
self.driver.replace(" CFLAGS_EXTRA \\\n", " CFLAGS_EXTRA \\\n", 1),
878+
self.driver.replace(" CFLAGS_EXTRA \\\n", "\tCFLAGS_EXTRA \\\n", 1),
879+
self.driver.replace(" CMAKE_PREFIX_PATH\n", " CMAKE_PREFIX_PATH \\\n", 1),
880+
self.driver.replace(" CFLAGS_EXTRA \\\n", " CFLAGS_EXTRA \\ \n", 1),
881+
)
882+
for changed in cases:
883+
with self.subTest(changed=changed), self.assertRaises(RELEASE.ReleaseError):
884+
self.parse(changed)
885+
886+
def test_unknown_intervening_commands_and_reassignments_are_rejected(self) -> None:
887+
for command in ('GCC_EXEC_PREFIX=/tmp/other', 'export CFLAGS=-fplugin=bad',
888+
'echo injected', 'unset CMAKE_ARGS', ': "$(id)"'):
889+
for changed in (
890+
self.driver.replace(self.SETUP, command + "\n" + self.SETUP, 1),
891+
self.driver.replace(self.END, "\n" + command + self.END, 1),
892+
):
893+
with self.subTest(command=command), self.assertRaises(RELEASE.ReleaseError):
894+
self.parse(changed)
895+
896+
def test_offline_values_and_package_binding_are_exact(self) -> None:
897+
for old, new in (
898+
('-Dpicotool_DIR=$PICOTOOL_PACKAGE_DIR', '-Dpicotool_DIR=/tmp/other'),
899+
('-DFETCHCONTENT_FULLY_DISCONNECTED:BOOL=ON', '-DFETCHCONTENT_FULLY_DISCONNECTED=ON'),
900+
('-DFETCHCONTENT_FULLY_DISCONNECTED:BOOL=ON', '-DFETCHCONTENT_FULLY_DISCONNECTED:BOOL=OFF'),
901+
('-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF', '-DCMAKE_FIND_USE_PACKAGE_REGISTRY=ON'),
902+
('-DCMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY=OFF', '-DCMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY=ON'),
903+
('-DPICOTOOL_FORCE_FETCH_FROM_GIT=OFF', '-DPICOTOOL_FORCE_FETCH_FROM_GIT=ON'),
904+
('picotool_DIR="$PICOTOOL_PACKAGE_DIR"', 'picotool_DIR="$(echo bad)"'),
905+
('FETCHCONTENT_FULLY_DISCONNECTED=ON', 'FETCHCONTENT_FULLY_DISCONNECTED=OFF'),
906+
):
907+
self.assertIn(old, self.driver)
908+
with self.subTest(old=old), self.assertRaises(RELEASE.ReleaseError):
909+
self.parse(self.driver.replace(old, new, 1))
910+
911+
def test_offline_export_is_not_optional_or_extensible(self) -> None:
912+
start = self.driver.index("\nexport \\\n", self.driver.index(self.SETUP))
913+
end = self.driver.index(self.END, start)
914+
export = self.driver[start:end]
915+
for changed_export in ("", export.replace(" picotool_DIR \\\n", "", 1),
916+
export.replace("export \\\n", "export \\\n GCC_EXEC_PREFIX \\\n", 1)):
917+
with self.subTest(export=changed_export), self.assertRaises(RELEASE.ReleaseError):
918+
self.parse(self.driver[:start] + changed_export + self.driver[end:])
919+
920+
def test_historical_scrub_only_form_is_version_selected(self) -> None:
921+
start = self.driver.index("\n\n" + self.SETUP)
922+
end = self.driver.index(self.END, start)
923+
historical = self.driver[:start] + self.driver[end:]
924+
self.assertIn("GCC_EXEC_PREFIX", self.parse(historical, "0.6.0"))
925+
with self.assertRaises(RELEASE.ReleaseError):
926+
self.parse(historical, "0.6.1")
927+
with self.assertRaises(RELEASE.ReleaseError):
928+
self.parse(self.driver, "0.6.0")
929+
930+
792931
if __name__ == "__main__":
793932
unittest.main(verbosity=2)

0 commit comments

Comments
 (0)