test(host): harden Phase 9 live rig
This commit is contained in:
+57
-15
@@ -1020,7 +1020,8 @@ mod tests {
|
|||||||
const PN_CHIP_FRAMES: usize = 24;
|
const PN_CHIP_FRAMES: usize = 24;
|
||||||
const PN_SEQUENCE_CHIPS: usize = 4_096;
|
const PN_SEQUENCE_CHIPS: usize = 4_096;
|
||||||
const PN_WINDOW_CHIPS: usize = 1_024;
|
const PN_WINDOW_CHIPS: usize = 1_024;
|
||||||
const PN_FILE_SECONDS: usize = 8;
|
const PN_FILE_SECONDS: usize = 12;
|
||||||
|
const PN_CAPTURE_SECONDS: u64 = 6;
|
||||||
const PN_SKIP_FRAMES: usize = PN_SAMPLE_RATE / 2;
|
const PN_SKIP_FRAMES: usize = PN_SAMPLE_RATE / 2;
|
||||||
const DESKTOP_PN_SEED: u64 = 0x1357_9bdf;
|
const DESKTOP_PN_SEED: u64 = 0x1357_9bdf;
|
||||||
const REMOTE_PN_SEED: u64 = 0x2468_ace1;
|
const REMOTE_PN_SEED: u64 = 0x2468_ace1;
|
||||||
@@ -1630,30 +1631,36 @@ mod tests {
|
|||||||
Ok(errors)
|
Ok(errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_ids_named(names: &[&str]) -> Result<HashSet<u64>> {
|
fn matching_node_ids(objects: &[serde_json::Value], names: &[&str]) -> Result<HashSet<u64>> {
|
||||||
let objects = pw_dump_objects("the Phase-9 xrun node set")?;
|
|
||||||
let wanted = names.iter().copied().collect::<HashSet<_>>();
|
let wanted = names.iter().copied().collect::<HashSet<_>>();
|
||||||
|
let mut seen_names = HashSet::new();
|
||||||
let found = objects
|
let found = objects
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|object| {
|
.filter_map(|object| {
|
||||||
let name = object
|
let name = object
|
||||||
.pointer("/info/props/node.name")
|
.pointer("/info/props/node.name")
|
||||||
.and_then(serde_json::Value::as_str)?;
|
.and_then(serde_json::Value::as_str)?;
|
||||||
wanted
|
if !wanted.contains(name) {
|
||||||
.contains(name)
|
return None;
|
||||||
.then(|| object.get("id").and_then(value_u64))?
|
}
|
||||||
|
let id = object.get("id").and_then(value_u64)?;
|
||||||
|
seen_names.insert(name);
|
||||||
|
Some(id)
|
||||||
})
|
})
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
if found.len() != wanted.len() {
|
let mut missing = wanted.difference(&seen_names).copied().collect::<Vec<_>>();
|
||||||
bail!(
|
if !missing.is_empty() {
|
||||||
"Phase-9 xrun snapshot resolved {} of {} required nodes ({names:?})",
|
missing.sort_unstable();
|
||||||
found.len(),
|
bail!("Phase-9 xrun snapshot did not resolve required node names: {missing:?}");
|
||||||
wanted.len()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Ok(found)
|
Ok(found)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn node_ids_named(names: &[&str]) -> Result<HashSet<u64>> {
|
||||||
|
let objects = pw_dump_objects("the Phase-9 xrun node set")?;
|
||||||
|
matching_node_ids(&objects, names)
|
||||||
|
}
|
||||||
|
|
||||||
fn xrun_delta(
|
fn xrun_delta(
|
||||||
before: &HashMap<u64, u64>,
|
before: &HashMap<u64, u64>,
|
||||||
after: &HashMap<u64, u64>,
|
after: &HashMap<u64, u64>,
|
||||||
@@ -1671,7 +1678,11 @@ mod tests {
|
|||||||
.sum()
|
.sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn record_capture(monitor_name: &str, label: &str) -> Result<Vec<u8>> {
|
async fn record_capture_for(
|
||||||
|
monitor_name: &str,
|
||||||
|
label: &str,
|
||||||
|
duration: Duration,
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
let raw = RawCaptureFile::new(label);
|
let raw = RawCaptureFile::new(label);
|
||||||
let output_file =
|
let output_file =
|
||||||
std::fs::File::create(&raw.0).with_context(|| format!("create {}", raw.0.display()))?;
|
std::fs::File::create(&raw.0).with_context(|| format!("create {}", raw.0.display()))?;
|
||||||
@@ -1690,12 +1701,16 @@ mod tests {
|
|||||||
.stdout(Stdio::from(output_file))
|
.stdout(Stdio::from(output_file))
|
||||||
.stderr(Stdio::piped());
|
.stderr(Stdio::piped());
|
||||||
let mut recorder = ContainedProcess::spawn(&mut command, "Phase-6 parec recorder")?;
|
let mut recorder = ContainedProcess::spawn(&mut command, "Phase-6 parec recorder")?;
|
||||||
tokio::time::sleep(Duration::from_secs(3)).await;
|
tokio::time::sleep(duration).await;
|
||||||
recorder.ensure_running()?;
|
recorder.ensure_running()?;
|
||||||
recorder.stop()?;
|
recorder.stop()?;
|
||||||
std::fs::read(&raw.0).with_context(|| format!("read {}", raw.0.display()))
|
std::fs::read(&raw.0).with_context(|| format!("read {}", raw.0.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn record_capture(monitor_name: &str, label: &str) -> Result<Vec<u8>> {
|
||||||
|
record_capture_for(monitor_name, label, Duration::from_secs(3)).await
|
||||||
|
}
|
||||||
|
|
||||||
fn tone_dbfs(raw: &[u8], frequency: f64) -> Result<f64> {
|
fn tone_dbfs(raw: &[u8], frequency: f64) -> Result<f64> {
|
||||||
const SAMPLE_RATE: f64 = 48_000.0;
|
const SAMPLE_RATE: f64 = 48_000.0;
|
||||||
const CHANNELS: usize = 2;
|
const CHANNELS: usize = 2;
|
||||||
@@ -1935,7 +1950,12 @@ mod tests {
|
|||||||
}
|
}
|
||||||
let relevant_xrun_nodes = node_ids_named(&xrun_names)?;
|
let relevant_xrun_nodes = node_ids_named(&xrun_names)?;
|
||||||
let xruns_before = pw_top_errors()?;
|
let xruns_before = pw_top_errors()?;
|
||||||
let raw = record_capture(&monitor_name, &format!("phase9-{}", arm.label)).await?;
|
let raw = record_capture_for(
|
||||||
|
&monitor_name,
|
||||||
|
&format!("phase9-{}", arm.label),
|
||||||
|
Duration::from_secs(PN_CAPTURE_SECONDS),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let xruns_after = pw_top_errors()?;
|
let xruns_after = pw_top_errors()?;
|
||||||
|
|
||||||
// Original routes must still be intact after the measurement, not only
|
// Original routes must still be intact after the measurement, not only
|
||||||
@@ -2014,6 +2034,28 @@ mod tests {
|
|||||||
assert!(max_cyclic_correlation(&vec![0.0; PN_WINDOW_CHIPS], &desktop) == 0.0);
|
assert!(max_cyclic_correlation(&vec![0.0; PN_WINDOW_CHIPS], &desktop) == 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn phase9_xrun_node_resolution_accepts_duplicate_names_but_not_missing_names() {
|
||||||
|
let objects = serde_json::json!([
|
||||||
|
{"id": 11, "info": {"props": {"node.name": "controlled"}}},
|
||||||
|
{"id": 12, "info": {"props": {"node.name": "controlled"}}},
|
||||||
|
{"id": 13, "info": {"props": {"node.name": "playback"}}},
|
||||||
|
{"id": 14, "info": {"props": {"node.name": "unrelated"}}}
|
||||||
|
]);
|
||||||
|
let objects = objects.as_array().expect("fixture is an array");
|
||||||
|
assert_eq!(
|
||||||
|
matching_node_ids(objects, &["controlled", "playback"])
|
||||||
|
.expect("all required names are represented"),
|
||||||
|
HashSet::from([11, 12, 13])
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
matching_node_ids(objects, &["controlled", "missing"])
|
||||||
|
.unwrap_err()
|
||||||
|
.to_string()
|
||||||
|
.contains("missing")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Phase-9 rig upgrade: the Phase-6 production-path topology is driven by
|
/// Phase-9 rig upgrade: the Phase-6 production-path topology is driven by
|
||||||
/// two pinned, independently generated PN probes. Every non-overlapping
|
/// two pinned, independently generated PN probes. Every non-overlapping
|
||||||
/// half-second window is normalized and correlated separately on left and
|
/// half-second window is normalized and correlated separately on left and
|
||||||
|
|||||||
Reference in New Issue
Block a user