@@ -41,6 +41,10 @@ subroutine collect_hash_functions(testsuite)
4141 , new_unittest(" water_hash" , test_water_hash) &
4242 , new_unittest(" pengy_hash" , test_pengy_hash) &
4343 , new_unittest(" spooky_hash" , test_spooky_hash) &
44+ , new_unittest(" hash_determinism" , test_hash_determinism) &
45+ , new_unittest(" hash_distribution" , test_hash_distribution) &
46+ , new_unittest(" nmhash32_kat" , test_nmhash32_kat) &
47+ , new_unittest(" nmhash32x_kat" , test_nmhash32x_kat) &
4448 ]
4549
4650 end subroutine collect_hash_functions
@@ -49,10 +53,11 @@ subroutine test_little_endian(error)
4953 ! > Error handling
5054 type (error_type), allocatable , intent (out ) :: error
5155
52- ! Test for endianness
53-
54- call check(error, little_endian, " The processor is not Little-Endian" )
55- if (allocated (error)) return
56+ ! Skip test on big-endian systems instead of failing
57+ if (.not. little_endian) then
58+ call skip_test(error, " The processor is not Little-Endian (skipping)" )
59+ return
60+ end if
5661
5762 end subroutine test_little_endian
5863
@@ -64,6 +69,15 @@ subroutine test_nmhash32(error)
6469 integer (int8) :: key_array(size_key_array)
6570 integer (int32) :: c_hash(0 :size_key_array)
6671
72+ ! The C reference implementation (nmhash.h) does not support
73+ ! big-endian. Skip C-comparison on BE; value-correctness is
74+ ! verified by the test_nmhash32_kat known-answer test instead.
75+ if (.not. little_endian) then
76+ call skip_test(error, &
77+ " NMHASH32 C-comparison skipped on Big-Endian (see KAT test)" )
78+ return
79+ end if
80+
6781 call read_array(" key_array.bin" , key_array )
6882
6983 ! Read hash array generated from key array by the C version of nmhash32
@@ -85,6 +99,15 @@ subroutine test_nmhash32x(error)
8599 integer (int8) :: key_array(size_key_array)
86100 integer (int32) :: c_hash(0 :size_key_array)
87101
102+ ! The C reference implementation (nmhash.h) does not support
103+ ! big-endian. Skip C-comparison on BE; value-correctness is
104+ ! verified by the test_nmhash32x_kat known-answer test instead.
105+ if (.not. little_endian) then
106+ call skip_test(error, &
107+ " NMHASH32X C-comparison skipped on Big-Endian (see KAT test)" )
108+ return
109+ end if
110+
88111 call read_array(" key_array.bin" , key_array )
89112
90113 ! Read hash array generated from key array by the C version of nmhash32x
@@ -162,6 +185,201 @@ subroutine test_spooky_hash(error)
162185 end subroutine test_spooky_hash
163186
164187
188+ ! > Test that all hash functions produce deterministic results
189+ ! > This test runs on ALL platforms (LE and BE)
190+ subroutine test_hash_determinism (error )
191+ ! > Error handling
192+ type (error_type), allocatable , intent (out ) :: error
193+
194+ integer (int8) :: key(8 )
195+ integer (int32) :: h32_a, h32_b
196+ integer (int64) :: h64_a, h64_b
197+ integer (int64) :: h128_a(2 ), h128_b(2 )
198+
199+ key = [1_int8 , 2_int8 , 3_int8 , 4_int8 , &
200+ 5_int8 , 6_int8 , 7_int8 , 8_int8 ]
201+
202+ ! nmhash32 determinism
203+ h32_a = nmhash32(key, nm_seed)
204+ h32_b = nmhash32(key, nm_seed)
205+ call check(error, h32_a == h32_b, " NMHASH32 not deterministic" )
206+ if (allocated (error)) return
207+
208+ ! nmhash32x determinism
209+ h32_a = nmhash32x(key, nm_seed)
210+ h32_b = nmhash32x(key, nm_seed)
211+ call check(error, h32_a == h32_b, " NMHASH32X not deterministic" )
212+ if (allocated (error)) return
213+
214+ ! water_hash determinism
215+ h32_a = water_hash(key, water_seed)
216+ h32_b = water_hash(key, water_seed)
217+ call check(error, h32_a == h32_b, " WATER_HASH not deterministic" )
218+ if (allocated (error)) return
219+
220+ ! pengy_hash determinism
221+ h64_a = pengy_hash(key, pengy_seed)
222+ h64_b = pengy_hash(key, pengy_seed)
223+ call check(error, h64_a == h64_b, " PENGY_HASH not deterministic" )
224+ if (allocated (error)) return
225+
226+ ! spooky_hash determinism
227+ h128_a = spooky_hash(key, spooky_seed)
228+ h128_b = spooky_hash(key, spooky_seed)
229+ call check(error, all (h128_a == h128_b), &
230+ " SPOOKY_HASH not deterministic" )
231+ if (allocated (error)) return
232+
233+ end subroutine test_hash_determinism
234+
235+ ! > Collision sanity check: verify distinct inputs produce distinct hashes.
236+ ! > For these well-tested hash functions with fixed deterministic inputs,
237+ ! > an accidental collision probability is ~2^-32 (32-bit) or ~2^-64
238+ ! > (64-bit), making this effectively deterministic.
239+ ! > This test runs on ALL platforms (LE and BE).
240+ subroutine test_hash_distribution (error )
241+ ! > Error handling
242+ type (error_type), allocatable , intent (out ) :: error
243+
244+ integer (int8) :: key_a(8 ), key_b(8 )
245+ integer (int32) :: h32_a, h32_b
246+ integer (int64) :: h64_a, h64_b
247+ integer (int64) :: h128_a(2 ), h128_b(2 )
248+
249+ key_a = [1_int8 , 2_int8 , 3_int8 , 4_int8 , &
250+ 5_int8 , 6_int8 , 7_int8 , 8_int8 ]
251+ key_b = [1_int8 , 2_int8 , 3_int8 , 4_int8 , &
252+ 5_int8 , 6_int8 , 7_int8 , 9_int8 ] ! differs in last byte
253+
254+ ! nmhash32 collision check
255+ h32_a = nmhash32(key_a, nm_seed)
256+ h32_b = nmhash32(key_b, nm_seed)
257+ call check(error, h32_a /= h32_b, &
258+ " NMHASH32 same hash for different inputs" )
259+ if (allocated (error)) return
260+
261+ ! nmhash32x collision check
262+ h32_a = nmhash32x(key_a, nm_seed)
263+ h32_b = nmhash32x(key_b, nm_seed)
264+ call check(error, h32_a /= h32_b, &
265+ " NMHASH32X same hash for different inputs" )
266+ if (allocated (error)) return
267+
268+ ! water_hash collision check
269+ h32_a = water_hash(key_a, water_seed)
270+ h32_b = water_hash(key_b, water_seed)
271+ call check(error, h32_a /= h32_b, &
272+ " WATER_HASH same hash for different inputs" )
273+ if (allocated (error)) return
274+
275+ ! pengy_hash collision check
276+ h64_a = pengy_hash(key_a, pengy_seed)
277+ h64_b = pengy_hash(key_b, pengy_seed)
278+ call check(error, h64_a /= h64_b, &
279+ " PENGY_HASH same hash for different inputs" )
280+ if (allocated (error)) return
281+
282+ ! spooky_hash collision check
283+ h128_a = spooky_hash(key_a, spooky_seed)
284+ h128_b = spooky_hash(key_b, spooky_seed)
285+ call check(error, any (h128_a /= h128_b), &
286+ " SPOOKY_HASH same hash for different inputs" )
287+ if (allocated (error)) return
288+
289+ end subroutine test_hash_distribution
290+
291+
292+ ! > Known-Answer Test for NMHASH32.
293+ ! > Verifies the Fortran implementation produces the exact canonical
294+ ! > LE-normalized hash values across all code paths. Reference values
295+ ! > were computed on a little-endian platform using the upstream C code.
296+ ! > This test runs on ALL platforms (LE and BE).
297+ subroutine test_nmhash32_kat (error )
298+ ! > Error handling
299+ type (error_type), allocatable , intent (out ) :: error
300+
301+ ! Number of test vectors
302+ integer , parameter :: num_kat = 14
303+
304+ ! Input lengths covering every code path:
305+ ! 0=zero, 1/2/3/4=small, 7/8=5-8 path, 9/32=9-32 path,
306+ ! 33/100/255=33-255 path, 256/300=long path (256+)
307+ integer , parameter :: kat_lengths(num_kat) = [ &
308+ 0 , 1 , 2 , 3 , 4 , 7 , 8 , &
309+ 9 , 32 , 33 , 100 , 255 , 256 , 300 ]
310+
311+ ! Reference NMHASH32 values (computed on LE with seed=0xDEADBEEF)
312+ integer (int32), parameter :: kat_expected(num_kat) = [ &
313+ int (z' B0D9C845' , int32), int (z' D52AD23F' , int32), &
314+ int (z' E909FDFF' , int32), int (z' FF1A009C' , int32), &
315+ int (z' 097D4183' , int32), int (z' 55CC8BBF' , int32), &
316+ int (z' 660D67B4' , int32), int (z' CB939B94' , int32), &
317+ int (z' 4CBE45F8' , int32), int (z' 2FD88BD0' , int32), &
318+ int (z' 83AC6B02' , int32), int (z' CC0E4E26' , int32), &
319+ int (z' 567D6B58' , int32), int (z' 865F0BC9' , int32) ]
320+
321+ ! Deterministic key: key(i) = IAND(i, 255)
322+ integer (int8) :: key(300 )
323+ integer :: i
324+ integer (int32) :: got
325+
326+ do i = 1 , 300
327+ key(i) = int (iand (i, 255 ), int8)
328+ end do
329+
330+ do i = 1 , num_kat
331+ got = nmhash32(key(1 :kat_lengths(i)), nm_seed)
332+ call check(error, got == kat_expected(i), &
333+ " NMHASH32 KAT failed" )
334+ if (allocated (error)) return
335+ end do
336+
337+ end subroutine test_nmhash32_kat
338+
339+ ! > Known-Answer Test for NMHASH32X.
340+ ! > Same approach as test_nmhash32_kat but for the NMHASH32X variant.
341+ ! > This test runs on ALL platforms (LE and BE).
342+ subroutine test_nmhash32x_kat (error )
343+ ! > Error handling
344+ type (error_type), allocatable , intent (out ) :: error
345+
346+ ! Number of test vectors
347+ integer , parameter :: num_kat = 14
348+
349+ ! Input lengths covering every code path
350+ integer , parameter :: kat_lengths(num_kat) = [ &
351+ 0 , 1 , 2 , 3 , 4 , 7 , 8 , &
352+ 9 , 32 , 33 , 100 , 255 , 256 , 300 ]
353+
354+ ! Reference NMHASH32X values (computed on LE with seed=0xDEADBEEF)
355+ integer (int32), parameter :: kat_expected(num_kat) = [ &
356+ int (z' 76844735' , int32), int (z' B7AE2C90' , int32), &
357+ int (z' EE2224FD' , int32), int (z' BBE39609' , int32), &
358+ int (z' 08467EE3' , int32), int (z' 10E572DA' , int32), &
359+ int (z' 2570CFA8' , int32), int (z' 1A06128A' , int32), &
360+ int (z' EABBF1B8' , int32), int (z' 9B1B3428' , int32), &
361+ int (z' F6F0233D' , int32), int (z' 7EB7CAFC' , int32), &
362+ int (z' B34D6C45' , int32), int (z' E89BEE9E' , int32) ]
363+
364+ ! Deterministic key: key(i) = IAND(i, 255)
365+ integer (int8) :: key(300 )
366+ integer :: i
367+ integer (int32) :: got
368+
369+ do i = 1 , 300
370+ key(i) = int (iand (i, 255 ), int8)
371+ end do
372+
373+ do i = 1 , num_kat
374+ got = nmhash32x(key(1 :kat_lengths(i)), nm_seed)
375+ call check(error, got == kat_expected(i), &
376+ " NMHASH32X KAT failed" )
377+ if (allocated (error)) return
378+ end do
379+
380+ end subroutine test_nmhash32x_kat
381+
382+
165383 subroutine generate_key_array ()
166384
167385 integer :: i, lun
0 commit comments