Skip to content

Commit 1d247ad

Browse files
committed
test(permutation): strengthen bitwise_permute coverage
Addresses Toby's review feedback on PR #165 — the existing `bitwise_permute_case` only asserts `output != input`, which is too weak to catch most plausible regressions in the bit-pack/unpack glue. Adds four targeted invariant checks: - `zero_in_zero_out`: 0 -> 0 across every supported width. Catches stray bits introduced by sign/endian bugs in the unpack/repack path. - `all_ones_invariant`: !0 is a fixed point of every bit permutation (all bit positions hold 1, so no movement is observable). Catches missing-bit bugs at high/low width boundaries. - `preserves_hamming_weight`: popcount(permute(x)) == popcount(x). The strongest single property check available here — any bug that drops, duplicates, or mis-positions a bit changes the count. Also justifies the `unsafe new_unchecked` in the NonZero impls (popcount > 0 in => popcount > 0 out). - `is_deterministic`: cheap guard against future nondeterminism (hash-seeds, uninit-memory branches, etc.) creeping into the path. Existing `permute_array` correctness is already covered in elementwise tests, so these new tests focus on the bit-shuffle glue specific to `bitwise.rs` rather than retesting the underlying primitive.
1 parent 1656fda commit 1d247ad

1 file changed

Lines changed: 81 additions & 0 deletions

File tree

packages/permutation/src/bitwise.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,85 @@ mod tests {
9797
test_permute::<64, _>(NonZeroU64::new(7178231783183).unwrap());
9898
test_permute::<128, _>(NonZeroU128::new(29472929298731313).unwrap());
9999
}
100+
101+
// Zero in -> zero out, for every supported width.
102+
// Catches sign/endian bugs in the unpack/repack glue: any path that
103+
// accidentally introduces a stray bit would fail here.
104+
#[test]
105+
fn bitwise_permute_zero_in_zero_out() {
106+
assert_eq!(tests::gen_key::<8>([0; 32]).bitwise_permute(0u8), 0);
107+
assert_eq!(tests::gen_key::<16>([0; 32]).bitwise_permute(0u16), 0);
108+
assert_eq!(tests::gen_key::<32>([0; 32]).bitwise_permute(0u32), 0);
109+
assert_eq!(tests::gen_key::<64>([0; 32]).bitwise_permute(0u64), 0);
110+
assert_eq!(tests::gen_key::<128>([0; 32]).bitwise_permute(0u128), 0);
111+
}
112+
113+
// All-ones is a fixed point of every bit permutation (every bit position
114+
// already holds a 1, so permuting positions can't change anything).
115+
// Catches missing-bit bugs at the high or low boundary of each width.
116+
#[test]
117+
fn bitwise_permute_all_ones_invariant() {
118+
assert_eq!(
119+
tests::gen_key::<8>([0; 32]).bitwise_permute(u8::MAX),
120+
u8::MAX
121+
);
122+
assert_eq!(
123+
tests::gen_key::<16>([0; 32]).bitwise_permute(u16::MAX),
124+
u16::MAX
125+
);
126+
assert_eq!(
127+
tests::gen_key::<32>([0; 32]).bitwise_permute(u32::MAX),
128+
u32::MAX
129+
);
130+
assert_eq!(
131+
tests::gen_key::<64>([0; 32]).bitwise_permute(u64::MAX),
132+
u64::MAX
133+
);
134+
assert_eq!(
135+
tests::gen_key::<128>([0; 32]).bitwise_permute(u128::MAX),
136+
u128::MAX
137+
);
138+
}
139+
140+
// Hamming weight (popcount) must be preserved — a permutation moves bits
141+
// but neither creates nor destroys them. This is the strongest single
142+
// property check available for the bit-shuffle glue: any bug that drops,
143+
// duplicates, or mis-positions a bit will change the count. As a
144+
// corollary, this is also what justifies the `unsafe new_unchecked` in
145+
// the NonZero impls: popcount > 0 in -> popcount > 0 out.
146+
#[test]
147+
fn bitwise_permute_preserves_hamming_weight() {
148+
let key8 = tests::gen_key::<8>([0; 32]);
149+
for n in [0u8, 1, 0x80, 0x55, 0xaa, 0xff] {
150+
assert_eq!(key8.bitwise_permute(n).count_ones(), n.count_ones());
151+
}
152+
153+
let key32 = tests::gen_key::<32>([0; 32]);
154+
for n in [0u32, 1, 0x8000_0000, 0xcafe_babe, 0x1234_5678, u32::MAX] {
155+
assert_eq!(key32.bitwise_permute(n).count_ones(), n.count_ones());
156+
}
157+
158+
let key128 = tests::gen_key::<128>([0; 32]);
159+
for n in [
160+
0u128,
161+
1,
162+
1 << 127,
163+
0x0123_4567_89ab_cdef_fedc_ba98_7654_3210,
164+
u128::MAX,
165+
] {
166+
assert_eq!(key128.bitwise_permute(n).count_ones(), n.count_ones());
167+
}
168+
}
169+
170+
// Same key + same input must produce the same output. Cheap guard against
171+
// any future change that accidentally introduces nondeterminism (e.g. a
172+
// hash-seed in the permute path, or branching on uninitialised memory
173+
// that happens to read consistent values in test but not in production).
174+
#[test]
175+
fn bitwise_permute_is_deterministic() {
176+
let key_a = tests::gen_key::<64>([7; 32]);
177+
let key_b = tests::gen_key::<64>([7; 32]);
178+
let input = 0xcafe_babe_dead_beefu64;
179+
assert_eq!(key_a.bitwise_permute(input), key_b.bitwise_permute(input));
180+
}
100181
}

0 commit comments

Comments
 (0)