Skip to content

Fix the Windows-only failures in the v2.2.2 test suite - #1845

Open
bigpresh wants to merge 3 commits into
mainfrom
fix/windows-cpantesters-failures
Open

bigpresh wants to merge 3 commits into
mainfrom
fix/windows-cpantesters-failures

Conversation

@bigpresh

@bigpresh bigpresh commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Fixes the three test files that fail on Windows and only on Windows in the
cpantesters results for v2.2.2
(report).
Each was a platform assumption, in the code or in the test, about the platform
it is running on.

t/unit/logger/format.t

%t and %u are documented as dd/Mon/yyyy hh:mm:ss, and were built by
handing "%d/%b/%Y %H:%M:%S" to strftime. The MinGW C runtime behind
Strawberry Perl renders %b as the month number, so a September log line came
out 29/9/2026 23:59:25 and failed the test's [A-Z][a-z]{2}.

The four date codes are now assembled field by field with sprintf, so they
zero-pad the same way everywhere, and the month falls back to the English
abbreviation only where strftime has no name to give back — so a localised
month name still works where the platform supports it. strftime is still used
for %{...}t, where the format string is the caller's own.

See the follow-up comment: an earlier revision of this PR used a fixed month
table, which would have changed %t/%u for every non-English locale, not just
for the platform that broke.

t/e2e/cli/gen.t

The generated MANIFEST and MANIFEST.SKIP were written on handles opened
without an explicit layer. On Windows that is a text-mode handle, so every
\n became \r\n and the pattern the generator appends to MANIFEST.SKIP
ended in a carriage return (^MyApp-App-\r), which does not match the path it
is there to match. Both files are now written with binmode ':raw', as
_copy_templates already did for every other file it writes.

Separately, the subtest checking that an explicit -d is honoured verbatim
used -d Other::App. A colon cannot appear in a Windows file name at all - it
introduces an alternate data stream - so no Windows file system can hold that
directory and a generator asked to make one there can only fail. That check now
uses a name Windows can hold, and the colon spelling is checked under a SKIP
on Windows, so the assertion still runs everywhere such a directory can exist.
The pre-existing assertions on the -d runs now diag STDERR/STDOUT when the
generator exits non-zero, since "died with status 5632" was not enough to
diagnose it from the report.

t/integration/handler/file.t

The surplus-../ case built a path back down to the fixture from the
filesystem root using $ROOT->relative('/'). On Windows that hands back the
whole path including the drive letter, and a drive letter in the middle of a
request path is not a name any file can have, so the handler's existence check
answered 404 before its containment check was reached - still safe, but the
wrong thing for a subtest about the guard to be exercising. The path is now
built from the fixture's own path with the volume and root stripped, so the
filesystem root is the only thing surplus ../ can collapse at on every
platform.

Verification

  • Full suite passes: 209 files, 1857 tests.
  • PERLIO=crlf (which reproduces Windows' text-mode newline translation on
    other platforms) shows the generated MANIFEST/MANIFEST.SKIP coming out
    with LF after the fix, and CRLF before it - and gen.t failing before and
    passing after under the same setting.
  • The logger output is checked against a strftime patched to return the month
    number: the old code reproduces the reported 29/9/2026 23:59:25 and fails
    the test's regex, the new code renders 29/Sep/2026 23:59:25 and passes.

Not fixed here

t/config_reader.t also failed in that report, with a syntax error at line 17
(__FILE__() is not parseable on perl 5.14.4). That is an old-perl issue rather
than a Windows one, so it is left alone - worth its own fix, since it will fail
on any 5.14.

Three files fail on Windows and only on Windows in the cpantesters
results for 2.2.2 (matrix.perl-magpie.org/results/ac560063-6c5e-1014-9b5f-c5c807c19ce6).
Each is a platform assumption, in the code or in the test, about the
platform it is running on.

