CCSDS 121.0-B-3 Lossless Data Compression (Rice/Golomb coding)
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement Float 9/7 wavelet, fix hcomp 16-bit bug, add interop skeleton

ocaml-idc (CCSDS 122.0-B):
- Implement CDF 9/7 lifting wavelet for lossy compression (was stubbed)
- 4-step lifting scheme with standard coefficients (α,β,γ,δ,K)
- Wavelet ID in header for format-aware decompression
- Tests: PSNR >40dB roundtrip, constant exact, 9/7 beats 5/3 on smooth

ocaml-hcomp (CCSDS 123.0-B):
- Fix 16-bit entropy coder truncation bug: escape values need bps+1 bits
(zigzag-mapped residuals can reach 2*(2^bps-1), caught by new fuzz test)
- 8 new tests: wire format (4 configs), spectral decorrelation, bps 1-16
- 2 new fuzz tests: roundtrip across all bps values, output length invariant

ocaml-rice (CCSDS 121.0-B):
- 9 new tests: wire format (5 exact byte checks), edge cases (4)
- 2 new fuzz tests: correlated data compresses well, determinism
- libaec interop test skeleton (test/interop/libaec/) — cross-validates
ocaml-rice against libaec via aec CLI, skips gracefully if not installed

+496
+43
fuzz/fuzz_rice.ml
··· 71 71 failf "roundtrip mismatch with block_size=%d" bs 72 72 end 73 73 74 + (** Compressed output for correlated data must be shorter than input. Correlated 75 + data is generated by starting from the fuzzed byte's first value and adding 76 + small deltas derived from subsequent bytes. *) 77 + let test_correlated_compresses_well buf = 78 + let max_len = 512 in 79 + let buf = 80 + if String.length buf > max_len then String.sub buf 0 max_len else buf 81 + in 82 + if String.length buf >= 64 then begin 83 + (* Build correlated data: start from first byte, add small deltas *) 84 + let n = String.length buf in 85 + let data = Bytes.make n '\000' in 86 + let v = ref (Char.code (String.get buf 0)) in 87 + for i = 0 to n - 1 do 88 + let delta = (Char.code (String.get buf i) mod 5) - 2 in 89 + v := max 0 (min 255 (!v + delta)); 90 + Bytes.set_uint8 data i !v 91 + done; 92 + let cfg = Rice.config ~block_size:16 ~bits_per_sample:8 () in 93 + let compressed = Rice.compress cfg data in 94 + if Bytes.length compressed >= Bytes.length data then 95 + failf "correlated data did not compress: input=%d compressed=%d" 96 + (Bytes.length data) (Bytes.length compressed) 97 + end 98 + 99 + (** Compression output length is deterministic: compressing the same input twice 100 + must produce identical output. *) 101 + let test_deterministic buf = 102 + let max_len = 1024 in 103 + let buf = 104 + if String.length buf > max_len then String.sub buf 0 max_len else buf 105 + in 106 + let data = Bytes.of_string buf in 107 + if Bytes.length data > 0 then begin 108 + let cfg = Rice.config ~block_size:16 ~bits_per_sample:8 () in 109 + let c1 = Rice.compress cfg data in 110 + let c2 = Rice.compress cfg data in 111 + if not (Bytes.equal c1 c2) then fail "compression is not deterministic" 112 + end 113 + 74 114 let suite = 75 115 ( "ccsds-121", 76 116 [ ··· 79 119 test_case "decompress no crash" [ bytes ] test_decompress_no_crash; 80 120 test_case "roundtrip block sizes" [ int; bytes ] 81 121 test_roundtrip_block_sizes; 122 + test_case "correlated compresses well" [ bytes ] 123 + test_correlated_compresses_well; 124 + test_case "deterministic" [ bytes ] test_deterministic; 82 125 ] )
+5
test/interop/libaec/dune
··· 1 + (test 2 + (name test_libaec) 3 + (libraries rice alcotest) 4 + (enabled_if 5 + (= %{env:INTEROP=false} true)))
+284
test/interop/libaec/test_libaec.ml
··· 1 + (** Interop tests: ocaml-rice vs libaec (aec CLI). 2 + 3 + Compresses data with ocaml-rice, decompresses with aec (and vice versa), 4 + then checks byte-for-byte equality of the round-tripped output. 5 + 6 + Requires the [aec] CLI from libaec to be installed. If [aec] is not found on 7 + PATH the tests are skipped rather than failed. *) 8 + 9 + let aec_available = 10 + lazy 11 + (let exit_code = Sys.command "aec --help >/dev/null 2>&1" in 12 + exit_code = 0) 13 + 14 + let skip_unless_aec () = if not (Lazy.force aec_available) then Alcotest.skip () 15 + 16 + (* -- Helpers ---------------------------------------------------------------- *) 17 + 18 + (** Write raw bytes to a temporary file, returning the path. *) 19 + let write_temp ~suffix data = 20 + let path = Filename.temp_file "rice_interop_" suffix in 21 + let oc = open_out_bin path in 22 + output_bytes oc data; 23 + close_out oc; 24 + path 25 + 26 + (** Read a file into bytes. *) 27 + let read_file path = 28 + let ic = open_in_bin path in 29 + let n = in_channel_length ic in 30 + let buf = Bytes.create n in 31 + really_input ic buf 0 n; 32 + close_in ic; 33 + buf 34 + 35 + (** Remove a list of temporary files. *) 36 + let cleanup paths = List.iter (fun p -> try Sys.remove p with _ -> ()) paths 37 + 38 + let bytes_eq = Alcotest.testable (Fmt.of_to_string Bytes.to_string) Bytes.equal 39 + 40 + (** Build the aec command line. 41 + 42 + aec CLI from libaec: 43 + - Compress: aec [-b block] [-j bits] [-r rsi] input output 44 + - Decompress: aec -d [-b block] [-j bits] [-r rsi] input output 45 + 46 + Flags: 47 + - [-b N]: block size (number of samples per block) 48 + - [-j N]: bits per sample 49 + - [-r N]: reference sample interval (we set equal to block_size) 50 + - [-n N]: byte-width of each sample (auto-derived from -j) 51 + - [-d]: decompress mode 52 + - [-m]: MSB first (big-endian samples, which matches our encoding) 53 + - [-t]: restrict encoded data (not needed for basic interop) *) 54 + let aec_cmd ?(decompress = false) ~block_size ~bits_per_sample input output = 55 + Printf.sprintf "aec%s -b %d -j %d -r %d -m %s %s" 56 + (if decompress then " -d" else "") 57 + block_size bits_per_sample block_size (* RSI = block_size *) 58 + input output 59 + 60 + (** Run a shell command, failing the test on non-zero exit. *) 61 + let run_cmd cmd = 62 + let exit_code = Sys.command (cmd ^ " 2>&1") in 63 + if exit_code <> 0 then 64 + Alcotest.failf "command failed (exit %d): %s" exit_code cmd 65 + 66 + (* -- Sample generators ------------------------------------------------------ *) 67 + 68 + (** Pack a list of samples into big-endian bytes at the given bit depth. *) 69 + let pack_samples ~bits_per_sample samples = 70 + let bps_bytes = (bits_per_sample + 7) / 8 in 71 + let n = List.length samples in 72 + let buf = Bytes.make (n * bps_bytes) '\000' in 73 + List.iteri 74 + (fun i s -> 75 + for j = bps_bytes - 1 downto 0 do 76 + Bytes.set_uint8 buf 77 + ((i * bps_bytes) + (bps_bytes - 1 - j)) 78 + ((s lsr (j * 8)) land 0xFF) 79 + done) 80 + samples; 81 + buf 82 + 83 + (** Generate deterministic pseudo-random samples. *) 84 + let generate_prng_samples ~bits_per_sample ~count ~seed = 85 + let state = Random.State.make [| seed |] in 86 + let max_val = 1 lsl min bits_per_sample 30 in 87 + List.init count (fun _ -> Random.State.int state max_val) 88 + 89 + (** Ramp: [0, 1, 2, ..., count-1], masked to bit depth. *) 90 + let generate_ramp ~bits_per_sample ~count = 91 + let mask = (1 lsl bits_per_sample) - 1 in 92 + List.init count (fun i -> i land mask) 93 + 94 + (** Constant value. *) 95 + let generate_constant ~count value = List.init count (fun _ -> value) 96 + 97 + (* -- Test body -------------------------------------------------------------- *) 98 + 99 + (** Compress with ocaml-rice, decompress with aec, compare. *) 100 + let test_rice_compress_aec_decompress ~block_size ~bits_per_sample raw_data () = 101 + skip_unless_aec (); 102 + let cfg = Rice.config ~block_size ~bits_per_sample () in 103 + let compressed = Rice.compress cfg raw_data in 104 + let compressed_path = write_temp ~suffix:".rz" compressed in 105 + let decompressed_path = Filename.temp_file "rice_interop_" ".raw" in 106 + let cmd = 107 + aec_cmd ~decompress:true ~block_size ~bits_per_sample compressed_path 108 + decompressed_path 109 + in 110 + Fun.protect 111 + ~finally:(fun () -> cleanup [ compressed_path; decompressed_path ]) 112 + (fun () -> 113 + run_cmd cmd; 114 + let result = read_file decompressed_path in 115 + Alcotest.(check bytes_eq) 116 + (Printf.sprintf "rice->aec bs=%d bps=%d" block_size bits_per_sample) 117 + raw_data result) 118 + 119 + (** Compress with aec, decompress with ocaml-rice, compare. *) 120 + let test_aec_compress_rice_decompress ~block_size ~bits_per_sample raw_data () = 121 + skip_unless_aec (); 122 + let cfg = Rice.config ~block_size ~bits_per_sample () in 123 + let raw_path = write_temp ~suffix:".raw" raw_data in 124 + let compressed_path = Filename.temp_file "rice_interop_" ".rz" in 125 + let cmd = 126 + aec_cmd ~decompress:false ~block_size ~bits_per_sample raw_path 127 + compressed_path 128 + in 129 + Fun.protect 130 + ~finally:(fun () -> cleanup [ raw_path; compressed_path ]) 131 + (fun () -> 132 + run_cmd cmd; 133 + let compressed = read_file compressed_path in 134 + match Rice.decompress cfg compressed with 135 + | Error msg -> 136 + Alcotest.failf "rice decompress failed (aec compressed): %s" msg 137 + | Ok result -> 138 + Alcotest.(check bytes_eq) 139 + (Printf.sprintf "aec->rice bs=%d bps=%d" block_size bits_per_sample) 140 + raw_data result) 141 + 142 + (** Full round-trip: rice compress -> aec decompress -> aec compress -> rice 143 + decompress, comparing at each stage. *) 144 + let test_full_roundtrip ~block_size ~bits_per_sample raw_data () = 145 + skip_unless_aec (); 146 + let cfg = Rice.config ~block_size ~bits_per_sample () in 147 + (* Step 1: compress with rice *) 148 + let rice_compressed = Rice.compress cfg raw_data in 149 + let rice_rz = write_temp ~suffix:".rice.rz" rice_compressed in 150 + let aec_decompressed_path = Filename.temp_file "rice_interop_" ".aec.raw" in 151 + let aec_recompressed_path = Filename.temp_file "rice_interop_" ".aec.rz" in 152 + Fun.protect 153 + ~finally:(fun () -> 154 + cleanup [ rice_rz; aec_decompressed_path; aec_recompressed_path ]) 155 + (fun () -> 156 + (* Step 2: decompress with aec *) 157 + run_cmd 158 + (aec_cmd ~decompress:true ~block_size ~bits_per_sample rice_rz 159 + aec_decompressed_path); 160 + let aec_raw = read_file aec_decompressed_path in 161 + Alcotest.(check bytes_eq) 162 + "rice->aec decompression matches original" raw_data aec_raw; 163 + (* Step 3: recompress with aec *) 164 + run_cmd 165 + (aec_cmd ~decompress:false ~block_size ~bits_per_sample 166 + aec_decompressed_path aec_recompressed_path); 167 + let aec_compressed = read_file aec_recompressed_path in 168 + (* Step 4: decompress with rice *) 169 + match Rice.decompress cfg aec_compressed with 170 + | Error msg -> 171 + Alcotest.failf "rice decompress of aec-compressed data failed: %s" msg 172 + | Ok result -> 173 + Alcotest.(check bytes_eq) 174 + "full round-trip matches original" raw_data result) 175 + 176 + (* -- Configurations --------------------------------------------------------- *) 177 + 178 + type test_config = { block_size : int; bits_per_sample : int } 179 + 180 + let configs = 181 + [ 182 + { block_size = 8; bits_per_sample = 8 }; 183 + { block_size = 16; bits_per_sample = 8 }; 184 + { block_size = 64; bits_per_sample = 8 }; 185 + { block_size = 8; bits_per_sample = 16 }; 186 + { block_size = 16; bits_per_sample = 16 }; 187 + { block_size = 64; bits_per_sample = 16 }; 188 + { block_size = 8; bits_per_sample = 32 }; 189 + { block_size = 16; bits_per_sample = 32 }; 190 + { block_size = 64; bits_per_sample = 32 }; 191 + ] 192 + 193 + let label c = Printf.sprintf "bs=%d/bps=%d" c.block_size c.bits_per_sample 194 + 195 + (** Generate test data for a given config. Multiple patterns concatenated. *) 196 + let make_test_data c = 197 + let count = c.block_size * 4 in 198 + (* 4 full blocks *) 199 + let bps = c.bits_per_sample in 200 + let ramp = generate_ramp ~bits_per_sample:bps ~count in 201 + let constant = generate_constant ~count 42 in 202 + let prng = generate_prng_samples ~bits_per_sample:bps ~count ~seed:12345 in 203 + ( pack_samples ~bits_per_sample:bps ramp, 204 + pack_samples ~bits_per_sample:bps constant, 205 + pack_samples ~bits_per_sample:bps prng ) 206 + 207 + (* -- Runner ----------------------------------------------------------------- *) 208 + 209 + let () = 210 + let rice_to_aec = 211 + List.concat_map 212 + (fun c -> 213 + let ramp, constant, prng = make_test_data c in 214 + [ 215 + Alcotest.test_case 216 + (Printf.sprintf "ramp %s" (label c)) 217 + `Quick 218 + (test_rice_compress_aec_decompress ~block_size:c.block_size 219 + ~bits_per_sample:c.bits_per_sample ramp); 220 + Alcotest.test_case 221 + (Printf.sprintf "constant %s" (label c)) 222 + `Quick 223 + (test_rice_compress_aec_decompress ~block_size:c.block_size 224 + ~bits_per_sample:c.bits_per_sample constant); 225 + Alcotest.test_case 226 + (Printf.sprintf "prng %s" (label c)) 227 + `Quick 228 + (test_rice_compress_aec_decompress ~block_size:c.block_size 229 + ~bits_per_sample:c.bits_per_sample prng); 230 + ]) 231 + configs 232 + in 233 + let aec_to_rice = 234 + List.concat_map 235 + (fun c -> 236 + let ramp, constant, prng = make_test_data c in 237 + [ 238 + Alcotest.test_case 239 + (Printf.sprintf "ramp %s" (label c)) 240 + `Quick 241 + (test_aec_compress_rice_decompress ~block_size:c.block_size 242 + ~bits_per_sample:c.bits_per_sample ramp); 243 + Alcotest.test_case 244 + (Printf.sprintf "constant %s" (label c)) 245 + `Quick 246 + (test_aec_compress_rice_decompress ~block_size:c.block_size 247 + ~bits_per_sample:c.bits_per_sample constant); 248 + Alcotest.test_case 249 + (Printf.sprintf "prng %s" (label c)) 250 + `Quick 251 + (test_aec_compress_rice_decompress ~block_size:c.block_size 252 + ~bits_per_sample:c.bits_per_sample prng); 253 + ]) 254 + configs 255 + in 256 + let full_roundtrip = 257 + List.concat_map 258 + (fun c -> 259 + let ramp, constant, prng = make_test_data c in 260 + [ 261 + Alcotest.test_case 262 + (Printf.sprintf "ramp %s" (label c)) 263 + `Quick 264 + (test_full_roundtrip ~block_size:c.block_size 265 + ~bits_per_sample:c.bits_per_sample ramp); 266 + Alcotest.test_case 267 + (Printf.sprintf "constant %s" (label c)) 268 + `Quick 269 + (test_full_roundtrip ~block_size:c.block_size 270 + ~bits_per_sample:c.bits_per_sample constant); 271 + Alcotest.test_case 272 + (Printf.sprintf "prng %s" (label c)) 273 + `Quick 274 + (test_full_roundtrip ~block_size:c.block_size 275 + ~bits_per_sample:c.bits_per_sample prng); 276 + ]) 277 + configs 278 + in 279 + Alcotest.run "rice-libaec" 280 + [ 281 + ("rice-compress/aec-decompress", rice_to_aec); 282 + ("aec-compress/rice-decompress", aec_to_rice); 283 + ("full-roundtrip", full_roundtrip); 284 + ]
+164
test/test_rice.ml
··· 204 204 Alcotest.(check bytes_eq) "unit_delay roundtrip" data result_ud; 205 205 Alcotest.(check bytes_eq) "neighborhood roundtrip" data result_nb 206 206 207 + (* -- Wire format tests ---------------------------------------------------- *) 208 + 209 + let bytes_of_list l = 210 + let b = Bytes.make (List.length l) '\000' in 211 + List.iteri (fun i v -> Bytes.set_uint8 b i v) l; 212 + b 213 + 214 + (** Wire format: compress [0,1,2,3,4,5,6,7] with block_size=8, bps=8 and check 215 + the exact output bytes. This catches encoder bugs that a roundtrip test 216 + would miss (e.g., both encoder and decoder sharing the same wrong mapping). 217 + *) 218 + let test_wire_format_ramp () = 219 + let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 220 + let data = pack_8 [ 0; 1; 2; 3; 4; 5; 6; 7 ] in 221 + let compressed = Rice.compress cfg data in 222 + let expected = 223 + bytes_of_list [ 0x00; 0x00; 0x00; 0x08; 0x0a; 0x49; 0x24; 0x80 ] 224 + in 225 + Alcotest.(check bytes_eq) "ramp wire format" expected compressed 226 + 227 + (** Wire format: single sample 42. *) 228 + let test_wire_format_single_42 () = 229 + let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 230 + let data = pack_8 [ 42 ] in 231 + let compressed = Rice.compress cfg data in 232 + let expected = bytes_of_list [ 0x00; 0x00; 0x00; 0x01; 0x55; 0x40 ] in 233 + Alcotest.(check bytes_eq) "single 42 wire format" expected compressed 234 + 235 + (** Wire format: single sample 255 (max for 8-bit). *) 236 + let test_wire_format_single_255 () = 237 + let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 238 + let data = pack_8 [ 255 ] in 239 + let compressed = Rice.compress cfg data in 240 + let expected = bytes_of_list [ 0x00; 0x00; 0x00; 0x01; 0x77; 0xf8 ] in 241 + Alcotest.(check bytes_eq) "single 255 wire format" expected compressed 242 + 243 + (** Wire format: alternating 0/255 pattern. *) 244 + let test_wire_format_alternating () = 245 + let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 246 + let data = pack_8 [ 0; 255; 0; 255; 0; 255; 0; 255 ] in 247 + let compressed = Rice.compress cfg data in 248 + let expected = 249 + bytes_of_list 250 + [ 251 + 0x00; 252 + 0x00; 253 + 0x00; 254 + 0x08; 255 + 0x78; 256 + 0x07; 257 + 0xfb; 258 + 0xfd; 259 + 0xfe; 260 + 0xff; 261 + 0x7f; 262 + 0xbf; 263 + 0xdf; 264 + 0xe0; 265 + ] 266 + in 267 + Alcotest.(check bytes_eq) "alternating 0/255 wire format" expected compressed 268 + 269 + (** Wire format: all-max (255) block. *) 270 + let test_wire_format_all_max () = 271 + let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 272 + let data = Bytes.make 8 '\xff' in 273 + let compressed = Rice.compress cfg data in 274 + let expected = 275 + bytes_of_list 276 + [ 0x00; 0x00; 0x00; 0x08; 0x40; 0x0f; 0xf8; 0x42; 0x10; 0x84; 0x20 ] 277 + in 278 + Alcotest.(check bytes_eq) "all-max wire format" expected compressed 279 + 280 + (* -- Edge case tests ------------------------------------------------------ *) 281 + 282 + (** Edge case: single sample roundtrip for all bit depths. *) 283 + let test_single_sample_all_bps () = 284 + List.iter 285 + (fun bps -> 286 + let max_val = (1 lsl min bps 16) - 1 in 287 + let bps_bytes = (bps + 7) / 8 in 288 + let cfg = Rice.config ~block_size:8 ~bits_per_sample:bps () in 289 + (* Single sample with value 0 *) 290 + let buf0 = Bytes.make bps_bytes '\000' in 291 + let r0 = roundtrip cfg buf0 in 292 + Alcotest.(check bytes_eq) (Printf.sprintf "single 0, bps=%d" bps) buf0 r0; 293 + (* Single sample with max value *) 294 + let bufmax = Bytes.make bps_bytes '\000' in 295 + (* Write max_val big-endian *) 296 + for j = bps_bytes - 1 downto 0 do 297 + Bytes.set_uint8 bufmax 298 + (bps_bytes - 1 - j) 299 + ((max_val lsr (j * 8)) land 0xFF) 300 + done; 301 + let rmax = roundtrip cfg bufmax in 302 + Alcotest.(check bytes_eq) 303 + (Printf.sprintf "single max, bps=%d" bps) 304 + bufmax rmax) 305 + [ 1; 2; 4; 8; 12; 16; 24; 32 ] 306 + 307 + (** Edge case: maximum sample value (2^bps - 1) for a block. *) 308 + let test_max_value_block () = 309 + let cfg = Rice.config ~block_size:8 ~bits_per_sample:8 () in 310 + let data = Bytes.make 8 '\xff' in 311 + let result = roundtrip cfg data in 312 + Alcotest.(check bytes_eq) "all-max-8 roundtrip" data result; 313 + (* 16-bit *) 314 + let cfg16 = Rice.config ~block_size:8 ~bits_per_sample:16 () in 315 + let data16 = Bytes.make 16 '\xff' in 316 + let result16 = roundtrip cfg16 data16 in 317 + Alcotest.(check bytes_eq) "all-max-16 roundtrip" data16 result16 318 + 319 + (** Edge case: alternating 0 / max pattern at various bit depths. *) 320 + let test_alternating_0_max () = 321 + List.iter 322 + (fun bps -> 323 + let max_val = (1 lsl min bps 16) - 1 in 324 + let bps_bytes = (bps + 7) / 8 in 325 + let n = 16 in 326 + let cfg = Rice.config ~block_size:8 ~bits_per_sample:bps () in 327 + let buf = Bytes.make (n * bps_bytes) '\000' in 328 + for i = 0 to n - 1 do 329 + let v = if i mod 2 = 0 then 0 else max_val in 330 + for j = bps_bytes - 1 downto 0 do 331 + Bytes.set_uint8 buf 332 + ((i * bps_bytes) + (bps_bytes - 1 - j)) 333 + ((v lsr (j * 8)) land 0xFF) 334 + done 335 + done; 336 + let result = roundtrip cfg buf in 337 + Alcotest.(check bytes_eq) 338 + (Printf.sprintf "alternating 0/max bps=%d" bps) 339 + buf result) 340 + [ 8; 16 ] 341 + 342 + (** Edge case: data that does not fill a complete block. *) 343 + let test_partial_block () = 344 + let cfg = Rice.config ~block_size:16 ~bits_per_sample:8 () in 345 + (* 5 samples: less than one block *) 346 + let data = pack_8 [ 10; 20; 30; 40; 50 ] in 347 + let result = roundtrip cfg data in 348 + Alcotest.(check bytes_eq) "partial block roundtrip" data result; 349 + (* 17 samples: one full block + 1 remainder *) 350 + let data2 = pack_8 (List.init 17 (fun i -> i * 3 mod 256)) in 351 + let result2 = roundtrip cfg data2 in 352 + Alcotest.(check bytes_eq) "block+1 roundtrip" data2 result2 353 + 207 354 (* -- Runner --------------------------------------------------------------- *) 208 355 209 356 let () = ··· 222 369 Alcotest.test_case "empty input" `Quick test_empty; 223 370 Alcotest.test_case "single sample" `Quick test_single_sample; 224 371 Alcotest.test_case "predictors" `Quick test_predictors; 372 + ] ); 373 + ( "wire-format", 374 + [ 375 + Alcotest.test_case "ramp 0..7" `Quick test_wire_format_ramp; 376 + Alcotest.test_case "single 42" `Quick test_wire_format_single_42; 377 + Alcotest.test_case "single 255" `Quick test_wire_format_single_255; 378 + Alcotest.test_case "alternating 0/255" `Quick 379 + test_wire_format_alternating; 380 + Alcotest.test_case "all max" `Quick test_wire_format_all_max; 381 + ] ); 382 + ( "edge-cases", 383 + [ 384 + Alcotest.test_case "single sample all bps" `Quick 385 + test_single_sample_all_bps; 386 + Alcotest.test_case "max value block" `Quick test_max_value_block; 387 + Alcotest.test_case "alternating 0/max" `Quick test_alternating_0_max; 388 + Alcotest.test_case "partial block" `Quick test_partial_block; 225 389 ] ); 226 390 ]