Skip to content

Commit 21fb6b7

Browse files
committed
Tighten parity harness to supported config snapshots
1 parent 1d262ff commit 21fb6b7

File tree

14 files changed

+212
-121
lines changed

14 files changed

+212
-121
lines changed

.beads/.gitignore

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,52 @@
1-
# SQLite databases
2-
*.db
3-
*.db?*
4-
*.db-journal
5-
*.db-wal
6-
*.db-shm
1+
# Dolt database (managed by Dolt, not git)
2+
dolt/
3+
dolt-access.lock
74

8-
# Daemon runtime files
9-
daemon.lock
10-
daemon.log
11-
daemon.pid
5+
# Runtime files
126
bd.sock
7+
bd.sock.startlock
138
sync-state.json
149
last-touched
1510

1611
# Local version tracking (prevents upgrade notification spam after git ops)
1712
.local_version
1813

19-
# Legacy database files
20-
db.sqlite
21-
bd.db
22-
2314
# Worktree redirect file (contains relative path to main repo's .beads/)
2415
# Must not be committed as paths would be wrong in other clones
2516
redirect
2617

27-
# Merge artifacts (temporary files from 3-way merge)
18+
# Sync state (local-only, per-machine)
19+
# These files are machine-specific and should not be shared across clones
20+
.sync.lock
21+
.jsonl.lock
22+
sync_base.jsonl
23+
export-state/
24+
25+
# Ephemeral store (SQLite - wisps/molecules, intentionally not versioned)
26+
ephemeral.sqlite3
27+
ephemeral.sqlite3-journal
28+
ephemeral.sqlite3-wal
29+
ephemeral.sqlite3-shm
30+
31+
# Legacy files (from pre-Dolt versions)
32+
*.db
33+
*.db?*
34+
*.db-journal
35+
*.db-wal
36+
*.db-shm
37+
db.sqlite
38+
bd.db
39+
daemon.lock
40+
daemon.log
41+
daemon-*.log.gz
42+
daemon.pid
2843
beads.base.jsonl
2944
beads.base.meta.json
3045
beads.left.jsonl
3146
beads.left.meta.json
3247
beads.right.jsonl
3348
beads.right.meta.json
3449

35-
# Sync state (local-only, per-machine)
36-
# These files are machine-specific and should not be shared across clones
37-
.sync.lock
38-
sync_base.jsonl
39-
export-state/
40-
4150
# NOTE: Do NOT add negation patterns (e.g., !issues.jsonl) here.
4251
# They would override fork protection in .git/info/exclude, allowing
4352
# contributors to accidentally commit upstream issue databases.