t/unit/logger/format.t

    %t and %u are documented as dd/Mon/yyyy hh:mm:ss and were built by
    handing "%d/%b/%Y %H:%M:%S" to strftime. The MinGW C runtime behind
    Strawberry Perl renders %b as the month number, so a September log
    line came out "29/9/2026 23:59:25" and failed the test's
    [A-Z][a-z]{2}. The four date codes are now assembled field by field
    with sprintf, and the month from a fixed Jan..Dec table, so that the
    documented format is the format produced on every platform. strftime
    is still used for %{...}t, where the format string is the caller's
    own and whatever their C library makes of it is what they asked for.

t/e2e/cli/gen.t

    The generated MANIFEST and MANIFEST.SKIP were written on handles
    opened without an explicit layer. On Windows that is a text-mode
    handle, so every \n became \r\n, and the pattern the generator
    appends to MANIFEST.SKIP ended in a carriage return -
    "^MyApp-App-\r" - which does not match the path it is there to
    match. Both files are now written with binmode ':raw', as
    _copy_templates already did for every other file it writes.

    The subtest that checks an explicit -d is honoured verbatim used
    "-d Other::App". A colon cannot appear in a Windows file name at
    all - it introduces an alternate data stream - so no Windows file
    system can hold that directory and a generator asked to make one
    there can only fail. The check is now made with a name Windows can
    hold, and the colon spelling is checked under a skip on Windows, so
    the assertion still runs where such a directory can exist.

t/integration/handler/file.t

    The surplus-../ case builds a path back down to the fixture from the
    filesystem root, using $ROOT->relative('/'). On Windows that hands
    back the whole path including the drive letter, and a drive letter
    in the middle of a request path is not a name any file can have, so
    the handler's existence check answered 404 before its containment
    check was ever reached. That is still safe, but it is the wrong
    thing for a subtest about the guard to be exercising. The path is now
    built from the fixture's path with the volume and root stripped, so
    the filesystem root is the only thing that surplus ../ can collapse
    at on every platform, and the test is back to testing the guard.

Verified: the full suite passes (209 files, 1857 tests); the two
generated files come out with LF under PERLIO=crlf, which reproduces
Windows' text-mode newline translation elsewhere, before and after; and
the logger output is checked against a strftime patched to return the
month number, where the old code reproduces the reported failure and the
new code does not.
Review feedback, and a correction to my own earlier claim: the fixed
Jan..Dec table I reached for first would have changed the output for
everyone whose locale is not English, not just on the platform that
broke. Perl calls setlocale(LC_ALL, "") at startup, so LANG does reach
strftime - with LC_ALL=de_DE.UTF-8 the old code renders March as "Mär"
and French as "mars", which is what people reading their own logs
expect, and it is behaviour worth keeping.

(I had assumed the environment locale would not reach strftime and
would not have noticed. It does.)

So ask strftime for %b as before, and fall back to the English
abbreviation only when the answer is not a name at all - which is the
MinGW case, where %b comes back as the month number. Locale-aware where
the platform can be, deterministic where it cannot.

Verified against de_DE and fr_FR built locally: the output is identical
to the old code's under each, and under a strftime patched to return the
month number the old code reproduces the reported 29/9/2026 while the
new code gives 29/Sep/2026. Full suite passes.
@bigpresh

Copy link
Copy Markdown
Member Author

Follow-up on the t/unit/logger/format.t fix: the month is no longer hardcoded.

@MONTH_NAME as a fixed table was too blunt. It fixed MinGW, but it also changed
%t/%u output for every non-English locale, which is not a change this
needs to make.

Perl calls setlocale(LC_ALL, "") at startup, so the environment locale does
reach strftime — with LC_ALL=de_DE.UTF-8 the old code renders March as
Mär, and fr_FR.UTF-8 as mars. That is the behaviour people reading their
logs on those systems have, and it is worth keeping.

