Skip to content
File

Blob: scripts/test_build_firmware.py

python119 lines
1"""Reject unsupported resolved SDK settings before compiling a radio image."""
2import importlib.util
3import json
4from pathlib import Path
5import tempfile
6from types import SimpleNamespace
7import unittest
8from unittest.mock import patch
9 
10spec = importlib.util.spec_from_file_location(
11 "build_firmware", Path(__file__).with_name("build_firmware.py")
12)
13build_firmware = importlib.util.module_from_spec(spec)
14spec.loader.exec_module(build_firmware)
15 
16SUPPORTED_CONFIGURATION = {
17 "COMPILER_OPTIMIZATION_PERF": True,
18 "COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE": True,
19 "SPIRAM_MALLOC_ALWAYSINTERNAL": 1024,
20 "MBEDTLS_HARDWARE_AES": True,
21 "MBEDTLS_HARDWARE_SHA": True,
22 "MBEDTLS_HARDWARE_MPI": True,
23}
24 
25 
26class BuildConfigurationTests(unittest.TestCase):
27 def test_firmware_uses_the_canonical_device_token(self):
28 with tempfile.TemporaryDirectory() as directory:
29 root = Path(directory)
30 build = root / "build"
31 (build / "config").mkdir(parents=True)
32 (build / "config/sdkconfig.json").write_text(json.dumps(SUPPORTED_CONFIGURATION))
33 (root / ".credential.env").write_text(
34 'WIFI_SSID="synthetic network"\nWIFI_PASSWORD="synthetic password"\n'
35 f'DEVICE_TOKEN="{"c" * 43}"\n'
36 )
37 (root / "worker").mkdir()
38 (root / "worker/.dev.vars").write_text(f'DEVICE_TOKEN="{"s" * 43}"\n')
39 with (
40 patch.object(build_firmware, "ROOT", root),
41 patch.object(build_firmware, "build_dir", return_value=build),
42 patch.object(build_firmware, "configured_sdk_root", return_value=root / "sdk"),
43 patch.object(build_firmware, "verify_checkout"),
44 patch.object(build_firmware, "idf_environment", return_value=("python3", {})),
45 patch.object(build_firmware.subprocess, "check_output", return_value=f"rustc ({build_firmware.RUST_RELEASE})"),
46 patch.object(build_firmware.subprocess, "run", return_value=SimpleNamespace(returncode=0)),
47 patch("sys.argv", ["build_firmware.py", "--signaling-url", "https://radio.example.com"]),
48 ):
49 with self.assertRaises(SystemExit) as result:
50 build_firmware.main()
51 self.assertEqual(result.exception.code, 0)
52 header = (build / "private/signaling_private.h").read_text()
53 self.assertIn('"' + "\\x63" * 43 + '"', header)
54 self.assertNotIn("\\x73" * 43, header)
55 
56 def check_configuration(self, config):
57 with tempfile.TemporaryDirectory() as directory:
58 build = Path(directory)
59 (build / "config").mkdir()
60 (build / "config/sdkconfig.json").write_text(json.dumps(config))
61 build_firmware.require_supported_sdk_configuration(build)
62 
63 def test_supported_s3_configuration_is_accepted(self):
64 self.check_configuration(SUPPORTED_CONFIGURATION)
65 
66 def test_stale_debug_configuration_is_rejected(self):
67 config = {**SUPPORTED_CONFIGURATION, "COMPILER_OPTIMIZATION_PERF": False}
68 with self.assertRaisesRegex(SystemExit, "-O2"):
69 self.check_configuration(config)
70 
71 def test_disabled_assertions_are_rejected(self):
72 config = {
73 **SUPPORTED_CONFIGURATION,
74 "COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE": False,
75 }
76 with self.assertRaisesRegex(SystemExit, "assertions"):
77 self.check_configuration(config)
78 
79 def test_stale_psram_threshold_is_rejected(self):
80 config = {**SUPPORTED_CONFIGURATION, "SPIRAM_MALLOC_ALWAYSINTERNAL": 16384}
81 with self.assertRaisesRegex(SystemExit, "1 KiB PSRAM"):
82 self.check_configuration(config)
83 
84 def test_disabled_hardware_crypto_is_rejected(self):
85 for setting in (
86 "MBEDTLS_HARDWARE_AES",
87 "MBEDTLS_HARDWARE_SHA",
88 "MBEDTLS_HARDWARE_MPI",
89 ):
90 with self.subTest(setting=setting):
91 config = {**SUPPORTED_CONFIGURATION, setting: False}
92 with self.assertRaisesRegex(SystemExit, "hardware AES/SHA/MPI"):
93 self.check_configuration(config)
94 
95 def test_missing_resolved_configuration_has_an_actionable_error(self):
96 with tempfile.TemporaryDirectory() as directory:
97 with self.assertRaisesRegex(SystemExit, "did not produce a readable"):
98 build_firmware.require_supported_sdk_configuration(Path(directory))
99 
100 def test_malformed_resolved_configuration_has_an_actionable_error(self):
101 with tempfile.TemporaryDirectory() as directory:
102 build = Path(directory)
103 (build / "config").mkdir()
104 (build / "config/sdkconfig.json").write_text("{")
105 with self.assertRaisesRegex(SystemExit, "invalid resolved"):
106 build_firmware.require_supported_sdk_configuration(build)
107 
108 def test_resolved_configuration_must_be_an_object(self):
109 with tempfile.TemporaryDirectory() as directory:
110 build = Path(directory)
111 (build / "config").mkdir()
112 (build / "config/sdkconfig.json").write_text("[]")
113 with self.assertRaisesRegex(SystemExit, "JSON object"):
114 build_firmware.require_supported_sdk_configuration(build)
115 
116 
117if __name__ == "__main__":
118 unittest.main()