Changes for version 0.03 - 2026-06-06

  • t/lib/INA_CPAN_Check.pm: emit exactly one TAP plan line per test file. Each check_* helper previously called plan_tests() itself, while the .t files also called plan_tests(count_*); this produced multiple "1..N" lines in a single file. Under a real TAP harness (prove / Test::Harness, as used by CPAN Testers) this raised "More than one plan found in TAP output" and made the affected files FAIL, even though every individual "ok" line passed when the scripts were run by hand. Affected files: t/9010-encoding.t, t/9030-distribution.t, t/9040-style.t. The plan_tests() call is now removed from every check_* helper, leaving the .t file as the sole owner of the plan line.
  • t/lib/INA_CPAN_Check.pm: count_A() now returns the actual number of MANIFEST entries instead of a fixed 1, so that the plan computed by t/9030-distribution.t matches the number of A1 checks that check_A() emits.
  • t/lib/INA_CPAN_Check.pm: remove the mid-stream plan_skip() calls from check_A() and check_C(); the MANIFEST-absent guard is handled by the .t file and by count_C() before any plan line is printed.
  • t/lib/INA_CPAN_Check.pm: check_K() now honours the k3_exempt option passed by t/9040-style.t. The argument was previously discarded by "my ($root) = @_;", so the intended exemption of accessor-style hash names (%env, %opts, %args) never took effect; the K3 detector also did not capture the hash name. check_K() now accepts "k3_exempt => REGEX", captures the returned hash name via /return \%(\w*)/, and skips a "return \%name" only when the name matches the supplied pattern. Behaviour is unchanged when k3_exempt is not passed (every "return \%..." is still flagged), so distributions that call check_K($root) without the option are unaffected.
  • t/lib/INA_CPAN_Check.pm: add a regression guard for the two TAP defect classes above. plan_tests() now refuses to emit a second "1..N" line, and an end-of-run reconciliation reports "planned X but ran Y" (setting a non-zero exit) when the emitted plan does not match the number of ok()/not-ok() lines. Both problems now FAIL immediately on a plain "perl t/foo.t", not only under a real harness.
  • t/lib/INA_CPAN_Check.pm: add selfcheck_suite(), which runs t/*.t (and xt/*.t) in a child Perl and verifies one plan line per file, plan == number of ok/not-ok lines, and no failures.
  • pmake.bat: at "pmake dist" time, after the existing source checks, run INA_CPAN_Check::selfcheck_suite() as check3 and abort the build if any test file fails the plan-sanity check (disable with --no-check3). Bump $PMAKE_BAT_VERSION to 0.34.
  • t/lib/INA_CPAN_Check.pm: pass \@files / \@pm_files (a reference) instead of [ @files ] (an anonymous copy) to _find_pm_t() in _scan_code(), check_D(), check_E(), and check_K(). The copy form meant the collected file list never reached the caller, so E1 (no shebang in lib/*.pm) and K3 (return { %hash } form) silently scanned zero files and always passed.
  • Documentation: BATsh.pm BUGS AND LIMITATIONS corrected. It no longer claims SH-mode background execution is unsupported (it is supported for external commands; see above and BATsh::SH), and it now clarifies that non-builtin commands (FINDSTR, SORT, etc.) are invoked as external programs rather than "unsupported". README and BATsh.pm POD additionally enumerate previously undocumented limitations: CMD "%VAR:~n,m%" / "%VAR:str1=str2%" and dynamic "%RANDOM%/%DATE%/%TIME%/ %CD%" variables; SH arrays, filename globbing, "~" tilde expansion, brace expansion, and the trap/getopts/select/alias/declare/eval/exec builtins and set -e/-u/-x options; and the shared (no sub-shell) "( ... )" grouping common to both modes.
  • SH expansion: a backslash-escaped "\$", "\`" or "\\" inside double quotes is now preserved literally and no longer triggers variable or command substitution (e.g. "\$_" yields a literal "$_").
  • SH read: the "read" built-in now returns a non-zero status at end of input so that "while read VAR; do ...; done < FILE" terminates instead of looping. Leading option flags such as "-r" are skipped and are no longer treated as target variable names.
  • SH assignment prefix: "VAR=value command args" (POSIX) now applies the assignment and then runs the command (e.g. "IFS= read -r LINE", "LC_ALL=C sort"); multiple prefixes are supported. A standalone assignment whose value merely contains spaces or a "$(...)" substitution (e.g. UPPER=$(echo "a b")) keeps the full value and is no longer mistaken for a prefix.
  • SH while/until: an input redirection on the "done" line ("while read L; do ...; done < FILE") now reopens STDIN from FILE for the duration of the loop so the loop's "read" consumes the file.
  • eg/06_sh_comprehensive.batsh: I/O-redirection section simplified to a plain "while read" loop now that the loop terminates correctly.
  • Tests: t/9070-examples.t now executes each eg/*.batsh in a child process and guards against runaway output and "syntax error" breakage (E4). t/9060-readme.t verifies the README advertises every eg/ example by name (R5). README gains an EXAMPLES section.
  • SH background execution: an unquoted trailing "&" starts an external command asynchronously and returns immediately. On Win32 the job is spawned via system(1, ...) (P_NOWAIT, PID returned); on Unix it is started through /bin/sh without a Perl fork, capturing the job PID via the shell's $! into a sysopen O_CREAT|O_EXCL temp file (Pure Perl, 5.005_03). The new $! parameter expands to the most recent background PID (empty before any job); $? is 0 on a successful launch (the job's own exit status is not awaited). Built-ins, functions, assignments and control words ignore the trailing "&" and run in the foreground; "&&", ">&"/"2>&1", quoted and escaped "\&" are not treated as background. No job control; CMD-mode "&" remains a sequential separator (see BUGS AND LIMITATIONS).
  • eg/05_cmd_comprehensive.batsh: the "IF ERRORLEVEL" diagnostic line "ECHO ERRORLEVEL>=0: ELTEST=%ELTEST%" contained a bare ">", which CMD mode correctly treats as output redirection (matching cmd.exe). As written, the message was silently redirected to a file named "=0:" instead of being printed, and that stray file was created in the current directory each time the example ran (including under "make test" via t/9070-examples.t). The ">" is now caret-escaped ("ECHO ERRORLEVEL ^>= 0: ELTEST=%ELTEST%"), so the line prints as intended and no file is written.

Documentation

Modules

Bilingual Shell for cmd.exe and bash in one script (self-contained)
Pure Perl cmd.exe interpreter for BATsh
Shared variable store for BATsh
Pure Perl bash/sh interpreter for BATsh