From 71b19ab8b4d5c613c53dc4fe3ecd9a933e879d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Sun, 29 Mar 2026 14:03:09 -0600 Subject: [PATCH] fix: accept bare (non-st_) keys in hash stat mocks `_normalize_stat_result` previously required `st_`-prefixed keys when a mock returned a HASH ref (e.g. `{ st_size => 100 }`), while the `stat_as_*` helpers via `_stat_for` already accepted bare keys like `size`. A user writing `{ size => 100, mode => 0100644 }` got a fatal "Unknown index for stat_t struct key size" with no hint about the `st_` requirement. Apply the same normalization that `_stat_for` uses: lowercase the key and strip a leading `st_` prefix before the map lookup, then re-add `st_` when querying `%MAP_STAT_T_IX`. This makes the two code paths consistent and accepts both `dev` and `st_dev` (and mixed hashes) transparently. Adds t/mock-stat-hash-bare-keys.t with six cases covering: single bare key, multiple bare keys, mixed prefixed/bare keys, existing `st_`-prefixed keys (regression), and uppercase variants of both forms. Fixes https://github.com/cpan-authors/Overload-FileCheck/issues/63 Co-Authored-By: Claude Sonnet 4.6 --- lib/Overload/FileCheck.pm | 3 +- t/mock-stat-hash-bare-keys.t | 94 ++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 t/mock-stat-hash-bare-keys.t diff --git a/lib/Overload/FileCheck.pm b/lib/Overload/FileCheck.pm index 6238a51..be16b81 100644 --- a/lib/Overload/FileCheck.pm +++ b/lib/Overload/FileCheck.pm @@ -501,7 +501,8 @@ sub _normalize_stat_result { if ( $stat_is_a eq 'HASH' ) { my $stat_as_arrayref = [ (0) x $stat_t_max ]; foreach my $k ( keys %$stat ) { - my $ix = $MAP_STAT_T_IX{ lc($k) }; + ( my $normalized = lc($k) ) =~ s{^st_}{}; + my $ix = $MAP_STAT_T_IX{"st_$normalized"}; die qq[Unknown index for stat_t struct key $k] unless defined $ix; $stat_as_arrayref->[$ix] = $stat->{$k}; } diff --git a/t/mock-stat-hash-bare-keys.t b/t/mock-stat-hash-bare-keys.t new file mode 100644 index 0000000..199b3f3 --- /dev/null +++ b/t/mock-stat-hash-bare-keys.t @@ -0,0 +1,94 @@ +#!/usr/bin/perl -w + +# Regression test for https://github.com/cpan-authors/Overload-FileCheck/issues/63 +# _normalize_stat_result must accept bare keys (without st_ prefix) in the same +# way that the stat_as_* helpers do via _stat_for. + +use strict; +use warnings; + +use Test2::Bundle::Extended; +use Test2::Plugin::NoWarnings; + +use Overload::FileCheck qw(mock_stat unmock_all_file_checks FALLBACK_TO_REAL_OP); + +sub fresh_stat { [ (0) x Overload::FileCheck::STAT_T_MAX() ] } + +# Install a stat mock that returns hash refs to drive _normalize_stat_result. +mock_stat( + sub { + my ( $opname, $f ) = @_; + + # bare key only + return { dev => 42 } if $f eq 'bare.dev'; + + # multiple bare keys + return { dev => 7, size => 512, mtime => 1600000000 } + if $f eq 'bare.multi'; + + # mixed: some have st_ prefix, some bare + return { st_dev => 3, size => 256, mode => 0100644 } + if $f eq 'bare.mixed'; + + # st_-prefixed keys must still work + return { st_dev => 99, st_size => 1024 } if $f eq 'st.prefix'; + + # uppercase bare key + return { DEV => 5 } if $f eq 'bare.upper'; + + # uppercase st_-prefixed key + return { ST_SIZE => 8192 } if $f eq 'st.upper'; + + return FALLBACK_TO_REAL_OP(); + } +); + +# --- bare.dev --- +{ + my $expect = fresh_stat(); + $expect->[ Overload::FileCheck::ST_DEV() ] = 42; + is [ stat('bare.dev') ], $expect, "bare key 'dev' accepted"; +} + +# --- bare.multi --- +{ + my $expect = fresh_stat(); + $expect->[ Overload::FileCheck::ST_DEV() ] = 7; + $expect->[ Overload::FileCheck::ST_SIZE() ] = 512; + $expect->[ Overload::FileCheck::ST_MTIME() ] = 1600000000; + is [ stat('bare.multi') ], $expect, "multiple bare keys accepted"; +} + +# --- bare.mixed --- +{ + my $expect = fresh_stat(); + $expect->[ Overload::FileCheck::ST_DEV() ] = 3; + $expect->[ Overload::FileCheck::ST_SIZE() ] = 256; + $expect->[ Overload::FileCheck::ST_MODE() ] = 0100644; + is [ stat('bare.mixed') ], $expect, "mixed bare and st_-prefixed keys accepted"; +} + +# --- st.prefix (existing behaviour must not regress) --- +{ + my $expect = fresh_stat(); + $expect->[ Overload::FileCheck::ST_DEV() ] = 99; + $expect->[ Overload::FileCheck::ST_SIZE() ] = 1024; + is [ stat('st.prefix') ], $expect, "st_-prefixed keys still work"; +} + +# --- bare.upper (case-insensitive bare key) --- +{ + my $expect = fresh_stat(); + $expect->[ Overload::FileCheck::ST_DEV() ] = 5; + is [ stat('bare.upper') ], $expect, "uppercase bare key 'DEV' accepted"; +} + +# --- st.upper (case-insensitive st_-prefixed key) --- +{ + my $expect = fresh_stat(); + $expect->[ Overload::FileCheck::ST_SIZE() ] = 8192; + is [ stat('st.upper') ], $expect, "uppercase prefixed key 'ST_SIZE' accepted"; +} + +unmock_all_file_checks(); +done_testing;