Skip to content

Commit b3c6cb7

Browse files
authored
Merge pull request #31 from FastLED/test/esp32-family-verify-scaffold
test(deploy): ESP32 family hardware-gated verify scaffolds (#19)
2 parents 3497688 + 2dfb65b commit b3c6cb7

2 files changed

Lines changed: 203 additions & 47 deletions

File tree

crates/fbuild-deploy/src/esp32.rs

Lines changed: 197 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,28 +1131,72 @@ mod tests {
11311131
assert!(!mm.is_match());
11321132
}
11331133

1134-
/// Hardware-gated integration test for fast deploy.
1134+
// ---------------------------------------------------------------
1135+
// Hardware-gated verify-deployment tests for each ESP32 family MCU.
1136+
//
1137+
// These tests are `#[ignore]` so they never run in CI. To exercise
1138+
// them on a local bench, set the env vars described below and run:
1139+
//
1140+
// uv run cargo test -p fbuild-deploy esp32::tests::try_verify_deployment_real -- --ignored --nocapture
1141+
//
1142+
// Each test reads **two** environment variables:
1143+
//
1144+
// <MCU>_PORT – serial port the board is attached to (e.g. COM13, /dev/ttyUSB0)
1145+
// <MCU>_FIRMWARE – absolute path to a pre-flashed firmware.bin
1146+
//
1147+
// where <MCU> is one of ESP32, ESP32S2, ESP32S3, ESP32C2, ESP32C3,
1148+
// ESP32C6, ESP32H2, ESP32P4.
1149+
//
1150+
// The firmware directory must also contain `bootloader.bin` and
1151+
// `partitions.bin` so that verify-flash can check all three regions
1152+
// in a single esptool invocation.
1153+
//
1154+
// Bootloader offsets per chip (from esp32.rs header comment):
1155+
// 0x1000 – esp32, esp32s2
1156+
// 0x0 – esp32c2, esp32c3, esp32c5, esp32c6, esp32h2, esp32s3
1157+
// 0x2000 – esp32p4
1158+
// ---------------------------------------------------------------
1159+
1160+
/// Shared implementation for all per-chip hardware-gated verify tests.
11351161
///
1136-
/// Requires a real ESP32-S3 attached to `COM13` that has been
1137-
/// pre-flashed with the FastLED reference firmware at
1138-
/// `C:\Users\niteris\dev\fastled\.pio\build\esp32s3\firmware.bin`.
1139-
/// Run with:
1140-
/// ```
1141-
/// uv run cargo test -p fbuild-deploy esp32::tests::try_verify_deployment_real_esp32s3 -- --ignored --nocapture
1142-
/// ```
1143-
///
1144-
/// Asserts (a) verify against the same image returns `Match` in
1145-
/// under 15 seconds, and (b) verify against a tampered firmware
1146-
/// (1 byte flipped) returns `Mismatch`.
1147-
#[test]
1148-
#[ignore = "requires ESP32-S3 attached to COM13 with FastLED reference firmware"]
1149-
fn try_verify_deployment_real_esp32s3() {
1150-
let port = "COM13";
1151-
let reference: std::path::PathBuf =
1152-
r"C:\Users\niteris\dev\fastled\.pio\build\esp32s3\firmware.bin".into();
1153-
if !reference.exists() {
1162+
/// 1. Reads `{port_env}` and `{firmware_env}` from the environment.
1163+
/// 2. Asserts that verify against the pre-flashed image returns `Match`
1164+
/// in under 15 seconds.
1165+
/// 3. Asserts that a tampered image (1 byte flipped) returns `Mismatch`.
1166+
fn run_verify_deployment_test(
1167+
chip: &str,
1168+
bootloader_offset: &str,
1169+
port_env: &str,
1170+
firmware_env: &str,
1171+
) {
1172+
let port = std::env::var(port_env).unwrap_or_else(|_| {
1173+
panic!(
1174+
"set {} to the serial port your {} board is attached to (e.g. COM13)",
1175+
port_env, chip
1176+
)
1177+
});
1178+
let firmware_path = std::env::var(firmware_env).unwrap_or_else(|_| {
11541179
panic!(
1155-
"reference firmware not found at {}; pre-build FastLED's esp32s3 env first",
1180+
"set {} to the absolute path of the pre-flashed firmware.bin for {}",
1181+
firmware_env, chip
1182+
)
1183+
});
1184+
let reference = std::path::PathBuf::from(&firmware_path);
1185+
assert!(
1186+
reference.is_file(),
1187+
"reference firmware not found at {}; build and flash it first",
1188+
reference.display()
1189+
);
1190+
let ref_dir = reference
1191+
.parent()
1192+
.unwrap_or_else(|| std::path::Path::new("."));
1193+
for name in ["bootloader.bin", "partitions.bin"] {
1194+
let artifact = ref_dir.join(name);
1195+
assert!(
1196+
artifact.is_file(),
1197+
"[{}] missing {} next to {}; otherwise this only verifies firmware.bin",
1198+
chip,
1199+
name,
11561200
reference.display()
11571201
);
11581202
}
@@ -1161,46 +1205,46 @@ mod tests {
11611205
flash_mode: "dio".to_string(),
11621206
flash_freq: "80m".to_string(),
11631207
default_baud: "921600".to_string(),
1164-
before_reset: "default-reset".to_string(),
1165-
after_reset: "hard-reset".to_string(),
1208+
before_reset: "default_reset".to_string(),
1209+
after_reset: "hard_reset".to_string(),
11661210
};
11671211
let deployer = Esp32Deployer::new(
1168-
"esp32s3", "921600", "0x0", "0x8000", "0x10000", &params, true,
1212+
chip,
1213+
"921600",
1214+
bootloader_offset,
1215+
"0x8000",
1216+
"0x10000",
1217+
&params,
1218+
true,
11691219
);
11701220

1171-
// Phase 1: matching image Match
1221+
// Phase 1: matching image -> Match
11721222
let start = std::time::Instant::now();
11731223
let outcome = deployer
1174-
.try_verify_deployment(&reference, port)
1175-
.expect("verify must not fail to run against attached ESP32-S3");
1224+
.try_verify_deployment(&reference, &port)
1225+
.unwrap_or_else(|e| panic!("verify must not fail against attached {}: {}", chip, e));
11761226
let elapsed = start.elapsed();
11771227
assert!(
11781228
outcome.is_match(),
1179-
"expected Match against pre-flashed firmware; got {:?}",
1229+
"[{}] expected Match against pre-flashed firmware; got {:?}",
1230+
chip,
11801231
outcome
11811232
);
11821233
assert!(
11831234
elapsed < std::time::Duration::from_secs(15),
1184-
"verify took {:?} — should complete in <15s for the FastLED 2.4MB image",
1235+
"[{}] verify took {:?} -- should complete in <15s",
1236+
chip,
11851237
elapsed
11861238
);
1187-
eprintln!("verify (Match) elapsed: {:?}", elapsed);
1239+
eprintln!("[{}] verify (Match) elapsed: {:?}", chip, elapsed);
11881240

1189-
// Phase 2: tampered image Mismatch
1241+
// Phase 2: tampered image -> Mismatch
11901242
let tmp = tempfile::TempDir::new().unwrap();
11911243
// Copy bootloader and partitions next to the tampered firmware so
11921244
// build_verify_flash_args picks them up alongside firmware.bin.
1193-
let ref_dir = reference.parent().unwrap();
1194-
std::fs::copy(
1195-
ref_dir.join("bootloader.bin"),
1196-
tmp.path().join("bootloader.bin"),
1197-
)
1198-
.unwrap();
1199-
std::fs::copy(
1200-
ref_dir.join("partitions.bin"),
1201-
tmp.path().join("partitions.bin"),
1202-
)
1203-
.unwrap();
1245+
for name in ["bootloader.bin", "partitions.bin"] {
1246+
std::fs::copy(ref_dir.join(name), tmp.path().join(name)).unwrap();
1247+
}
12041248
let tampered = tmp.path().join("firmware.bin");
12051249
let mut bytes = std::fs::read(&reference).unwrap();
12061250
// Flip a byte well past the image header to avoid invalidating
@@ -1211,13 +1255,121 @@ mod tests {
12111255
std::fs::write(&tampered, &bytes).unwrap();
12121256

12131257
let outcome = deployer
1214-
.try_verify_deployment(&tampered, port)
1215-
.expect("verify must not fail to run with tampered firmware");
1258+
.try_verify_deployment(&tampered, &port)
1259+
.unwrap_or_else(|e| {
1260+
panic!(
1261+
"[{}] verify must not fail with tampered firmware: {}",
1262+
chip, e
1263+
)
1264+
});
12161265
assert!(
12171266
!outcome.is_match(),
1218-
"expected Mismatch for tampered firmware; got {:?}",
1267+
"[{}] expected Mismatch for tampered firmware; got {:?}",
1268+
chip,
12191269
outcome
12201270
);
1221-
eprintln!("verify (Mismatch) detected correctly");
1271+
eprintln!("[{}] verify (Mismatch) detected correctly", chip);
1272+
}
1273+
1274+
/// ESP32 (Xtensa, bootloader at 0x1000).
1275+
///
1276+
/// ```text
1277+
/// ESP32_PORT=COM5 ESP32_FIRMWARE=C:\path\to\firmware.bin \
1278+
/// uv run cargo test -p fbuild-deploy esp32::tests::try_verify_deployment_real_esp32 -- --ignored --nocapture
1279+
/// ```
1280+
#[test]
1281+
#[ignore = "requires real ESP32 board — set ESP32_PORT and ESP32_FIRMWARE"]
1282+
fn try_verify_deployment_real_esp32() {
1283+
run_verify_deployment_test("esp32", "0x1000", "ESP32_PORT", "ESP32_FIRMWARE");
1284+
}
1285+
1286+
/// ESP32-S2 (Xtensa single-core, bootloader at 0x1000).
1287+
///
1288+
/// ```text
1289+
/// ESP32S2_PORT=COM6 ESP32S2_FIRMWARE=C:\path\to\firmware.bin \
1290+
/// uv run cargo test -p fbuild-deploy esp32::tests::try_verify_deployment_real_esp32s2 -- --ignored --nocapture
1291+
/// ```
1292+
#[test]
1293+
#[ignore = "requires real ESP32-S2 board — set ESP32S2_PORT and ESP32S2_FIRMWARE"]
1294+
fn try_verify_deployment_real_esp32s2() {
1295+
run_verify_deployment_test("esp32s2", "0x1000", "ESP32S2_PORT", "ESP32S2_FIRMWARE");
1296+
}
1297+
1298+
/// ESP32-S3 (Xtensa dual-core, bootloader at 0x0).
1299+
///
1300+
/// This is the original baseline test, now using env-var configuration
1301+
/// consistent with the rest of the family.
1302+
///
1303+
/// ```text
1304+
/// ESP32S3_PORT=COM13 ESP32S3_FIRMWARE=C:\Users\niteris\dev\fastled\.pio\build\esp32s3\firmware.bin \
1305+
/// uv run cargo test -p fbuild-deploy esp32::tests::try_verify_deployment_real_esp32s3 -- --ignored --nocapture
1306+
/// ```
1307+
#[test]
1308+
#[ignore = "requires real ESP32-S3 board — set ESP32S3_PORT and ESP32S3_FIRMWARE"]
1309+
fn try_verify_deployment_real_esp32s3() {
1310+
run_verify_deployment_test("esp32s3", "0x0", "ESP32S3_PORT", "ESP32S3_FIRMWARE");
1311+
}
1312+
1313+
/// ESP32-C2 (RISC-V single-core, bootloader at 0x0).
1314+
///
1315+
/// ```text
1316+
/// ESP32C2_PORT=COM7 ESP32C2_FIRMWARE=C:\path\to\firmware.bin \
1317+
/// uv run cargo test -p fbuild-deploy esp32::tests::try_verify_deployment_real_esp32c2 -- --ignored --nocapture
1318+
/// ```
1319+
#[test]
1320+
#[ignore = "requires real ESP32-C2 board — set ESP32C2_PORT and ESP32C2_FIRMWARE"]
1321+
fn try_verify_deployment_real_esp32c2() {
1322+
run_verify_deployment_test("esp32c2", "0x0", "ESP32C2_PORT", "ESP32C2_FIRMWARE");
1323+
}
1324+
1325+
/// ESP32-C3 (RISC-V single-core, bootloader at 0x0).
1326+
///
1327+
/// ```text
1328+
/// ESP32C3_PORT=COM8 ESP32C3_FIRMWARE=C:\path\to\firmware.bin \
1329+
/// uv run cargo test -p fbuild-deploy esp32::tests::try_verify_deployment_real_esp32c3 -- --ignored --nocapture
1330+
/// ```
1331+
#[test]
1332+
#[ignore = "requires real ESP32-C3 board — set ESP32C3_PORT and ESP32C3_FIRMWARE"]
1333+
fn try_verify_deployment_real_esp32c3() {
1334+
run_verify_deployment_test("esp32c3", "0x0", "ESP32C3_PORT", "ESP32C3_FIRMWARE");
1335+
}
1336+
1337+
/// ESP32-C6 (RISC-V single-core, bootloader at 0x0).
1338+
///
1339+
/// ```text
1340+
/// ESP32C6_PORT=COM9 ESP32C6_FIRMWARE=C:\path\to\firmware.bin \
1341+
/// uv run cargo test -p fbuild-deploy esp32::tests::try_verify_deployment_real_esp32c6 -- --ignored --nocapture
1342+
/// ```
1343+
#[test]
1344+
#[ignore = "requires real ESP32-C6 board — set ESP32C6_PORT and ESP32C6_FIRMWARE"]
1345+
fn try_verify_deployment_real_esp32c6() {
1346+
run_verify_deployment_test("esp32c6", "0x0", "ESP32C6_PORT", "ESP32C6_FIRMWARE");
1347+
}
1348+
1349+
/// ESP32-H2 (RISC-V single-core, bootloader at 0x0).
1350+
///
1351+
/// ```text
1352+
/// ESP32H2_PORT=COM10 ESP32H2_FIRMWARE=C:\path\to\firmware.bin \
1353+
/// uv run cargo test -p fbuild-deploy esp32::tests::try_verify_deployment_real_esp32h2 -- --ignored --nocapture
1354+
/// ```
1355+
#[test]
1356+
#[ignore = "requires real ESP32-H2 board — set ESP32H2_PORT and ESP32H2_FIRMWARE"]
1357+
fn try_verify_deployment_real_esp32h2() {
1358+
run_verify_deployment_test("esp32h2", "0x0", "ESP32H2_PORT", "ESP32H2_FIRMWARE");
1359+
}
1360+
1361+
/// ESP32-P4 (RISC-V dual-core, OPI flash, bootloader at 0x2000).
1362+
///
1363+
/// Note: ESP32-P4 uses OPI flash and has a different bootloader offset
1364+
/// (0x2000) compared to other ESP32 chips.
1365+
///
1366+
/// ```text
1367+
/// ESP32P4_PORT=COM11 ESP32P4_FIRMWARE=C:\path\to\firmware.bin \
1368+
/// uv run cargo test -p fbuild-deploy esp32::tests::try_verify_deployment_real_esp32p4 -- --ignored --nocapture
1369+
/// ```
1370+
#[test]
1371+
#[ignore = "requires real ESP32-P4 board — set ESP32P4_PORT and ESP32P4_FIRMWARE"]
1372+
fn try_verify_deployment_real_esp32p4() {
1373+
run_verify_deployment_test("esp32p4", "0x2000", "ESP32P4_PORT", "ESP32P4_FIRMWARE");
12221374
}
12231375
}

crates/fbuild-packages/src/disk_cache/index.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -536,8 +536,12 @@ impl CacheIndex {
536536
fn is_pid_alive(pid: u32) -> bool {
537537
#[cfg(unix)]
538538
{
539-
// kill(pid, 0) checks if process exists without sending a signal
540-
unsafe { libc::kill(pid as i32, 0) == 0 }
539+
// kill(pid, 0) checks if process exists without sending a signal.
540+
// Use raw FFI to avoid a libc crate dependency (matters for musl builds).
541+
extern "C" {
542+
fn kill(pid: i32, sig: i32) -> i32;
543+
}
544+
unsafe { kill(pid as i32, 0) == 0 }
541545
}
542546
#[cfg(windows)]
543547
{

0 commit comments

Comments
 (0)