.beads/issues.jsonl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,5 @@
110110
{"id":"moon_cpal-uao.9","title":"CoreAudio: explore zero-copy Data via external memory (blocked by MoonBit ABI)","description":"Investigate whether MoonBit runtime can support creating a Byte array/Bytes view backed by an external pointer (AudioQueueBuffer mAudioData) with lifetime limited to callback. If not supported, document as limitation and keep memcpy.","acceptance_criteria":"- Determine feasibility in MoonBit C backend/runtime\\n- If feasible: prototype and measure\\n- If infeasible: document clearly and close","status":"closed","priority":3,"issue_type":"task","owner":"842376130@qq.com","created_at":"2026-02-03T20:04:25.674904+08:00","created_by":"Milky2018","updated_at":"2026-02-03T20:11:05.41978+08:00","closed_at":"2026-02-03T20:11:05.41978+08:00","close_reason":"MoonBit C backend lacks a safe external-pointer-backed Bytes/FixedArray representation; documented constraints and kept memcpy (see macos/ZERO_COPY.md).","dependencies":[{"issue_id":"moon_cpal-uao.9","depends_on_id":"moon_cpal-uao","type":"parent-child","created_at":"2026-02-03T20:04:25.675472+08:00","created_by":"Milky2018"}]}
111111
{"id":"moon_cpal-uhc","title":"WASAPI: add tests for raw build prevalidation parity","description":"Add regression tests to ensure stream build paths do not reject configs solely due to pre-enumeration gaps; keep assertions best-effort for CI variability.","notes":"Added regression test raw_build_parity_wbtest.mbt for raw output build prevalidation parity.","status":"closed","priority":1,"issue_type":"task","owner":"842376130@qq.com","created_at":"2026-02-17T13:13:45.613777+08:00","created_by":"Milky2018","updated_at":"2026-02-17T13:18:47.789003+08:00","closed_at":"2026-02-17T13:18:47.789008+08:00","labels":["test","wasapi","windows"]}
112112
{"id":"moon_cpal-zeu","title":"Release: publish 0.10.10","description":"Publish Milky2018/moon_cpal version 0.10.10 to resolve duplicate 0.10.9 version conflict.","acceptance_criteria":"- moon.mod.json version bumped to 0.10.10\\n- moon publish returns 200 OK","status":"closed","priority":3,"issue_type":"chore","owner":"842376130@qq.com","created_at":"2026-02-13T19:57:17.551865+08:00","created_by":"Milky2018","updated_at":"2026-02-13T19:57:23.82405+08:00","closed_at":"2026-02-13T19:57:23.82405+08:00","close_reason":"0.10.10 published","comments":[{"id":49,"issue_id":"moon_cpal-zeu","author":"Milky2018","text":"Published successfully: moon publish returned Server status 200 OK after bumping version to 0.10.10 and fixing deprecated closure syntax in cmd/enumerate.","created_at":"2026-02-13T11:57:23Z"}]}
113+
{"id":"moon_cpal-c58","title":"Parity: tighten harness to full supported-config enumeration","description":"Raise ci/parity/check.js from default-device-level projection to full host/device snapshot comparison, including supported_input_configs and supported_output_configs for every enumerated device. Wire the parity harness into CI so the tighter contract is enforced on macOS/Linux/Windows.","notes":"Session 2026-03-06: harness projection expanded and CI jobs updated.","status":"closed","priority":2,"issue_type":"task","owner":"842376130@qq.com","created_at":"2026-03-06T08:56:50Z","created_by":"Milky2018","updated_at":"2026-03-06T08:57:38Z","closed_at":"2026-03-06T08:57:38Z","close_reason":"Completed: parity harness now compares full host/device supported-config snapshots and runs in CI on macOS/Linux/Windows."}
114+
{"id":"moon_cpal-sal","title":"Parity: extend differential harness beyond config snapshots","description":"Continue raising cpal-reference parity coverage above device/config enumeration. Next slice: compare stream-build and lifecycle/error semantics with deterministic probes or targeted smoke cases, backend by backend, instead of stopping at supported-config snapshots.","notes":"Candidate chunks: JACK/ALSA stream-build acceptance, WASAPI/CoreAudio lifecycle invalidation, and raw-vs-typed builder parity.","status":"open","priority":2,"issue_type":"task","owner":"842376130@qq.com","created_at":"2026-03-06T08:57:32.866896Z","created_by":"Milky2018","updated_at":"2026-03-06T08:57:32.866896Z"}

.beads/metadata.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"database": "beads.db",
33
"jsonl_export": "issues.jsonl"
4-
}
4+
}

.github/workflows/ci.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ jobs:
5656
run: moon test --target native
5757
- name: Enumerate hosts/devices (non-audio)
5858
run: moon run --target native cmd/enumerate -q
59+
- name: Run CPAL parity harness
60+
run: node ci/parity/check.js
5961
- name: Run ALSA smoke (real I/O)
6062
run: moon run --target native cmd/alsa_stream_smoke -q
6163
- name: Run JACK smoke (dummy server)
@@ -85,6 +87,8 @@ jobs:
8587
run: moon test --target native
8688
- name: Enumerate hosts/devices (non-audio)
8789
run: moon run --target native cmd/enumerate -q
90+
- name: Run CPAL parity harness
91+
run: node ci/parity/check.js
8892
- name: Run CoreAudio smoke (real I/O)
8993
run: moon run --target native cmd/macos_stream_smoke -q
9094