_month_name now asks strftime for %b as before, and falls back to the
English abbreviation only when the answer is not a name — which is exactly
the MinGW case, where %b is the month number.

Verified with de_DE/fr_FR built locally, comparing old and new through the
same strftime:

locale old new
C 11/Mar/2026 09:05:07 11/Mar/2026 09:05:07
de_DE.UTF-8 11/Mär/2026 09:05:07 11/Mär/2026 09:05:07
fr_FR.UTF-8 11/mars/2026 09:05:07 11/mars/2026 09:05:07

and with %b patched to return the month number (the reported MinGW behaviour):

old = 29/9/2026 23:59:25     <- reproduces the cpantesters failure
new = 29/Sep/2026 23:59:25   <- documented dd/Mon/yyyy

Full suite passes (209 files, 1857 tests). So the "behaviour change for non-English
locales" noted in the PR description does not apply — the earlier commit and this
one together are locale-preserving everywhere the platform supports it.

One correction to my own reasoning

In the original description I wrote that %b "yields the month number" as though
I had confirmed that from MinGW's sources. I hadn't — the fetch of
mingw-w64-crt/msvcrt/strftime.c 404'd, and I did not retry. The evidence is
behavioural only: the cpantesters report output (29/9/2026 23:59:25) plus the
simulation above, which reproduces the failure exactly. The observation is solid;
the mechanism (which C runtime, and why) is not something I verified. Worth
keeping in mind when reviewing.

The test asserted the month is exactly `[A-Z][a-z]{2}`, which passes in
C/en_GB but fails in many locales: e.g. French `mars` or `sept.`, and
German `Mär` (non-ASCII). The old code would also have produced those
values and the test would have failed on such systems even though the
code behaved as documented.

Relax the check to require that the month field is not all digits and
contains at least one non-slash character — enough to catch the actual
MinGW regression where `%b` returned the month number (`29/9/2026`),
while not breaking on real locale month names. This matches the
locale-preserving fix to the logger itself.

All logger tests still pass in C, en_GB, de_DE, and fr_FR locales.
@bigpresh

Copy link
Copy Markdown
Member Author

Second follow-up: the date-codes test itself was locale-sensitive, and is now loosened.

Raised while fixing the month regression, so noting it here rather than opening a
separate PR.

t/unit/logger/format.t asserted the month is exactly [A-Z][a-z]{2}. That holds in
C/en_GB but not elsewhere:

locale %b old assertion
en_GB Sep passes
de_DE (March) Mär fails — non-ASCII
fr_FR sept. / mars fails — lower-case, punctuated

This is a pre-existing failure in v2.2.2, not something the earlier commits
introduced — verified by running the shipped Logger.pm under fr_FR. It's
date-dependent, which makes it worse than a clean always-fail: de_DE passes
today only because German and English abbreviate September the same way, and
fails 12 days a year on the other months.

The assertion now requires that the month field is not all digits (and is not
empty/slash), leaving the numeric fields strict:

my $MONTH = qr{[^\d/][^/]*};

The digit check is what keeps the test meaningful. Every real month name contains
a non-digit; the MinGW %b-returns-a-number bug produces 29/9/2026, which is
still rejected:

29/9/2026 23:59:25     -> NO MATCH (caught)
11/3/2026 09:05:07     -> NO MATCH (caught)
01/12/2026 00:00:00    -> NO MATCH (caught)

29/Sep/2026 23:59:25   -> MATCH
11/Mär/2026 09:05:07   -> MATCH
30/sept./2026 22:57:17 -> MATCH
11/mars/2026 09:05:07  -> MATCH

format.t passes under C, en_GB, de_DE and fr_FR. Full suite passes
(209 files, 1857 tests).

I also checked for the same assumption elsewhere — it appears only in this
subtest. t/time.t and t/unit/core/cookie.t hardcode Jan but those assert
fixed epochs in HTTP-date form, which is correctly locale-independent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant