Tests failing due to directory separators are unforced errors

As is my wont, I issued a cpan-outdated | cpanm -v on my Windows 8 laptop. Apparently, it had been a while since I had touched my self-built Perl 5.20.1 installation on this account, so it took quite some time to go through everything. Glancing at the screen occasionally, I saw a few error messages fly by, so, at the end, I checked again with another cpan-outdated.

C:\Users\user\src> cpan-outdated

I know why IO::Socket::SSL failed: I haven’t built OpenSSL 1.0.1j yet, and therefore, I hadn’t set an environment variable pointing to the installation before starting the process. So, this was an expected, and desired failure. Out of the remaining three, it seemed Test::Simple seemed like a good place to start the investigation for an easy first win.

Switching to the build directory, and issuing an nmake test, the first thing I noticed was:

t\Builder\fork_with_new_stdout.t ........ Use of uninitialized value $pipe in pattern match (m//) at t\Builder\fork_with_new_stdout.t line 28, line 1.

This wasn’t the cause of the failure. Being on Windows, I do expect certain fork related functionality not to work right, anyway. No, the failed test came from:

t\subtest\die.t ......................... 1/?
#   Failed test at t\subtest\die.t line 24.
#                   'Death in the subtest at t\subtest\die.t line 20.
# '
#     doesn't match '(?^:^Death in the subtest at t\subtest\die.t line )'
# Looks like you failed 1 test of 3.

If you are not expecting something like this, it is hard to see why a regular expression pattern that seems to be identical in every respect to the portion of the string it is supposed to match indeed fails to match.

Here is the line of code that gives rise to the “test failure”:

$Test->like( [email protected], qr/^Death in the subtest at $0 line /);

See the problem now?

How about:

C:\src> perl -E "say qq{Death in the subtest at t\subtest\die.t line}"
Death in the subtest at tsubtestdie.t line

As we saw before, Windows directory separators are not the same as Unix directory separators.

In particular, the role of \ in double-quoted strings is often overlooked when file paths are interpolated into strings.

As Paul Evans noted, the fix is simple. Change the offending line to:

$Test->like( [email protected], qr/^Death in the subtest at \Q$0\E line /);

So that:

t\subtest\die.t .. ok
All tests successful.
Files=1, Tests=3,  0 wallclock secs ( 0.17 usr +  0.05 sys =  0.22 CPU)
Result: PASS

This, of course, brings up a simple question for TDD: We can only test for what we can anticipate. Among the things we can anticipate, we are also limited by our ability to formulate a test, but, the fact that in a lot of instances we do not know what we do not know is the absolute limiting factor in devising tests.

Should there have been a test testing that t/subtest/die.t used the correct test pattern?

PS: Bug report filed.

PPS: The failures in Dancer2 tests also seem to be related to directory separators, but in a different way:

#   Failed test 'Got correct location with lib/ and bin/'
#   at t/classes/Dancer2-Core-Role-HasLocation/base.t line 47.
#                   '\FakeDancerDir'
#     doesn't match '(?^:^[\\/]FakeDancerDir[\\/]$)'

#   Failed test 'blib/ dir is ignored'
#   at t/classes/Dancer2-Core-Role-HasLocation/base.t line 85.
#                   '\FakeDancerDir\blib'
#     doesn't match '(?^:^[\\/]FakeDancerDir[\\/]$)'
# Looks like you failed 2 tests of 11.

There are also a couple of exceptions in the guts of Dancer2 that occur during testing:

[main:4196] error @2014-11-09 07:24:21> Exception caught in 'core.app.before_request' filter: Hook error: Can't locate object method "failure" via package "Foo" (perhaps you forgot to load "Foo"?) at t/app.t line 150.


[AppDies:4732] error @2014-11-09 07:25:25> Route exception: oh no in c:/opt/perl/site/lib/Return/MultiLevel.pm l. 97 t/error.t ............................................. ok

I am not sure if these messages are expected side effects. Dancer2 will require more investigation.