Skip to content

Commit ebe2ca7

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 6eb118f commit ebe2ca7

2 files changed

Lines changed: 88 additions & 3 deletions

File tree

.github/workflows/ci.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,14 @@ jobs:
4949
- name: Install SDK packages
5050
run: sdkmanager "platforms;android-35" "build-tools;36.0.0"
5151
- name: Download shared conformance vectors
52-
run: curl -sSfL -o vectors.jsonl https://raw.githubusercontent.com/firechip/cobs-conformance/v1.0.0/vectors/vectors.jsonl
52+
run: |
53+
base=https://raw.githubusercontent.com/firechip/cobs-conformance/v1.1.0/vectors
54+
curl -sSfL -o vectors.jsonl "$base/vectors.jsonl"
55+
curl -sSfL -o sentinel.jsonl "$base/sentinel.jsonl"
56+
curl -sSfL -o errors.jsonl "$base/errors.jsonl"
5357
- name: Check conformance
5458
env:
5559
COBS_CONFORMANCE_VECTORS: ${{ github.workspace }}/vectors.jsonl
60+
COBS_CONFORMANCE_SENTINEL: ${{ github.workspace }}/sentinel.jsonl
61+
COBS_CONFORMANCE_ERRORS: ${{ github.workspace }}/errors.jsonl
5662
run: ./gradlew :cobs:testDebugUnitTest --tests "dev.firechip.cobs.ConformanceTest" --console=plain

cobs/src/test/kotlin/dev/firechip/cobs/ConformanceTest.kt

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@ package dev.firechip.cobs
55

66
import java.io.File
77
import org.junit.Assert.assertArrayEquals
8+
import org.junit.Assert.assertFalse
89
import org.junit.Assert.assertTrue
10+
import org.junit.Assert.fail
911
import org.junit.Assume.assumeTrue
1012
import org.junit.Test
1113

1214
/**
1315
* Verifies this library against the shared conformance vectors from
1416
* https://github.com/firechip/cobs-conformance
1517
*
16-
* Skipped unless the `COBS_CONFORMANCE_VECTORS` environment variable points to a
17-
* downloaded `vectors.jsonl` (CI sets this; local `./gradlew test` skips it).
18+
* Each check is skipped unless the corresponding environment variable points to a
19+
* downloaded JSONL file (CI sets these; local `./gradlew test` skips them):
20+
* - `COBS_CONFORMANCE_VECTORS` -> `vectors.jsonl` {decoded, cobs, cobsr}
21+
* - `COBS_CONFORMANCE_SENTINEL` -> `sentinel.jsonl` {decoded, sentinel, cobs, cobsr}
22+
* - `COBS_CONFORMANCE_ERRORS` -> `errors.jsonl` {encoded, cobs, cobsr}
1823
*/
1924
class ConformanceTest {
2025

@@ -52,4 +57,78 @@ class ConformanceTest {
5257
}
5358
assertTrue("no vectors checked", count > 0)
5459
}
60+
61+
@Test
62+
fun conformsToSentinelVectors() {
63+
val path = System.getenv("COBS_CONFORMANCE_SENTINEL")
64+
assumeTrue("set COBS_CONFORMANCE_SENTINEL to run", !path.isNullOrBlank())
65+
66+
val regex = Regex(
67+
"\"decoded\":\"([0-9a-f]*)\",\"sentinel\":\"([0-9a-f]*)\"," +
68+
"\"cobs\":\"([0-9a-f]*)\",\"cobsr\":\"([0-9a-f]*)\"",
69+
)
70+
var count = 0
71+
File(path!!).forEachLine { line ->
72+
if (line.isBlank()) return@forEachLine
73+
val match = regex.find(line) ?: error("malformed sentinel line: $line")
74+
val (d, s, c, cr) = match.destructured
75+
val data = hex(d)
76+
val sentinel = hex(s)[0]
77+
val cobs = hex(c)
78+
val cobsr = hex(cr)
79+
assertArrayEquals("cobs sentinel $s encode $d", cobs, Cobs.encodeWithSentinel(data, sentinel))
80+
assertArrayEquals("cobsr sentinel $s encode $d", cobsr, Cobsr.encodeWithSentinel(data, sentinel))
81+
assertArrayEquals("cobs sentinel $s decode $c", data, Cobs.decodeWithSentinel(cobs, sentinel))
82+
assertArrayEquals("cobsr sentinel $s decode $cr", data, Cobsr.decodeWithSentinel(cobsr, sentinel))
83+
assertFalse("sentinel $s must not appear in cobs $c", cobs.contains(sentinel))
84+
assertFalse("sentinel $s must not appear in cobsr $cr", cobsr.contains(sentinel))
85+
count++
86+
}
87+
assertTrue("no sentinel vectors checked", count > 0)
88+
}
89+
90+
@Test
91+
fun conformsToErrorVectors() {
92+
val path = System.getenv("COBS_CONFORMANCE_ERRORS")
93+
assumeTrue("set COBS_CONFORMANCE_ERRORS to run", !path.isNullOrBlank())
94+
95+
val regex = Regex(
96+
"\"encoded\":\"([0-9a-f]*)\",\"cobs\":(null|\"[0-9a-f]*\")," +
97+
"\"cobsr\":(null|\"[0-9a-f]*\")",
98+
)
99+
var count = 0
100+
File(path!!).forEachLine { line ->
101+
if (line.isBlank()) return@forEachLine
102+
val match = regex.find(line) ?: error("malformed error line: $line")
103+
val (e, c, cr) = match.destructured
104+
val encoded = hex(e)
105+
checkDecode("cobs decode $e", c, encoded) { Cobs.decode(it) }
106+
checkDecode("cobsr decode $e", cr, encoded) { Cobsr.decode(it) }
107+
count++
108+
}
109+
assertTrue("no error vectors checked", count > 0)
110+
}
111+
112+
/**
113+
* Asserts that [decode] applied to [encoded] either fails (when [field] is the
114+
* JSON literal `null`) or produces the hex payload quoted in [field].
115+
*/
116+
private fun checkDecode(
117+
label: String,
118+
field: String,
119+
encoded: ByteArray,
120+
decode: (ByteArray) -> ByteArray,
121+
) {
122+
if (field == "null") {
123+
try {
124+
decode(encoded)
125+
fail("$label: expected decode to fail but it succeeded")
126+
} catch (_: CobsDecodeException) {
127+
// expected: this input MUST fail to decode
128+
}
129+
} else {
130+
val expected = hex(field.substring(1, field.length - 1))
131+
assertArrayEquals(label, expected, decode(encoded))
132+
}
133+
}
55134
}

0 commit comments

Comments
 (0)