Skip to content

Commit e8937a3

Browse files
committed
test: verify sentinel + decode-error conformance vectors
Harness + CI now check sentinel.jsonl and errors.jsonl at vectors v1.1.0.
1 parent 75d4162 commit e8937a3

2 files changed

Lines changed: 129 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,14 @@ jobs:
6565
- uses: actions/checkout@v7
6666
- uses: dtolnay/rust-toolchain@stable
6767
- name: Download shared conformance vectors
68-
run: curl -sSfL -o vectors.jsonl https://raw.githubusercontent.com/firechip/cobs-conformance/v1.0.0/vectors/vectors.jsonl
68+
run: |
69+
base=https://raw.githubusercontent.com/firechip/cobs-conformance/v1.1.0/vectors
70+
curl -sSfL -o vectors.jsonl "$base/vectors.jsonl"
71+
curl -sSfL -o sentinel.jsonl "$base/sentinel.jsonl"
72+
curl -sSfL -o errors.jsonl "$base/errors.jsonl"
6973
- name: Check conformance
7074
env:
7175
COBS_CONFORMANCE_VECTORS: ${{ github.workspace }}/vectors.jsonl
72-
run: cargo test --test conformance -- --nocapture
76+
COBS_CONFORMANCE_SENTINEL: ${{ github.workspace }}/sentinel.jsonl
77+
COBS_CONFORMANCE_ERRORS: ${{ github.workspace }}/errors.jsonl
78+
run: cargo test --test conformance --all-features -- --nocapture

tests/conformance.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,22 @@ fn field<'a>(line: &'a str, key: &str) -> &'a str {
2323
&rest[..end]
2424
}
2525

26+
/// Extracts `"<key>":<value>` where the value is either a quoted hex string or
27+
/// the JSON literal `null`. Returns `None` for `null` (i.e. decode must fail).
28+
fn field_opt<'a>(line: &'a str, key: &str) -> Option<&'a str> {
29+
let pat = format!("\"{key}\":");
30+
let start = line.find(&pat).expect("missing key") + pat.len();
31+
let rest = &line[start..];
32+
if rest.starts_with("null") {
33+
return None;
34+
}
35+
let rest = rest
36+
.strip_prefix('"')
37+
.expect("expected quoted value or null");
38+
let end = rest.find('"').expect("unterminated value");
39+
Some(&rest[..end])
40+
}
41+
2642
#[test]
2743
fn conforms_to_shared_vectors() {
2844
let path = match std::env::var("COBS_CONFORMANCE_VECTORS") {
@@ -61,3 +77,108 @@ fn conforms_to_shared_vectors() {
6177
assert!(count > 0, "no vectors found in {path}");
6278
eprintln!("conformance: {count} vectors verified");
6379
}
80+
81+
#[test]
82+
fn conforms_to_sentinel_vectors() {
83+
let path = match std::env::var("COBS_CONFORMANCE_SENTINEL") {
84+
Ok(p) if !p.is_empty() => p,
85+
_ => {
86+
eprintln!("skipping: set COBS_CONFORMANCE_SENTINEL to run");
87+
return;
88+
}
89+
};
90+
91+
let file = std::fs::File::open(&path).expect("open sentinel file");
92+
let mut count = 0usize;
93+
for line in BufReader::new(file).lines() {
94+
let line = line.expect("read line");
95+
if line.trim().is_empty() {
96+
continue;
97+
}
98+
let decoded = from_hex(field(&line, "decoded"));
99+
let sentinel = from_hex(field(&line, "sentinel"));
100+
assert_eq!(sentinel.len(), 1, "sentinel must be a single byte");
101+
let sentinel = sentinel[0];
102+
let e_cobs = from_hex(field(&line, "cobs"));
103+
let e_cobsr = from_hex(field(&line, "cobsr"));
104+
105+
assert_eq!(
106+
cobs::encode_to_vec_with_sentinel(&decoded, sentinel),
107+
e_cobs,
108+
"cobs encode_with_sentinel"
109+
);
110+
assert_eq!(
111+
cobsr::encode_to_vec_with_sentinel(&decoded, sentinel),
112+
e_cobsr,
113+
"cobsr encode_with_sentinel"
114+
);
115+
assert_eq!(
116+
cobs::decode_to_vec_with_sentinel(&e_cobs, sentinel).unwrap(),
117+
decoded,
118+
"cobs decode_with_sentinel"
119+
);
120+
assert_eq!(
121+
cobsr::decode_to_vec_with_sentinel(&e_cobsr, sentinel).unwrap(),
122+
decoded,
123+
"cobsr decode_with_sentinel"
124+
);
125+
assert!(
126+
!e_cobs.contains(&sentinel),
127+
"sentinel byte must not appear in cobs output"
128+
);
129+
assert!(
130+
!e_cobsr.contains(&sentinel),
131+
"sentinel byte must not appear in cobsr output"
132+
);
133+
count += 1;
134+
}
135+
assert!(count > 0, "no sentinel vectors found in {path}");
136+
eprintln!("conformance: {count} sentinel vectors verified");
137+
}
138+
139+
#[test]
140+
fn conforms_to_error_vectors() {
141+
let path = match std::env::var("COBS_CONFORMANCE_ERRORS") {
142+
Ok(p) if !p.is_empty() => p,
143+
_ => {
144+
eprintln!("skipping: set COBS_CONFORMANCE_ERRORS to run");
145+
return;
146+
}
147+
};
148+
149+
let file = std::fs::File::open(&path).expect("open errors file");
150+
let mut count = 0usize;
151+
for line in BufReader::new(file).lines() {
152+
let line = line.expect("read line");
153+
if line.trim().is_empty() {
154+
continue;
155+
}
156+
let encoded = from_hex(field(&line, "encoded"));
157+
158+
match field_opt(&line, "cobs") {
159+
Some(hex) => assert_eq!(
160+
cobs::decode_to_vec(&encoded).unwrap(),
161+
from_hex(hex),
162+
"cobs decode"
163+
),
164+
None => assert!(
165+
cobs::decode_to_vec(&encoded).is_err(),
166+
"cobs decode must fail"
167+
),
168+
}
169+
match field_opt(&line, "cobsr") {
170+
Some(hex) => assert_eq!(
171+
cobsr::decode_to_vec(&encoded).unwrap(),
172+
from_hex(hex),
173+
"cobsr decode"
174+
),
175+
None => assert!(
176+
cobsr::decode_to_vec(&encoded).is_err(),
177+
"cobsr decode must fail"
178+
),
179+
}
180+
count += 1;
181+
}
182+
assert!(count > 0, "no error vectors found in {path}");
183+
eprintln!("conformance: {count} error vectors verified");
184+
}

0 commit comments

Comments
 (0)