Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion FileCheck.xs
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,19 @@ BOOT:
newCONSTSUB(stash, "CHECK_IS_TRUE", &PL_sv_yes ); /* could use newSViv(1) or &PL_sv_yes */
newCONSTSUB(stash, "CHECK_IS_FALSE", &PL_sv_no ); /* could use newSViv(0) or &PL_sv_no */
newCONSTSUB(stash, "CHECK_IS_NULL", &PL_sv_undef );
newCONSTSUB(stash, "FALLBACK_TO_REAL_OP", newSVnv(-1) );
/* FALLBACK_TO_REAL_OP is a dualvar: numeric -1, string
* "__OFC_FALLBACK__". The string form lets the Perl-layer
* _check() distinguish a real -1.0 NV result (e.g. -M on a
* file 1 day in the future) from the sentinel. Numeric
* comparisons (== -1) keep working for backward compat. */
{
SV *fb = newSVnv(-1);
sv_setpv(fb, "__OFC_FALLBACK__");
SvNOK_on(fb);
SvIOK_on(fb);
SvIV_set(fb, -1);
newCONSTSUB(stash, "FALLBACK_TO_REAL_OP", fb);
}

/* provide constants to add entry in a fake stat array */

Expand Down
6 changes: 4 additions & 2 deletions lib/Overload/FileCheck.pm
Original file line number Diff line number Diff line change
Expand Up @@ -511,8 +511,10 @@ sub _check {
if ( ref $out eq 'SCALAR' ) {
return ( CHECK_IS_TRUE, $$out );
}
# Bare scalar: from direct mock_file_check callbacks
if ( !ref $out && $out == FALLBACK_TO_REAL_OP ) {
# Bare scalar: from direct mock_file_check callbacks.
# Use string comparison to detect the dualvar sentinel —
# numeric == would collide with a real -1.0 value.
if ( !ref $out && "$out" eq '__OFC_FALLBACK__' ) {
return (FALLBACK_TO_REAL_OP);
}
return ( CHECK_IS_TRUE, $out );
Expand Down
2 changes: 1 addition & 1 deletion t/01_boot.t
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ is Overload::FileCheck::_loaded(), 1, '_loaded';

is int Overload::FileCheck::CHECK_IS_TRUE(), 1, "CHECK_IS_TRUE";
is int Overload::FileCheck::CHECK_IS_FALSE(), 0, "CHECK_IS_FALSE";
is Overload::FileCheck::FALLBACK_TO_REAL_OP(), -1, "FALLBACK_TO_REAL_OP";
ok Overload::FileCheck::FALLBACK_TO_REAL_OP() == -1, "FALLBACK_TO_REAL_OP == -1";

my @ops = qw{
OP_FTRREAD
Expand Down
68 changes: 68 additions & 0 deletions t/nv-sentinel-collision.t
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,72 @@ subtest 'mock_all_from_stat: other NV values pass through correctly' => sub {
unmock_stat();
};

subtest 'direct mock_file_check: -M returns -1.0 without sentinel collision' => sub {
mock_file_check( '-M' => sub {
my ($file) = @_;
return -1.0 if $file eq '/test/exactly_minus_one';
return FALLBACK_TO_REAL_OP;
});

my $age = -M '/test/exactly_minus_one';
ok( defined $age, '-M returns a defined value (not undef from fallback)' );
ok( abs($age - (-1.0)) < 0.01, "-M returns -1.0 (got: $age)" )
if defined $age;

my $real = -M '/nonexistent/direct/mock/fallback';
ok( !defined $real, 'FALLBACK_TO_REAL_OP still delegates to real OP' );

unmock_file_check('-M');
};

subtest 'direct mock_file_check: -A returns -1.0 without sentinel collision' => sub {
mock_file_check( '-A' => sub {
my ($file) = @_;
return -1.0 if $file eq '/test/exactly_minus_one';
return FALLBACK_TO_REAL_OP;
});

my $age = -A '/test/exactly_minus_one';
ok( defined $age, '-A returns a defined value' );
ok( abs($age - (-1.0)) < 0.01, "-A returns -1.0 (got: $age)" )
if defined $age;

unmock_file_check('-A');
};

subtest 'direct mock_file_check: -C returns -1.0 without sentinel collision' => sub {
mock_file_check( '-C' => sub {
my ($file) = @_;
return -1.0 if $file eq '/test/exactly_minus_one';
return FALLBACK_TO_REAL_OP;
});

my $age = -C '/test/exactly_minus_one';
ok( defined $age, '-C returns a defined value' );
ok( abs($age - (-1.0)) < 0.01, "-C returns -1.0 (got: $age)" )
if defined $age;

unmock_file_check('-C');
};

subtest 'FALLBACK_TO_REAL_OP constant backward compat' => sub {
ok( FALLBACK_TO_REAL_OP == -1, 'numeric comparison still works' );
ok( FALLBACK_TO_REAL_OP < 0, 'numeric ordering still works' );
};

subtest 'direct mock_file_check: scalar ref workaround also works' => sub {
mock_file_check( '-M' => sub {
my ($file) = @_;
return \(-1.0) if $file eq '/test/ref_wrap';
return FALLBACK_TO_REAL_OP;
});

my $age = -M '/test/ref_wrap';
ok( defined $age, '-M returns defined for scalar ref wrapped -1.0' );
ok( abs($age - (-1.0)) < 0.01, "-M returns -1.0 via scalar ref (got: $age)" )
if defined $age;

unmock_file_check('-M');
};

done_testing;
Loading