Merge remote-tracking branch 'origin/keymindCascade' into keymindCascade

This commit is contained in:
mikecarper committed 2026-10-27 14:11:07 -07:00
commit eab29a12cd
15 files changed
+2067 -166

No files matched your search

+72 -11
View File
@@ -453,8 +453,8 @@ int main() {
sensor = ram.requirements("NRF52_PLATFORM", {}, "t114_sensor")
self.assertEqual(sensor["components"]["client_table"], 32 * 320 + 16)
def test_g3_psram_tables_reserve_full_internal_fallback_without_cutting_other_owners(self):
flags = {"STATION_G3_ESP32": 1, "BOARD_HAS_PSRAM": 1,
def test_s3_psram_tables_reserve_full_internal_fallback_without_cutting_other_owners(self):
flags = {"CONFIG_IDF_TARGET_ESP32S3": 1, "BOARD_HAS_PSRAM": 1,
"MESH_CLIENT_REPEATER_ONLY": 1, "WITH_MQTT_BRIDGE": 1,
"DISPLAY_CLASS": "SH1106Display", "ADMIN_PASSWORD": "test",
"ENABLE_OTA": 1, "WITH_UDP_BRIDGE": 1, "WITH_RS232_BRIDGE": "Serial2"}
@@ -475,11 +475,11 @@ int main() {
self.assertEqual(ram.requirements("ESP32_PLATFORM", {
**flags, "MESH_REPEATER_PSRAM_TABLES": 1}, target), selected)
def test_g3_psram_table_defaults_follow_board_psram_platform_and_source_role_gates(self):
flags = {"STATION_G3_ESP32": 1, "BOARD_HAS_PSRAM": 1,
def test_psram_table_defaults_follow_s3_psram_platform_and_source_role_gates(self):
flags = {"CONFIG_IDF_TARGET_ESP32S3": 1, "BOARD_HAS_PSRAM": 1,
"MESH_CLIENT_REPEATER_ONLY": 1}
cases = [
("ESP32_PLATFORM", {k: v for k, v in flags.items() if k != "STATION_G3_ESP32"},
("ESP32_PLATFORM", {k: v for k, v in flags.items() if k != "CONFIG_IDF_TARGET_ESP32S3"},
"other_s3_repeater"),
("ESP32_PLATFORM", {k: v for k, v in flags.items() if k != "BOARD_HAS_PSRAM"},
"Station_G3_ESP32_repeater"),
@@ -493,6 +493,10 @@ int main() {
("ESP32_PLATFORM", {**flags, "MESH_CLIENT_REPEATER_ONLY": 0},
"Station_G3_ESP32_room_server"),
]
for mcu in ("ESP32", "ESP32S2", "ESP32C3", "ESP32C6"):
cases.append(("ESP32_PLATFORM", {
**flags, "CONFIG_IDF_TARGET_ESP32S3": 0,
"CONFIG_IDF_TARGET_" + mcu: 1}, "other_repeater"))
for platform in ("NRF52_PLATFORM", "RP2040_PLATFORM", "STM32_PLATFORM"):
cases.extend((platform, {**flags, **extra}, "other_repeater")
for extra in ({}, {"MESH_REPEATER_PSRAM_TABLES": 1}))
@@ -508,6 +512,30 @@ int main() {
self.assertIn("repeater_scope_storage_fallback", renamed["components"])
self.assertIn("repeater_advert_storage_fallback", renamed["components"])
def test_psram_repeater_table_defaults_cover_opi_and_qspi_families_without_a_g3_marker(self):
flags = {"BOARD_HAS_PSRAM": 1, "MESH_CLIENT_REPEATER_ONLY": 1,
"CONFIG_IDF_TARGET_ESP32S3": 1, "CONFIG_SPIRAM_USE_MALLOC": 1}
families = (
("LilyGo_TDeck_repeater", {"CONFIG_SPIRAM_MODE_OCT": 1,
"CONFIG_SPIRAM_BOOT_INIT": 1}),
("Station_G2_repeater", {"CONFIG_SPIRAM_MODE_OCT": 1,
"CONFIG_SPIRAM_BOOT_INIT": 1}),
("heltec_v4_repeater", {"CONFIG_SPIRAM_MODE_QUAD": 1}),
("SenseCapIndicator-LoRa-N16R2_repeater", {"CONFIG_SPIRAM_MODE_QUAD": 1}),
)
for target, extra in families:
with self.subTest(target=target):
defines = {**flags, **extra}
self.assertNotIn("STATION_G3_ESP32", defines)
selected = ram.requirements("ESP32_PLATFORM", defines, target)
original = ram.requirements("ESP32_PLATFORM", {
**defines, "MESH_REPEATER_PSRAM_TABLES": 0}, target)
self.assertEqual(selected["components"], {
**original["components"], "repeater_scope_storage_fallback": 18892,
"repeater_advert_storage_fallback": 17936})
self.assertEqual(selected["required_heap_bytes"] - original["required_heap_bytes"],
36828)
def test_explicit_psram_repeater_table_opt_in_and_capacity_overrides_are_fully_budgeted(self):
flags = {"BOARD_HAS_PSRAM": 1, "MESH_CLIENT_REPEATER_ONLY": 1,
"MESH_REPEATER_PSRAM_TABLES": 1}
@@ -537,7 +565,7 @@ int main() {
original["required_heap_bytes"] + scope + advert)
def test_psram_advert_budget_excludes_portable_iram_and_rtc_storage_only(self):
flags = {"STATION_G3_ESP32": 1, "BOARD_HAS_PSRAM": 1,
flags = {"CONFIG_IDF_TARGET_ESP32S3": 1, "BOARD_HAS_PSRAM": 1,
"MESH_CLIENT_REPEATER_ONLY": 1}
target = "Station_G3_ESP32_repeater"
original = ram.requirements("ESP32_PLATFORM", flags, target)
@@ -560,7 +588,7 @@ int main() {
**flags, **disabled_backends}, target), original)
def test_each_psram_table_fallback_can_set_the_contiguous_allocation_bound(self):
flags = {"STATION_G3_ESP32": 1, "BOARD_HAS_PSRAM": 1,
flags = {"CONFIG_IDF_TARGET_ESP32S3": 1, "BOARD_HAS_PSRAM": 1,
"MESH_CLIENT_REPEATER_ONLY": 1, "MAX_CLIENTS": 1,
"FLOOD_PACKET_FILTER_SLOTS": 1, "MAX_RECENT_REPEATERS": 0}
for extra, name in (({}, "repeater_scope_storage_fallback"),
@@ -571,13 +599,14 @@ int main() {
self.assertEqual(policy["required_contiguous_bytes"], policy["components"][name])
self.assertGreater(policy["required_contiguous_bytes"], 16384)
def test_g3_psram_fallback_elf_gate_rejects_old_budget_and_enforces_exact_total(self):
flags = {"STATION_G3_ESP32": 1, "BOARD_HAS_PSRAM": 1,
def test_s3_psram_fallback_elf_gate_rejects_old_budget_and_enforces_exact_total(self):
flags = {"BOARD_HAS_PSRAM": 1,
"MESH_CLIENT_REPEATER_ONLY": 1, "WITH_MQTT_BRIDGE": 1}
target = "Station_G3_ESP32_repeater"
target = "heltec_v4_repeater"
previous = {**flags, "MESH_REPEATER_PSRAM_TABLES": 0}
old = ram.requirements("ESP32_PLATFORM", previous, target)["required_heap_bytes"]
required = ram.requirements("ESP32_PLATFORM", flags, target)["required_heap_bytes"]
required = ram.requirements("ESP32_PLATFORM", {
**flags, "CONFIG_IDF_TARGET_ESP32S3": 1}, target)["required_heap_bytes"]
with tempfile.TemporaryDirectory() as temporary:
path = Path(temporary) / "firmware.elf"
report = Path(temporary) / "firmware.memory.json"
@@ -596,6 +625,38 @@ int main() {
self.assertEqual(result["components"]["repeater_scope_storage_fallback"], 18892)
self.assertEqual(result["components"]["repeater_advert_storage_fallback"], 17936)
def test_psram_table_elf_policy_uses_authoritative_mcu_over_missing_or_stale_sdk_macros(self):
flags = {"BOARD_HAS_PSRAM": 1, "MESH_CLIENT_REPEATER_ONLY": 1}
target = "board_repeater"
with tempfile.TemporaryDirectory() as temporary:
path = Path(temporary) / "firmware.elf"
report = Path(temporary) / "firmware.memory.json"
for mcu, stale_macro in (("esp32s3", None), ("esp32s3", 0),
("esp32", None), ("esp32", 1)):
with self.subTest(mcu=mcu, stale_macro=stale_macro), contextlib.redirect_stdout(io.StringIO()), contextlib.redirect_stderr(io.StringIO()):
defines = dict(flags)
if stale_macro is not None:
defines["CONFIG_IDF_TARGET_ESP32S3"] = stale_macro
expected = ram.requirements("ESP32_PLATFORM", {
**defines, "CONFIG_IDF_TARGET_ESP32S3": int(mcu == "esp32s3"),
"MESH_RAM_DEFAULT_RECENT_REPEATERS": 256 if mcu == "esp32" else 2048,
}, target)
capacity = expected["required_heap_bytes"]
# Classic ESP32 reclaims the second 64 KiB ROM-stack
# region. The S3 excludes it; both exclude the first 16 KiB.
length = capacity + 0x4000 - (0x10000 if mcu == "esp32" else 0)
esp_fixture(path, modern=True, internal_length=length)
status = ram.check_firmware(path, "ESP32_PLATFORM", mcu,
defines, target, report)
result = json.loads(report.read_text())
self.assertEqual(status, 0)
self.assertTrue(result["passed"])
self.assertEqual(result["available_internal_bytes"], capacity)
self.assertEqual(result["required_heap_bytes"], capacity)
self.assertEqual(result["components"], expected["components"])
for name in ("repeater_scope_storage_fallback", "repeater_advert_storage_fallback"):
self.assertEqual(name in result["components"], mcu == "esp32s3")
def test_browser_terminal_reserves_internal_session_and_psram_aware_scrollback(self):
defines = {"ENABLE_USB_INTERFACE": 1, "WIFI_SSID": "", "DISPLAY_CLASS": "SSD1306Display"}
base = ram.requirements("ESP32_PLATFORM", {**defines, "WEBCONFIG_DISABLED": 1}, "v4_companion")
+10 -1
View File
@@ -115,7 +115,6 @@ class FloodAdvertPsramTests(unittest.TestCase):
(cls.work / "esp_heap_caps.h").write_text(HEAP)
(cls.work / "sdkconfig.h").write_text(
"#define CONFIG_IDF_TARGET_ESP32S3 1\n"
"#define CONFIG_SPIRAM_BOOT_INIT 1\n"
"#define CONFIG_SPIRAM_USE_MALLOC 1\n")
cls.compiler = [os.environ.get("CXX", "g++")]
cls.binaries = {}
@@ -132,6 +131,16 @@ class FloodAdvertPsramTests(unittest.TestCase):
source = self.work / (name + ".cpp")
binary = self.work / name
code = HARNESS.replace("@BANKS@", "").replace("int main(", "int traceMain(")
code = code.replace(
"#define MAKE_LIMITER(name) mesh::StaticFloodAdvertLimiter<> name\n",
"#define MAKE_LIMITER(name) \\\n"
" const unsigned name##_calls = heap_test::heap().external_calls + heap_test::heap().internal_calls; \\\n"
" mesh::StaticFloodAdvertLimiter<> name; \\\n"
" CHECK(heap_test::heap().external_calls + heap_test::heap().internal_calls == name##_calls); \\\n"
" CHECK(name.begin()); \\\n"
" const unsigned name##_initialized = heap_test::heap().external_calls + heap_test::heap().internal_calls; \\\n"
" CHECK(name.begin()); \\\n"
" CHECK(heap_test::heap().external_calls + heap_test::heap().internal_calls == name##_initialized)\n")
# Release builds of the limiter are exercised while harness assertions
# stay active. The trace also checks exact full-key prefix collisions.
code = code.rstrip()[:-1] + " return 0;\n}\n"
+4
View File
@@ -66,6 +66,10 @@ public:
uint8_t flood_channel_data_rule_max_hops = FLOOD_CHANNEL_HOPS_ALL;
FloodChannelScopeEntry flood_channel_scopes[FLOOD_CHANNEL_SCOPE_SLOTS]{};
char flood_channel_direct_scopes[FLOOD_CHANNEL_DIRECT_SCOPE_SLOTS][32]{};
auto floodChannelScopes() -> decltype(flood_channel_scopes)& { return flood_channel_scopes; }
auto floodChannelScopes() const -> const decltype(flood_channel_scopes)& { return flood_channel_scopes; }
auto floodChannelDirectScopes() -> decltype(flood_channel_direct_scopes)& { return flood_channel_direct_scopes; }
auto floodChannelDirectScopes() const -> const decltype(flood_channel_direct_scopes)& { return flood_channel_direct_scopes; }
uint8_t flood_packet_filter_blacklist_count = 0;
uint8_t flood_packet_filter_blacklist[18][3]{};
unsigned seeded = 0;
+46 -32
View File
@@ -13,7 +13,6 @@ from test_flood_advert_psram import HEAP
ROOT = Path(__file__).resolve().parents[1]
SDK = ("#define CONFIG_IDF_TARGET_ESP32S3 1\n"
"#define CONFIG_SPIRAM_BOOT_INIT 1\n"
"#define CONFIG_SPIRAM_USE_MALLOC 1\n")
DEFINES = ["ESP32_PLATFORM=1", "BOARD_HAS_PSRAM=1", "STATION_G3_ESP32=1",
"MESH_CLIENT_REPEATER_ONLY=1"]
@@ -112,19 +111,19 @@ static std::vector<uint8_t> snapshot(const MyMesh& mesh) {
const auto first = static_cast<const uint8_t*>(data);
bytes.insert(bytes.end(), first, first + size);
};
add(mesh.flood_channel_scopes, sizeof(mesh.flood_channel_scopes));
add(mesh.flood_channel_direct_scopes, sizeof(mesh.flood_channel_direct_scopes));
add(mesh.flood_channel_scope_requirements, sizeof(mesh.flood_channel_scope_requirements));
add(mesh.floodChannelScopes(), sizeof(mesh.floodChannelScopes()));
add(mesh.floodChannelDirectScopes(), sizeof(mesh.floodChannelDirectScopes()));
add(mesh.floodChannelScopeRequirements(), sizeof(mesh.floodChannelScopeRequirements()));
return bytes;
}
static void populate(MyMesh& mesh) {
for (unsigned i = 0; i < 32; ++i)
std::snprintf(mesh.flood_channel_direct_scopes[i], 32, "#Scope%u", i);
std::snprintf(mesh.floodChannelDirectScopes()[i], 32, "#Scope%u", i);
for (unsigned i = 0; i < 255; ++i) {
auto& row = mesh.flood_channel_scopes[i];
auto& row = mesh.floodChannelScopes()[i];
row.target_id = i % 32 + 1;
row.selector = FloodFilterPolicy::encodeChannelScopeTargetSelector(0, true);
auto& requirement = mesh.flood_channel_scope_requirements[i];
auto& requirement = mesh.floodChannelScopeRequirements()[i];
requirement.key_len = 32;
std::memset(requirement.secret, uint8_t(i + 1), sizeof(requirement.secret));
}
@@ -132,10 +131,13 @@ static void populate(MyMesh& mesh) {
static void exercise(const char* mode) {
MemoryFS disk;
{
const unsigned constructor_calls = heap_test::heap().external_calls + heap_test::heap().internal_calls;
MyMesh writer(disk);
static_assert(sizeof(writer.flood_channel_scopes) == 255 * 36, "all scope slots");
static_assert(sizeof(writer.flood_channel_direct_scopes) == 32 * 32, "all direct names");
static_assert(sizeof(writer.flood_channel_scope_requirements) == 255 * 34, "all requirements");
CHECK(heap_test::heap().external_calls + heap_test::heap().internal_calls == constructor_calls);
writer.begin();
static_assert(sizeof(writer.floodChannelScopes()) == 255 * 36, "all scope slots");
static_assert(sizeof(writer.floodChannelDirectScopes()) == 32 * 32, "all direct names");
static_assert(sizeof(writer.floodChannelScopeRequirements()) == 255 * 34, "all requirements");
#if MESH_REPEATER_PSRAM_TABLES
static_assert(sizeof(MyMesh) < 256, "no inline fallback arrays");
auto& heap = heap_test::heap();
@@ -153,24 +155,26 @@ static void exercise(const char* mode) {
(void)mode;
#endif
CHECK(snapshot(writer) == std::vector<uint8_t>(18874, 0));
const void* scopes = writer.flood_channel_scopes;
const void* direct = writer.flood_channel_direct_scopes;
const void* require = writer.flood_channel_scope_requirements;
const void* scopes = writer.floodChannelScopes();
const void* direct = writer.floodChannelDirectScopes();
const void* require = writer.floodChannelScopeRequirements();
populate(writer);
const auto initial = snapshot(writer);
writer.begin();
CHECK(snapshot(writer) == initial);
CHECK(writer.saveFloodChannelScopes());
CHECK(writer.saveFloodChannelScopeRequirements());
CHECK(disk.files["/scopes"].size() == 10210);
CHECK(disk.files["/require"].size() == 8675);
std::vector<uint8_t> expected = {'F','C','S','5',255};
const auto* start = reinterpret_cast<const uint8_t*>(writer.flood_channel_scopes);
const auto* start = reinterpret_cast<const uint8_t*>(writer.floodChannelScopes());
expected.insert(expected.end(), start, start + 9180);
expected.push_back(32);
start = reinterpret_cast<const uint8_t*>(writer.flood_channel_direct_scopes);
start = reinterpret_cast<const uint8_t*>(writer.floodChannelDirectScopes());
expected.insert(expected.end(), start, start + 1024);
CHECK(disk.files["/scopes"] == expected);
expected = {'F','C','R','1',255};
start = reinterpret_cast<const uint8_t*>(writer.flood_channel_scope_requirements);
start = reinterpret_cast<const uint8_t*>(writer.floodChannelScopeRequirements());
expected.insert(expected.end(), start, start + 8670);
CHECK(disk.files["/require"] == expected);
const auto saved = disk.files;
@@ -191,9 +195,9 @@ static void exercise(const char* mode) {
CHECK(snapshot(writer) == initial && disk.files == saved);
}
disk.fail_rename = disk.short_write = false;
std::memset(writer.flood_channel_scopes, 0xa5, sizeof(writer.flood_channel_scopes));
std::memset(writer.flood_channel_direct_scopes, 0xa5, sizeof(writer.flood_channel_direct_scopes));
std::memset(writer.flood_channel_scope_requirements, 0xa5, sizeof(writer.flood_channel_scope_requirements));
std::memset(writer.floodChannelScopes(), 0xa5, sizeof(writer.floodChannelScopes()));
std::memset(writer.floodChannelDirectScopes(), 0xa5, sizeof(writer.floodChannelDirectScopes()));
std::memset(writer.floodChannelScopeRequirements(), 0xa5, sizeof(writer.floodChannelScopeRequirements()));
CHECK(writer.loadFloodChannelScopes());
writer.loadFloodChannelScopeRequirements();
CHECK(snapshot(writer) == initial);
@@ -206,17 +210,17 @@ static void exercise(const char* mode) {
CHECK(writer.loadFloodChannelScopes());
writer.loadFloodChannelScopeRequirements();
CHECK(snapshot(writer) == initial);
CHECK(scopes == writer.flood_channel_scopes && direct == writer.flood_channel_direct_scopes
&& require == writer.flood_channel_scope_requirements);
CHECK(scopes == writer.floodChannelScopes() && direct == writer.floodChannelDirectScopes()
&& require == writer.floodChannelScopeRequirements());
// Compare the complete production file payloads between inline and PSRAM runs.
for (const char* path : {"/scopes", "/require"}) {
const auto& bytes = disk.files[path];
CHECK(std::fwrite(bytes.data(), 1, bytes.size(), stdout) == bytes.size());
}
writer.deleteFloodChannelScope(".255", reply);
CHECK(!std::strcmp(reply, "OK") && writer.flood_channel_scopes[254].target_id == 0);
CHECK(!std::strcmp(reply, "OK") && writer.floodChannelScopes()[254].target_id == 0);
writer.deleteFloodChannelScopeRequirement(".255", reply);
CHECK(!std::strcmp(reply, "OK") && writer.flood_channel_scope_requirements[254].key_len == 0);
CHECK(!std::strcmp(reply, "OK") && writer.floodChannelScopeRequirements()[254].key_len == 0);
writer.deleteFloodChannelScope("all", reply);
CHECK(!std::strcmp(reply, "OK"));
writer.deleteFloodChannelScopeRequirement("all", reply);
@@ -225,6 +229,8 @@ static void exercise(const char* mode) {
}
CHECK(heap_test::heap().blocks.empty());
MyMesh recreated(disk);
CHECK(heap_test::heap().blocks.empty());
recreated.begin();
CHECK(snapshot(recreated) == std::vector<uint8_t>(18874, 0));
}
int main(int argc, char** argv) {
@@ -260,10 +266,11 @@ def production_program():
start = header.rfind("#if MESH_REPEATER_PSRAM_TABLES", 0, location)
declarations = header[start:header.index("#if MESH_ENABLE_FLOOD_GROUP_MODERATION", location)]
constructor = source.index("MyMesh::MyMesh(")
start = source.index("#if MESH_REPEATER_PSRAM_TABLES", constructor)
binders = source[start:source.index("#endif", start) + len("#endif")]
start = source.index(" memset(flood_channel_scopes, 0, sizeof(flood_channel_scopes));", constructor)
start = source.index("#if !MESH_REPEATER_PSRAM_TABLES\n memset(flood_channel_scopes", constructor)
resets = source[start:source.index("#if MESH_ENABLE_FLOOD_GROUP_MODERATION", start)]
begin_start = source.index("void MyMesh::begin(FILESYSTEM *fs) {")
begin_start = source.index("{", begin_start) + 1
begin = source[begin_start:source.index("#if MESH_ENABLE_FLOOD_RULE_ENGINE", begin_start)]
methods = [
"bool MyMesh::loadFloodChannelScopes(", "bool MyMesh::saveFloodChannelScopes(",
"void MyMesh::loadFloodChannelScopeRequirements(",
@@ -277,12 +284,13 @@ def production_program():
"static bool isValidStoredFloodFilterScopeName(",
"static bool isExactFloodChannelScopeSelector(",
]
owner = "class MyMesh {\npublic:\nMemoryFS* _fs;\n"
owner = ("class MyMesh {\npublic:\nMemoryFS* _fs;\n"
"struct Limiter { bool begin() { return true; } } flood_advert_limiter;\n")
for name in ("FloodChannelScopeEntry", "FloodChannelScopeRequireEntry"):
owner += body(header, "struct " + name + " {") + ";\n"
owner += declarations + "\nint end_marker;\n"
owner += "explicit MyMesh(MemoryFS& fs) : _fs(&fs),\n" + binders
owner += "\nend_marker(0) {\n" + resets + "}\n"
owner += declarations
owner += "explicit MyMesh(MemoryFS& fs) : _fs(&fs) {\n" + resets + "}\n"
owner += "void begin() {\n" + begin + "}\n"
owner += ("bool loadFloodChannelScopes();\n"
"bool saveFloodChannelScopes(bool empty_table=false);\n"
"void loadFloodChannelScopeRequirements();\n"
@@ -353,11 +361,17 @@ class RepeaterScopePsramTests(unittest.TestCase):
cases = [([], 0, True, SDK), (["ESP32_PLATFORM=1", "STATION_G3_ESP32=1"], 0, True, SDK),
(["ESP32_PLATFORM=1", "BOARD_HAS_PSRAM=1"], 0, True, SDK),
(DEFINES[:-1], 0, True, SDK),
([define for define in DEFINES if not define.startswith("STATION_G3")], 1, True, SDK),
(DEFINES, 1, True, SDK), (DEFINES + ["MESH_REPEATER_PSRAM_TABLES=0"], 0, True, ""),
(["MESH_REPEATER_PSRAM_TABLES=1"], 1, False, SDK),
(DEFINES[:-1] + ["MESH_REPEATER_PSRAM_TABLES=1"], 1, False, SDK),
(DEFINES[:-1] + ["MESH_CLIENT_REPEATER_ONLY=0", "MESH_REPEATER_PSRAM_TABLES=1"],
1, False, SDK),
(DEFINES + ["MESH_REPEATER_PSRAM_TABLES=2"], 2, False, SDK),
(DEFINES, 1, False, SDK.replace("CONFIG_IDF_TARGET_ESP32S3 1", "CONFIG_IDF_TARGET_ESP32 1")),
(DEFINES, 1, False, SDK.replace("CONFIG_SPIRAM_BOOT_INIT 1", "CONFIG_SPIRAM_BOOT_INIT 0")),
(DEFINES, 0, True, SDK.replace("CONFIG_IDF_TARGET_ESP32S3 1", "CONFIG_IDF_TARGET_ESP32 1")),
(DEFINES + ["MESH_REPEATER_PSRAM_TABLES=1"], 1, False,
SDK.replace("CONFIG_IDF_TARGET_ESP32S3 1", "CONFIG_IDF_TARGET_ESP32 1")),
(DEFINES, 1, True, SDK + "#define CONFIG_SPIRAM_BOOT_INIT 1\n"),
(DEFINES, 1, False, SDK.replace("CONFIG_SPIRAM_USE_MALLOC 1", "CONFIG_SPIRAM_USE_MALLOC 0"))]
for index, (defines, expected, success, config) in enumerate(cases):
with self.subTest(index=index, defines=defines):
+10 -7
View File
@@ -280,7 +280,9 @@ class WsTransportPsramTest(unittest.TestCase):
cls.binaries = {}
variants = {
"g3_psram": (4, ["STATION_G3_ESP32", "BOARD_HAS_PSRAM"]),
"other_psram": (4, ["BOARD_HAS_PSRAM"]),
"generic_psram": (4, ["BOARD_HAS_PSRAM"]),
"classic_psram": (4, ["CONFIG_IDF_TARGET_ESP32", "BOARD_HAS_PSRAM"]),
"s3_psram": (4, ["CONFIG_IDF_TARGET_ESP32S3", "BOARD_HAS_PSRAM"]),
"g3_internal": (4, ["STATION_G3_ESP32"]),
"other_internal": (4, []),
"idf5": (5, ["STATION_G3_ESP32", "BOARD_HAS_PSRAM"]),
@@ -306,14 +308,15 @@ class WsTransportPsramTest(unittest.TestCase):
capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
def test_g3_psram_and_guarded_failure_lifecycles(self):
for scenario in ["external_success", "internal_boundary", "reserve_denied",
"largest_denied", "both_fail", "sdk_failure"]:
with self.subTest(scenario=scenario):
self.run_scenario("g3_psram", scenario)
def test_board_agnostic_psram_and_guarded_failure_lifecycles(self):
for variant in ["g3_psram", "generic_psram", "classic_psram", "s3_psram"]:
for scenario in ["external_success", "internal_boundary", "reserve_denied",
"largest_denied", "both_fail", "sdk_failure"]:
with self.subTest(variant=variant, scenario=scenario):
self.run_scenario(variant, scenario)
def test_other_target_allocation_defaults_and_failure_cleanup(self):
for variant in ["other_psram", "g3_internal", "other_internal"]:
for variant in ["g3_internal", "other_internal"]:
for scenario in ["generic_success", "generic_failure", "sdk_failure"]:
with self.subTest(variant=variant, scenario=scenario):
self.run_scenario(variant, scenario)