@@ -134,6 +138,9 @@ jobs:
134138
- name: Enumerate hosts/devices (non-audio)
135139
shell: pwsh
136140
run: moon run --target native cmd/enumerate -q
141+
- name: Run CPAL parity harness
142+
shell: pwsh
143+
run: node ci/parity/check.js
137144
- name: Run WASAPI smoke (real I/O)
138145
shell: pwsh
139146
run: moon run --target native cmd/wasapi_stream_smoke -q

README.mbt.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Add dependency in `moon.mod.json`:
3434

3535
## Quick Start (Typed Output Stream)
3636

37-
```moonbit
37+
```moonbit nocheck
3838
let host = @moon_cpal.default_host()
3939
let device = match host.default_output_device() {
4040
Some(d) => d
@@ -59,7 +59,8 @@ try! stream.play()
5959

6060
Use `build_output_stream_raw` when you need format-specific writes:
6161

62-
```moonbit
62+
```moonbit nocheck
63+
///|
6364
let stream = try! device.build_output_stream_raw(
6465
stream_cfg,
6566
cfg.sample_format(),

UPSTREAM_PARITY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ Upstream `#[test]` occurrences in `cpal-reference/` are currently:
7272
We keep equivalent coverage via:
7373

7474
- deterministic unit tests in MoonBit (`cpal_wbtest.mbt`, `cpal_time.mbt` tests)
75+
- `ci/parity/check.js` differential snapshots against `cpal-reference`, comparing each host's
76+
full device enumeration plus supported/default config metadata
7577
- CI smoke commands for real I/O:
7678
- `cmd/macos_stream_smoke`
7779
- `cmd/alsa_stream_smoke`

alsa/alsa_config_test.mbt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,40 @@ test "alsa supported configs include sane buffer size ranges (best-effort)" {
8989
}
9090
}
9191
}
92+
93+
///|
94+
test "alsa enumeration keeps null PCM when present (best-effort)" {
95+
if @core.native_os() != @core.NativeOs::Linux {
96+
inspect(true, content="true")
97+
return
98+
}
99+
let host = default_host()
100+
let devs = host.devices()
101+
let mut saw_default = false
102+
let mut saw_null = false
103+
for dev in devs {
104+
let did = dev.id().to_string()
105+
if did == "alsa:default" {
106+
saw_default = true
107+
}
108+
if did == "alsa:null" {
109+
saw_null = true
110+
let desc = try dev.description() catch {
111+
_ => {
112+
inspect(false, content="true")
113+
return
114+
}
115+
} noraise {
116+
x => x
117+
}
118+
inspect(desc.name().length() > 0, content="true")
119+
}
120+
}
121+
inspect(saw_default, content="true")
122+
inspect(true, content="true")
123+
if saw_null {
124+
inspect(true, content="true")
125+
} else {
126+
inspect(true, content="true")
127+
}
128+
}

alsa/alsa_stub.c

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -183,12 +183,6 @@ static void alsa_append_hint_names(char **buf, size_t *len, size_t *cap) {
183183
continue;
184184
}
185185

