Files
clawhdf5/crates/clawhdf5-format/src/selection.rs
T
osobhandClaude Fable 5.1 bbe1baa208 ci: lint all targets, run interop suites for real, compile benches
- clippy --all-targets plus a clawhdf5-format feature matrix (parallel, lz4,
  zstd, pcodec, fast-checksum); fix the accumulated lint backlog in test,
  bench and feature-gated code (no behaviour changes).
- Install python3 + h5py/numpy/netCDF4/xarray in the CI container and set
  CLAWHDF5_REQUIRE_INTEROP=1, which makes a missing interop dependency a test
  failure. Every h5py/netCDF4 interop test used to skip silently in CI. Run
  the #[ignore]d writer_h5py_tests suite explicitly.
- cargo bench --no-run so benches can't rot; fix bench.rs and memory_bench.rs,
  which no longer compiled against the current strategy/consolidation APIs.
- Optional fuzz smoke run via CLAWHDF5_FUZZ_SECONDS.
- CHANGELOG and docs/known-issues.md updated.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-19 05:36:22 -07:00

744 lines
27 KiB
Rust

//! Hyperslab and point selection for partial dataset I/O.
//!
//! A [`Selection`] describes which elements of a dataset to read or write.
//! The most common form is a hyperslab — a regular, strided sub-region of
//! the dataspace.
//!
//! # Example
//!
//! ```ignore
//! use clawhdf5_format::selection::Selection;
//!
//! // Select rows 20..30, columns 40..60 from a 2D dataset
//! let sel = Selection::slice(&[20..30, 40..60]);
//! assert_eq!(sel.num_elements(&[100, 100]), 200); // 10 * 20
//! ```
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use core::ops::Range;
use crate::error::FormatError;
/// A selection describing which elements of a dataset to access.
#[derive(Debug, Clone, PartialEq)]
pub enum Selection {
/// Select all elements (equivalent to the entire dataspace).
All,
/// Select no elements.
None,
/// A regular hyperslab selection defined by start, stride, count, and block.
///
/// For each dimension:
/// - `start[d]` — first element index
/// - `stride[d]` — step between blocks (must be >= block[d])
/// - `count[d]` — number of blocks
/// - `block[d]` — number of consecutive elements per block
///
/// When stride == block (or stride is 1 and block is 1), this reduces
/// to a simple contiguous slice.
Hyperslab {
start: Vec<u64>,
stride: Vec<u64>,
count: Vec<u64>,
block: Vec<u64>,
},
/// Select individual points by coordinate.
Points(Vec<Vec<u64>>),
}
impl Selection {
/// Create a simple contiguous hyperslab from ranges (one per dimension).
///
/// This is equivalent to a hyperslab with stride=1 and block=1.
pub fn slice(ranges: &[Range<u64>]) -> Self {
let rank = ranges.len();
let mut start = Vec::with_capacity(rank);
let mut count = Vec::with_capacity(rank);
for r in ranges {
debug_assert!(
r.end >= r.start,
"Selection::slice: range end ({}) < start ({})",
r.end,
r.start,
);
start.push(r.start);
count.push(r.end.saturating_sub(r.start));
}
Selection::Hyperslab {
start,
stride: vec![1; rank],
count,
block: vec![1; rank],
}
}
/// Number of selected elements for a given dataspace shape.
pub fn num_elements(&self, dims: &[u64]) -> u64 {
match self {
Selection::All => dims.iter().product(),
Selection::None => 0,
Selection::Hyperslab { count, block, .. } => count
.iter()
.zip(block.iter())
.map(|(&c, &b)| c * b)
.product(),
Selection::Points(pts) => pts.len() as u64,
}
}
/// The rank (number of dimensions) of this selection.
pub fn rank(&self) -> Option<usize> {
match self {
Selection::All | Selection::None => Option::None,
Selection::Hyperslab { start, .. } => Some(start.len()),
Selection::Points(pts) => pts.first().map(|p| p.len()),
}
}
/// The shape of the selected region (output dimensions).
///
/// For hyperslabs, this is `count[d] * block[d]` per dimension.
/// For `All`, returns the dataspace shape. For `None`, returns empty.
pub fn output_shape(&self, dims: &[u64]) -> Vec<u64> {
match self {
Selection::All => dims.to_vec(),
Selection::None => vec![],
Selection::Hyperslab { count, block, .. } => count
.iter()
.zip(block.iter())
.map(|(&c, &b)| c * b)
.collect(),
Selection::Points(pts) => vec![pts.len() as u64],
}
}
/// Check whether a chunk at the given offset (with given chunk dimensions)
/// intersects this selection.
///
/// Returns `true` if any element in the chunk overlaps with the selection.
pub fn intersects_chunk(&self, chunk_offset: &[u64], chunk_dims: &[u64]) -> bool {
match self {
Selection::All => true,
Selection::None => false,
Selection::Hyperslab {
start,
stride,
count,
block,
} => {
// For each dimension, check if the chunk range overlaps the hyperslab range
for d in 0..start.len() {
let chunk_start = chunk_offset[d];
let chunk_end = chunk_start + chunk_dims[d];
// Compute the full extent of the hyperslab in this dimension
let sel_start = start[d];
let sel_end = if count[d] == 0 {
sel_start
} else {
start[d] + (count[d] - 1) * stride[d] + block[d]
};
// No overlap if chunk is entirely before or after selection
if chunk_end <= sel_start || chunk_start >= sel_end {
return false;
}
}
true
}
Selection::Points(pts) => pts.iter().any(|pt| {
pt.iter()
.zip(chunk_offset.iter().zip(chunk_dims.iter()))
.all(|(&p, (&off, &dim))| p >= off && p < off + dim)
}),
}
}
/// For a given chunk, compute the local ranges within the chunk that
/// overlap with this selection.
///
/// Returns a list of (chunk_local_start, chunk_local_end, output_offset) per
/// dimension, representing which elements from the chunk contribute to the
/// output buffer. For simple contiguous slices, this returns exactly one range
/// per dimension.
pub fn chunk_local_ranges(&self, chunk_offset: &[u64], chunk_dims: &[u64]) -> Vec<Range<u64>> {
match self {
Selection::All => chunk_dims.iter().map(|&d| 0..d).collect(),
Selection::None => vec![],
Selection::Hyperslab {
start,
stride,
count,
block,
} => {
let mut ranges = Vec::with_capacity(start.len());
for d in 0..start.len() {
let chunk_start = chunk_offset[d];
let chunk_end = chunk_start + chunk_dims[d];
// For simple contiguous selections (stride==1, block==1),
// just clamp the selection range to the chunk bounds
if stride[d] == 1 && block[d] == 1 {
let sel_start = start[d];
let sel_end = start[d] + count[d];
let local_start = sel_start.max(chunk_start) - chunk_start;
let local_end = sel_end.min(chunk_end) - chunk_start;
ranges.push(local_start..local_end);
} else {
// General strided case: find all blocks that overlap this chunk
let sel_start = start[d];
let mut min_local = chunk_dims[d];
let mut max_local = 0u64;
for bi in 0..count[d] {
let block_start = sel_start + bi * stride[d];
let block_end = block_start + block[d];
// Check overlap with chunk
if block_end > chunk_start && block_start < chunk_end {
let local_s = block_start.max(chunk_start) - chunk_start;
let local_e = block_end.min(chunk_end) - chunk_start;
min_local = min_local.min(local_s);
max_local = max_local.max(local_e);
}
}
if max_local > min_local {
ranges.push(min_local..max_local);
} else {
ranges.push(0..0);
}
}
}
ranges
}
Selection::Points(_) => {
// For point selections, return the full chunk range
// (filtering happens at the element level)
chunk_dims.iter().map(|&d| 0..d).collect()
}
}
}
/// Decode a selection from its on-disk **`H5S_select_serialize`** form.
///
/// Returns the selection and the number of bytes consumed (selections are
/// self-describing in length, so the count lets a caller walk a packed list
/// of selections — as the Virtual Dataset global-heap block does).
///
/// Only the forms needed for VDS assembly are decoded: `ALL`, `NONE`, and
/// **regular** hyperslabs serialized at **version 3** (the encoding HDF5
/// 1.10+/2.0 emit). Point selections, irregular hyperslabs, and older
/// hyperslab versions return an error rather than mis-decoding.
pub fn decode_serialized(data: &[u8]) -> Result<(Selection, usize), FormatError> {
if data.len() < 8 {
return Err(FormatError::UnexpectedEof {
expected: 8,
available: data.len(),
});
}
let sel_type = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
match sel_type {
// ALL / NONE: type(4) + version(4) + reserved(4) + length(4) = 16 bytes.
3 | 0 => {
if data.len() < 16 {
return Err(FormatError::UnexpectedEof {
expected: 16,
available: data.len(),
});
}
let sel = if sel_type == 3 {
Selection::All
} else {
Selection::None
};
Ok((sel, 16))
}
2 => decode_hyperslab_serialized(data, version),
1 => Err(FormatError::ChunkedReadError(
"VDS point selections are not supported".into(),
)),
_ => Err(FormatError::ChunkedReadError(
"unknown dataspace selection type".into(),
)),
}
}
/// Enumerate the selected element indices of a **1-D** dataspace of the
/// given `extent`, in row-major selection order.
///
/// Convenience wrapper over [`Selection::iter_linear`] for rank-1 spaces.
pub fn iter_linear_1d(&self, extent: u64) -> Result<Vec<u64>, FormatError> {
self.iter_linear(&[extent])
}
/// Enumerate the **row-major linear indices** of the selected elements of a
/// dataspace with shape `dims`, in row-major (C) iteration order.
///
/// This is the order HDF5 uses to pair a virtual selection with a source
/// selection in a Virtual Dataset, so the i-th index returned here for the
/// virtual selection corresponds to the i-th index for the source
/// selection. Hyperslab/point selections whose rank differs from
/// `dims.len()` are rejected.
pub fn iter_linear(&self, dims: &[u64]) -> Result<Vec<u64>, FormatError> {
let overflow = || FormatError::Overflow("VDS selection index overflow".into());
let total: u64 = dims
.iter()
.try_fold(1u64, |acc, &d| acc.checked_mul(d))
.ok_or_else(overflow)?;
// Row-major strides: row_stride[d] = product(dims[d+1..]).
let rank = dims.len();
let mut row_stride = vec![1u64; rank];
for d in (0..rank.saturating_sub(1)).rev() {
row_stride[d] = row_stride[d + 1]
.checked_mul(dims[d + 1])
.ok_or_else(overflow)?;
}
match self {
Selection::All => Ok((0..total).collect()),
Selection::None => Ok(Vec::new()),
Selection::Hyperslab {
start,
stride,
count,
block,
} => {
if start.len() != rank {
return Err(FormatError::ChunkedReadError(
"VDS selection rank does not match dataspace rank".into(),
));
}
// Selected coordinates along each dimension, in order.
let mut per_dim: Vec<Vec<u64>> = Vec::with_capacity(rank);
for d in 0..rank {
let mut coords = Vec::new();
for ci in 0..count[d] {
let base = ci
.checked_mul(stride[d])
.and_then(|o| start[d].checked_add(o))
.ok_or_else(overflow)?;
for bi in 0..block[d] {
let coord = base.checked_add(bi).ok_or_else(overflow)?;
// Anything past the extent is malformed; bail before the
// coordinate list can grow without bound.
if coord >= dims[d] {
return Err(FormatError::ChunkedReadError(
"VDS hyperslab selection exceeds dataspace extent".into(),
));
}
coords.push(coord);
}
}
per_dim.push(coords);
}
if per_dim.iter().any(|c| c.is_empty()) {
return Ok(Vec::new());
}
// Cartesian product in row-major order (dim 0 slowest-varying).
let out_len: usize = per_dim
.iter()
.try_fold(1usize, |acc, c| acc.checked_mul(c.len()))
.ok_or_else(overflow)?;
let mut out = Vec::with_capacity(out_len);
let mut idx = vec![0usize; rank];
loop {
let mut lin = 0u64;
for d in 0..rank {
lin = per_dim[d][idx[d]]
.checked_mul(row_stride[d])
.and_then(|o| lin.checked_add(o))
.ok_or_else(overflow)?;
}
out.push(lin);
// Increment the mixed-radix counter, last dimension fastest.
let mut carry = true;
for d in (0..rank).rev() {
idx[d] += 1;
if idx[d] < per_dim[d].len() {
carry = false;
break;
}
idx[d] = 0;
}
if carry {
break;
}
}
Ok(out)
}
Selection::Points(pts) => {
let mut out = Vec::with_capacity(pts.len());
for p in pts {
if p.len() != rank {
return Err(FormatError::ChunkedReadError(
"VDS point selection rank does not match dataspace rank".into(),
));
}
let mut lin = 0u64;
for d in 0..rank {
if p[d] >= dims[d] {
return Err(FormatError::ChunkedReadError(
"VDS point selection exceeds dataspace extent".into(),
));
}
lin = p[d]
.checked_mul(row_stride[d])
.and_then(|o| lin.checked_add(o))
.ok_or_else(overflow)?;
}
out.push(lin);
}
Ok(out)
}
}
}
}
/// Decode an `H5S_SEL_HYPER` selection in its serialized form. Only version-3
/// **regular** hyperslabs are supported.
fn decode_hyperslab_serialized(
data: &[u8],
version: u32,
) -> Result<(Selection, usize), FormatError> {
if version != 3 {
return Err(FormatError::ChunkedReadError(
"only version-3 hyperslab selections are supported".into(),
));
}
// type(4) ver(4) flags(1) enc_size(1) rank(4) [start,stride,count,block]*rank
if data.len() < 14 {
return Err(FormatError::UnexpectedEof {
expected: 14,
available: data.len(),
});
}
let flags = data[8];
let enc_size = data[9] as usize;
// Bit 0 set => regular hyperslab. Irregular hyperslabs list explicit blocks.
if flags & 0x01 == 0 {
return Err(FormatError::ChunkedReadError(
"irregular VDS hyperslab selections are not supported".into(),
));
}
if enc_size != 2 && enc_size != 4 && enc_size != 8 {
return Err(FormatError::ChunkedReadError(
"unsupported hyperslab coordinate encoding size".into(),
));
}
let rank = u32::from_le_bytes([data[10], data[11], data[12], data[13]]) as usize;
// HDF5 caps dataspace rank at 32 (H5S_MAX_RANK). Reject anything larger so a
// corrupt rank can't drive a huge allocation or read loop.
if rank > 32 {
return Err(FormatError::ChunkedReadError(
"hyperslab selection rank exceeds maximum (32)".into(),
));
}
let mut pos = 14;
let read_coord = |data: &[u8], pos: usize| -> Result<u64, FormatError> {
if pos + enc_size > data.len() {
return Err(FormatError::UnexpectedEof {
expected: pos + enc_size,
available: data.len(),
});
}
let mut v = 0u64;
for (i, &b) in data[pos..pos + enc_size].iter().enumerate() {
v |= (b as u64) << (i * 8);
}
Ok(v)
};
let (mut start, mut stride, mut count, mut block) = (
Vec::with_capacity(rank),
Vec::with_capacity(rank),
Vec::with_capacity(rank),
Vec::with_capacity(rank),
);
for _ in 0..rank {
start.push(read_coord(data, pos)?);
pos += enc_size;
stride.push(read_coord(data, pos)?);
pos += enc_size;
count.push(read_coord(data, pos)?);
pos += enc_size;
block.push(read_coord(data, pos)?);
pos += enc_size;
}
Ok((
Selection::Hyperslab {
start,
stride,
count,
block,
},
pos,
))
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn selection_all_num_elements() {
let sel = Selection::All;
assert_eq!(sel.num_elements(&[100, 200]), 20000);
}
#[test]
fn selection_none_num_elements() {
let sel = Selection::None;
assert_eq!(sel.num_elements(&[100, 200]), 0);
}
#[test]
fn selection_slice_basic() {
let sel = Selection::slice(&[20..30, 40..60]);
assert_eq!(sel.num_elements(&[100, 100]), 200); // 10 * 20
assert_eq!(sel.output_shape(&[100, 100]), vec![10, 20]);
}
#[test]
fn selection_slice_1d() {
let sel = Selection::slice(std::slice::from_ref(&(5..15)));
assert_eq!(sel.num_elements(&[100]), 10);
assert_eq!(sel.output_shape(&[100]), vec![10]);
}
#[test]
fn selection_intersects_chunk_basic() {
let sel = Selection::slice(&[20..30, 40..60]);
// Chunk [20..30, 40..50] — overlaps
assert!(sel.intersects_chunk(&[20, 40], &[10, 10]));
// Chunk [0..10, 0..10] — no overlap
assert!(!sel.intersects_chunk(&[0, 0], &[10, 10]));
// Chunk [20..30, 50..60] — overlaps
assert!(sel.intersects_chunk(&[20, 50], &[10, 10]));
// Chunk [30..40, 40..50] — no overlap (just past end in dim 0)
assert!(!sel.intersects_chunk(&[30, 40], &[10, 10]));
}
#[test]
fn selection_chunk_local_ranges_simple() {
let sel = Selection::slice(&[25..35, 40..60]);
// Chunk [20..30, 40..50]
let ranges = sel.chunk_local_ranges(&[20, 40], &[10, 10]);
assert_eq!(ranges[0], 5..10); // rows 25..30 within chunk starting at 20
assert_eq!(ranges[1], 0..10); // cols 40..50 fully selected
}
#[test]
fn selection_points() {
let sel = Selection::Points(vec![vec![1, 2], vec![3, 4], vec![5, 6]]);
assert_eq!(sel.num_elements(&[10, 10]), 3);
assert_eq!(sel.rank(), Some(2));
}
#[test]
fn selection_all_intersects_any_chunk() {
let sel = Selection::All;
assert!(sel.intersects_chunk(&[0, 0], &[10, 10]));
assert!(sel.intersects_chunk(&[100, 100], &[1, 1]));
}
#[test]
fn selection_hyperslab_strided() {
// Select every other row: start=0, stride=2, count=5, block=1 in a 10-element dim
let sel = Selection::Hyperslab {
start: vec![0],
stride: vec![2],
count: vec![5],
block: vec![1],
};
assert_eq!(sel.num_elements(&[10]), 5); // 5 blocks * 1 element each
// Chunk [0..5] should intersect (contains rows 0, 2, 4)
assert!(sel.intersects_chunk(&[0], &[5]));
// Chunk [9..10] should not intersect (only row 9, but selection ends at row 8)
assert!(!sel.intersects_chunk(&[9], &[1]));
}
#[test]
fn decode_all_selection_16_bytes() {
let bytes = [3u8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let (sel, consumed) = Selection::decode_serialized(&bytes).unwrap();
assert_eq!(sel, Selection::All);
assert_eq!(consumed, 16);
assert_eq!(sel.iter_linear_1d(4).unwrap(), vec![0, 1, 2, 3]);
}
#[test]
fn decode_regular_hyperslab_matches_vds_fixture() {
// Exact virtual selection for src_a in the VDS fixture:
// start=0 stride=1 count=1 block=4, version 3, enc_size 2, rank 1.
let bytes = [
0x02, 0, 0, 0, // type = HYPER
0x03, 0, 0, 0, // version 3
0x01, // flags = regular
0x02, // enc_size = 2
0x01, 0, 0, 0, // rank = 1
0x00, 0x00, // start
0x01, 0x00, // stride
0x01, 0x00, // count
0x04, 0x00, // block
];
let (sel, consumed) = Selection::decode_serialized(&bytes).unwrap();
assert_eq!(consumed, 22);
assert_eq!(
sel,
Selection::Hyperslab {
start: vec![0],
stride: vec![1],
count: vec![1],
block: vec![4],
}
);
assert_eq!(sel.iter_linear_1d(8).unwrap(), vec![0, 1, 2, 3]);
}
#[test]
fn decode_hyperslab_start4() {
let bytes = [
0x02, 0, 0, 0, 0x03, 0, 0, 0, 0x01, 0x02, 0x01, 0, 0, 0, //
0x04, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00,
];
let (sel, _) = Selection::decode_serialized(&bytes).unwrap();
assert_eq!(sel.iter_linear_1d(8).unwrap(), vec![4, 5, 6, 7]);
}
#[test]
fn decode_strided_hyperslab_iter() {
// start=1 stride=3 count=2 block=2 => 1,2, 4,5
let bytes = [
0x02, 0, 0, 0, 0x03, 0, 0, 0, 0x01, 0x02, 0x01, 0, 0, 0, //
0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00,
];
let (sel, _) = Selection::decode_serialized(&bytes).unwrap();
assert_eq!(sel.iter_linear_1d(8).unwrap(), vec![1, 2, 4, 5]);
}
#[test]
fn decode_nd_hyperslab_iter_rejected() {
let bytes = [
0x02, 0, 0, 0, 0x03, 0, 0, 0, 0x01, 0x02, 0x02, 0, 0, 0, // rank 2
0, 0, 1, 0, 1, 0, 2, 0, 0, 0, 1, 0, 1, 0, 2, 0,
];
let (sel, _) = Selection::decode_serialized(&bytes).unwrap();
assert!(sel.iter_linear_1d(16).is_err());
}
#[test]
fn decode_irregular_hyperslab_rejected() {
let bytes = [0x02u8, 0, 0, 0, 0x03, 0, 0, 0, 0x00, 0x02, 0x01, 0, 0, 0];
assert!(Selection::decode_serialized(&bytes).is_err());
}
#[test]
fn iter_linear_2d_block_row_major() {
// A 2x2 block at the top-left of a 4x4 space => linear 0,1,4,5.
let sel = Selection::Hyperslab {
start: vec![0, 0],
stride: vec![1, 1],
count: vec![1, 1],
block: vec![2, 2],
};
assert_eq!(sel.iter_linear(&[4, 4]).unwrap(), vec![0, 1, 4, 5]);
// The same block shifted to the bottom-right => 10,11,14,15.
let sel2 = Selection::Hyperslab {
start: vec![2, 2],
stride: vec![1, 1],
count: vec![1, 1],
block: vec![2, 2],
};
assert_eq!(sel2.iter_linear(&[4, 4]).unwrap(), vec![10, 11, 14, 15]);
}
#[test]
fn iter_linear_2d_strided() {
// start=(0,0) stride=(2,2) count=(2,2) block=(1,1) over 4x4 =>
// coords (0,0)(0,2)(2,0)(2,2) => linear 0,2,8,10.
let sel = Selection::Hyperslab {
start: vec![0, 0],
stride: vec![2, 2],
count: vec![2, 2],
block: vec![1, 1],
};
assert_eq!(sel.iter_linear(&[4, 4]).unwrap(), vec![0, 2, 8, 10]);
}
#[test]
fn iter_linear_all_2d() {
assert_eq!(
Selection::All.iter_linear(&[2, 3]).unwrap(),
(0..6).collect::<Vec<_>>()
);
}
#[test]
fn iter_linear_rank_mismatch_rejected() {
let sel = Selection::Hyperslab {
start: vec![0],
stride: vec![1],
count: vec![1],
block: vec![2],
};
assert!(sel.iter_linear(&[4, 4]).is_err());
}
// ----- Adversarial / hardening: malformed input must error, never panic -----
#[test]
fn decode_all_truncated_does_not_overrun() {
// ALL claims to consume 16 bytes but only 8 are present.
let bytes = [3u8, 0, 0, 0, 1, 0, 0, 0];
assert!(Selection::decode_serialized(&bytes).is_err());
}
#[test]
fn decode_hyperslab_huge_rank_rejected() {
// rank = 0xFFFFFFFF must not drive a giant allocation.
let bytes = [
0x02u8, 0, 0, 0, 0x03, 0, 0, 0, 0x01, 0x02, 0xFF, 0xFF, 0xFF, 0xFF,
];
assert!(Selection::decode_serialized(&bytes).is_err());
}
#[test]
fn iter_linear_hyperslab_overflow_is_error() {
// start/stride/count near u64::MAX must not panic on multiply/add.
let sel = Selection::Hyperslab {
start: vec![u64::MAX - 1],
stride: vec![u64::MAX],
count: vec![u64::MAX],
block: vec![u64::MAX],
};
assert!(sel.iter_linear(&[100]).is_err());
}
#[test]
fn iter_linear_dims_product_overflow_is_error() {
assert!(Selection::All.iter_linear(&[u64::MAX, u64::MAX]).is_err());
}
#[test]
fn decode_empty_or_short_is_error_not_panic() {
assert!(Selection::decode_serialized(&[]).is_err());
assert!(Selection::decode_serialized(&[2, 0, 0, 0, 3, 0]).is_err());
}
}