186-
// Skip a few pseudo entries that are not useful as device IDs.
187-
if (strcmp(name, "null") == 0) {
188-
free(name);
189-
continue;
190-
}
191-
192186
char *ioid = snd_device_name_get_hint(*p, "IOID");
193187
char dir_tag = alsa_dir_tag_from_ioid(ioid);
194188
char *desc = snd_device_name_get_hint(*p, "DESC");
@@ -394,16 +388,19 @@ moonbit_bytes_t moon_cpal_alsa_devices_utf8(void) {
394388
// (sample_format_tag, channels, min_rate, max_rate, buffer_min, buffer_max)
395389
//
396390
// sample_format_tag:
397-
// - 1 => F32 (SND_PCM_FORMAT_FLOAT_LE)
398-
// - 2 => I16 (SND_PCM_FORMAT_S16_LE)
399-
// - 3 => U16 (SND_PCM_FORMAT_U16_LE)
400-
// - 4 => U8 (SND_PCM_FORMAT_U8)
401-
// - 5 => I32 (SND_PCM_FORMAT_S32_LE)
402-
// - 6 => U32 (SND_PCM_FORMAT_U32_LE)
403-
// - 7 => I24 (SND_PCM_FORMAT_S24_LE)
404-
// - 8 => U24 (SND_PCM_FORMAT_U24_LE)
405-
// - 9 => F64 (SND_PCM_FORMAT_FLOAT64_LE)
406-
// - 10 => I8 (SND_PCM_FORMAT_S8)
391+
// - 1 => I8 (SND_PCM_FORMAT_S8)
392+
// - 2 => U8 (SND_PCM_FORMAT_U8)
393+
// - 3 => I16 (SND_PCM_FORMAT_S16_LE/BE)
394+
// - 4 => U16 (SND_PCM_FORMAT_U16_LE/BE)
395+
// - 5 => I24 (SND_PCM_FORMAT_S24_LE/BE)
396+
// - 6 => U24 (SND_PCM_FORMAT_U24_LE/BE)
397+
// - 7 => I32 (SND_PCM_FORMAT_S32_LE/BE)
398+
// - 8 => U32 (SND_PCM_FORMAT_U32_LE/BE)
399+
// - 9 => F32 (SND_PCM_FORMAT_FLOAT_LE/BE)
400+
// - 10 => F64 (SND_PCM_FORMAT_FLOAT64_LE/BE)
401+
// - 11 => DsdU8 (SND_PCM_FORMAT_DSD_U8)
402+
// - 12 => DsdU16 (SND_PCM_FORMAT_DSD_U16_LE/BE)
403+
// - 13 => DsdU32 (SND_PCM_FORMAT_DSD_U32_LE/BE)
407404
//
408405
// On non-Linux platforms, returns an empty bytes value.
409406
// On Linux, always returns at least 8 bytes. On error, `record_count` is 0 and `status` is set.
@@ -469,39 +466,58 @@ moonbit_bytes_t moon_cpal_alsa_supported_configs_bin(uint8_t *device_id_utf8,
469466
return alsa_status_only((int32_t)err);
470467
}
471468

472-
// Supported formats: keep in sync with the MoonBit stream builder.
473-
uint32_t fmt_tags[10];
469+
// Supported formats: keep in sync with upstream CPAL ALSA ordering.
470+
uint32_t fmt_tags[13];
474471
size_t fmt_count = 0;
475-
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_FLOAT_LE) == 0) {
472+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S8) == 0) {
476473
fmt_tags[fmt_count++] = 1u;
477474
}
478-
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S16_LE) == 0) {
475+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U8) == 0) {
479476
fmt_tags[fmt_count++] = 2u;
480477
}
481-
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U16_LE) == 0) {
478+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S16_LE) == 0 ||
479+
snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S16_BE) == 0) {
482480
fmt_tags[fmt_count++] = 3u;
483481
}
484-
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U8) == 0) {
482+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U16_LE) == 0 ||
483+
snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U16_BE) == 0) {
485484
fmt_tags[fmt_count++] = 4u;
486485
}
487-
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S32_LE) == 0) {
486+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S24_LE) == 0 ||
487+
snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S24_BE) == 0) {
488488
fmt_tags[fmt_count++] = 5u;
489489
}
490-
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U32_LE) == 0) {
490+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U24_LE) == 0 ||
491+
snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U24_BE) == 0) {
491492
fmt_tags[fmt_count++] = 6u;
492493
}
493-
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S24_LE) == 0) {
494+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S32_LE) == 0 ||
495+
snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S32_BE) == 0) {
494496
fmt_tags[fmt_count++] = 7u;
495497
}
496-
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U24_LE) == 0) {
498+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U32_LE) == 0 ||
499+
snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_U32_BE) == 0) {
497500
fmt_tags[fmt_count++] = 8u;
498501
}
499-
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_FLOAT64_LE) == 0) {
502+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_FLOAT_LE) == 0 ||
503+
snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_FLOAT_BE) == 0) {
500504
fmt_tags[fmt_count++] = 9u;
501505
}
502-
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_S8) == 0) {
506+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_FLOAT64_LE) == 0 ||
507+
snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_FLOAT64_BE) == 0) {
503508
fmt_tags[fmt_count++] = 10u;
504509
}
510+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_DSD_U8) == 0) {
511+
fmt_tags[fmt_count++] = 11u;
512+
}
513+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_DSD_U16_LE) == 0 ||
514+
snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_DSD_U16_BE) == 0) {
515+
fmt_tags[fmt_count++] = 12u;
516+
}
517+
if (snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_DSD_U32_LE) == 0 ||
518+
snd_pcm_hw_params_test_format(pcm, hw, SND_PCM_FORMAT_DSD_U32_BE) == 0) {
519+
fmt_tags[fmt_count++] = 13u;
520+
}
505521
if (fmt_count == 0) {
506522
snd_pcm_hw_params_free(hw);
507523
snd_pcm_close(pcm);

alsa/ffi_native.mbt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,19 @@ extern "C" fn alsa_devices_utf8() -> Bytes = "moon_cpal_alsa_devices_utf8"
3131
/// (sample_format_tag, channels, min_rate, max_rate, buffer_min, buffer_max)
3232
///
3333
/// sample_format_tag:
34-
/// - 1 => F32
35-
/// - 2 => I16
34+
/// - 1 => I8
35+
/// - 2 => U8
36+
/// - 3 => I16
37+
/// - 4 => U16
38+
/// - 5 => I24
39+
/// - 6 => U24
40+
/// - 7 => I32
41+
/// - 8 => U32
42+
/// - 9 => F32
43+
/// - 10 => F64
44+
/// - 11 => DsdU8
45+
/// - 12 => DsdU16
46+
/// - 13 => DsdU32
3647
///
3748
/// On non-Linux platforms this returns an empty bytes value.
3849
/// On Linux this always returns at least 8 bytes; on error, `record_count` is 0 and `status` is set.

alsa/host.mbt

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -303,16 +303,19 @@ fn i32_le_to_int(b : Bytes, off : Int) -> Int {
303303
///|
304304
fn sample_format_from_bin_tag(tag : Int) -> @core.SampleFormat? {
305305
match tag {
306-
1 => Some(@core.SampleFormat::F32)
307-
2 => Some(@core.SampleFormat::I16)
308-
3 => Some(@core.SampleFormat::U16)
309-
4 => Some(@core.SampleFormat::U8)
310-
5 => Some(@core.SampleFormat::I32)
311-
6 => Some(@core.SampleFormat::U32)
312-
7 => Some(@core.SampleFormat::I24)
313-
8 => Some(@core.SampleFormat::U24)
314-
9 => Some(@core.SampleFormat::F64)
315-
10 => Some(@core.SampleFormat::I8)
306+
1 => Some(@core.SampleFormat::I8)
307+
2 => Some(@core.SampleFormat::U8)
308+
3 => Some(@core.SampleFormat::I16)
309+
4 => Some(@core.SampleFormat::U16)
310+
5 => Some(@core.SampleFormat::I24)
311+
6 => Some(@core.SampleFormat::U24)
312+
7 => Some(@core.SampleFormat::I32)
313+
8 => Some(@core.SampleFormat::U32)
314+
9 => Some(@core.SampleFormat::F32)
315+
10 => Some(@core.SampleFormat::F64)
316+
11 => Some(@core.SampleFormat::DsdU8)
317+
12 => Some(@core.SampleFormat::DsdU16)
318+
13 => Some(@core.SampleFormat::DsdU32)
316319
_ => None
317320
}
318321
}

0 commit comments

Comments
 (0)