diff --git a/e2e/MODULE.bazel.lock b/e2e/MODULE.bazel.lock
index 009e98d352a03c837db833110aa1af9592d11a90..a3b486a5c8c9223336c46f66d830161f26ef5ffb 100644
--- a/e2e/MODULE.bazel.lock
+++ b/e2e/MODULE.bazel.lock
@@ -835,7 +835,7 @@
"moduleExtensions": {
"//:MODULE.bazel%_repo_rules": {
"general": {
- "bzlTransitiveDigest": "9JxXrmfNb78Xi25kLIofG+wYfaaT/4WtRWFscwj7N9s=",
+ "bzlTransitiveDigest": "D+LVccvjhC6Fe6jgT0CX0WPaNhyl1RcBJygqKAExA8o=",
"accumulatedFileDigests": {},
"envVariables": {},
"generatedRepoSpecs": {
diff --git a/e2e/toolchain/echo/echo.bat b/e2e/toolchain/echo/echo.bat
index 187afc3acee3409e80be87828307d2de6b6815be..2da4198f837fd2fcaf31a9b218dee32feac47f41 100644
--- a/e2e/toolchain/echo/echo.bat
+++ b/e2e/toolchain/echo/echo.bat
@@ -1,18 +1 @@
-:: Enable Batch extensions
-@verify other 2>nul
-@setlocal EnableExtensions
-@if errorlevel 1 (
- echo "Failed to enable extensions"
- exit /b 120
-)
-
-:: Enable delayed expansion of variables with `!VAR!`
-@verify other 2>nul
-@setlocal EnableDelayedExpansion
-@if errorlevel 1 (
- echo "Failed to enable extensions"
- exit /b 120
-)
-
-:: Execute!
@echo.%*
\ No newline at end of file
diff --git a/toolchain/test/BUILD.bazel b/toolchain/test/BUILD.bazel
index 5a9399aa9cd8ffe10458fb17c091156c837e5f64..19450e25d6da91ed24d4f129b5283700d082707f 100644
--- a/toolchain/test/BUILD.bazel
+++ b/toolchain/test/BUILD.bazel
@@ -1,4 +1,7 @@
-exports_files(["posix.tmpl.sh"])
+exports_files([
+ "posix.tmpl.sh",
+ "nt.tmpl.bat",
+])
exports_files(
[
@@ -8,3 +11,12 @@ exports_files(
],
visibility = ["//visibility:public"],
)
+
+alias(
+ name = "template",
+ actual = select({
+ "//toolchain/constraint/os:windows": ":nt.tmpl.bat",
+ "//conditions:default": ":posix.tmpl.sh",
+ }),
+ visibility = ["//visibility:public"],
+)
diff --git a/toolchain/test/nt.tmpl.bat b/toolchain/test/nt.tmpl.bat
new file mode 100644
index 0000000000000000000000000000000000000000..fca3626ef231cac15c2f42e7cdb1dace9ac90531
--- /dev/null
+++ b/toolchain/test/nt.tmpl.bat
@@ -0,0 +1,215 @@
+@echo off
+
+:: Enable Batch extensions
+verify other 2>nul
+setlocal EnableExtensions
+if errorlevel 1 (
+ echo "Failed to enable extensions"
+ exit /b 120
+)
+
+:: Check for delayed expansion of variables with `!VAR!`
+verify other 2>nul
+setlocal EnableDelayedExpansion
+if errorlevel 1 (
+ echo "Failed to enable extensions"
+ exit /b 120
+)
+setlocal DisableDelayedExpansion
+
+:: Bazel substitutions
+set "EXECUTABLE={{executable}}"
+set "STDOUT={{stdout}}"
+set "STDERR={{stderr}}"
+
+:: Runfiles
+if [%RUNFILES_MANIFEST_ONLY%] neq [1] (
+ echo>&2.Only runfile manifests are supported
+ exit /b 2
+)
+setlocal EnableDelayedExpansion
+for %%v in (EXECUTABLE,STDOUT,STDERR) do (
+ for /f "tokens=1,2* usebackq" %%a in ("%RUNFILES_MANIFEST_FILE%") do (
+ if "_main/!%%v!" == "%%a" (
+ set "%%v=%%~fb"
+ )
+ if "!%%v!" == "../%%a" (
+ set "%%v=%%~fb"
+ )
+ )
+)
+setlocal DisableDelayedExpansion
+
+:: Execute!
+for /f %%a in ("%EXECUTABLE%") do set EXTENSION=%%~xa
+if "%EXTENSION%" == ".bat" set LAUNCHER=call
+%LAUNCHER% "%EXECUTABLE%" %* >stdout.txt 2>stderr.txt
+set "CODE=%ERRORLEVEL%"
+if %CODE% neq 0 (
+ >&2 echo.Failed to run: %EXECUTABLE% %*
+ >&2 echo.stdout:
+ >&2 type stdout.txt
+ >&2 echo.stderr:
+ >&2 type stderr.txt
+ exit /b %CODE%
+)
+
+:: Compare
+set "JUNIT=junit.xml"
+if not [%XML_OUTPUT_FILE%] == [] set "JUNIT=%XML_OUTPUT_FILE%"
+call :junit CODE stdout.txt "%STDOUT%" stderr.txt "%STDERR%" >"%JUNIT%"
+exit /b %CODE%
+
+:junit - creates JUnit XML output from comparing files
+:: %1 - return code variable
+:: %* - pairs of files to compare
+setlocal
+
+:: Output TAP/JUnit headers
+set COUNT=0
+for %%a in (%*) do set /a "COUNT+=1"
+set /a "TESTS=COUNT/2"
+echo.^
+>&2 echo.1..%TESTS%
+set SUM=0
+
+:: Loop through each file pairing
+set INDEX=0
+:loop
+set /a "INDEX+=1"
+call :compare CODE %INDEX% "%~2" "%~3"
+set /a "SUM+=CODE"
+shift /2
+shift /2
+if exist "%~3" goto :loop
+
+:: Output the JUnit footer
+echo.^
+
+endlocal & set "%~1=%SUM%"
+goto :eof
+
+:compare - compare two files
+:: %1 - return code variable
+:: %2 - test index
+:: %3 - file under test
+:: %4 - expected output
+setlocal
+set "INDEX=%~2"
+set "FILEPATH=%~3"
+set "EXPECTED=%~4"
+if "%EXPECTED:~-19%" == "\toolchain\test\any" (
+ call :any CODE %INDEX% "%FILEPATH%" "%EXPECTED%"
+) else if "%EXPECTED:~-21%" == "\toolchain\test\empty" (
+ call :empty CODE %INDEX% "%FILEPATH%" "%EXPECTED%"
+) else if "%EXPECTED:~-25%" == "\toolchain\test\non-empty" (
+ call :non-empty CODE %INDEX% "%FILEPATH%" "%EXPECTED%"
+) else (
+ call :diff CODE %INDEX% "%FILEPATH%" "%EXPECTED%"
+)
+endlocal & set %~1=%CODE%
+goto :eof
+
+:any - a file can have any content
+:: %1 - return code variable
+:: %2 - test index
+:: %3 - file under test
+:: %4 - expected output
+setlocal
+set "INDEX=%~2"
+set "FILEPATH=%~3"
+set "EXPECTED=%~4"
+>&2 echo.ok %INDEX% - %FILEPATH% contained any content
+echo.^
+endlocal & set %~1=0
+goto :eof
+
+:empty - a file must have zero content
+:: %1 - return code variable
+:: %2 - test index
+:: %3 - file under test
+:: %4 - expected output
+setlocal
+set "INDEX=%~2"
+set "FILEPATH=%~3"
+set "EXPECTED=%~4"
+for /f %%a in ("%FILEPATH%") do set "SIZE=%%~Za"
+if %SIZE% equ 0 (
+ >&2 echo.ok %INDEX% - %FILEPATH% was an empty file
+ echo.^
+ set "CODE=0"
+) else (
+ >&2 echo.not ok %INDEX% - %FILEPATH% contained content when an empty file was expected
+ echo. ^
+ echo. ^%FILEPATH% contained unexpected content:
+ type "%FILEPATH%"
+ echo. ^
+ echo. ^
+ set "CODE=1"
+)
+endlocal & set %~1=%CODE%
+goto :eof
+
+:non-empty - a file must have some content
+:: %1 - return code variable
+:: %2 - test index
+:: %3 - file under test
+:: %4 - expected output
+setlocal
+set "INDEX=%~2"
+set "FILEPATH=%~3"
+set "EXPECTED=%~4"
+for /f %%a in ("%FILEPATH%") do set "SIZE=%%~Za"
+if %SIZE% neq 0 (
+ >&2 echo.ok %INDEX% - %FILEPATH% was a non-empty file
+ echo. ^
+ set "CODE=0"
+) else (
+ >&2 echo.not ok %INDEX% - %FILEPATH% was an empty file when content was expected
+ echo. ^
+ echo. ^%FILEPATH% was an empty file when content was expected^
+ echo. ^
+ set "CODE=1"
+)
+endlocal & set %~1=%CODE%
+goto :eof
+
+:diff - compare the two passed files for content equivalence
+:: %1 - return code variable
+:: %2 - test index
+:: %3 - file under test
+:: %4 - expected output
+setlocal
+set "INDEX=%~2"
+set "FILEPATH=%~3"
+set "EXPECTED=%~4"
+set "DIFF=%SYSTEMROOT%\\system32\\fc.exe"
+if not exist "%DIFF%" (
+ >&2 echo.not ok %INDEX% - missing file compare executable: %DIFF%
+ echo. ^
+ echo. ^Missing file compare executable: %DIFF%^
+ echo. ^
+ set CODE=1
+ goto :fail
+)
+set "CODE=%ERRORLEVEL%"
+if %CODE% equ 0 (
+ >&2 echo.ok %INDEX% - %FILEPATH% was identical
+ echo.^
+) else if %CODE% equ 1 (
+ >&2 echo.not ok %INDEX% - %FILEPATH% had different content to %EXPECTED%
+ echo. ^
+ echo. ^%FILEPATH% contained different content:
+ "%DIFF%" "%FILEPATH%" "%EXPECTED%"
+ echo. ^
+ echo. ^
+) else (
+ >&2 echo.not ok %INDEX% - unknown exit code from %DIFF%: %CODE%
+ echo. ^
+ echo. ^Unknown exit code from %DIFF%: %CODE%^
+ echo. ^
+ set CODE=1
+)
+:fail
+endlocal & set %~1=%CODE%
+goto :eof
diff --git a/toolchain/test/posix.tmpl.sh b/toolchain/test/posix.tmpl.sh
index d17d4c49a0c6fc380adf22d292fb19fd8ffccdd9..66da711b1c82fdb0160838436950a1f97da7f091 100644
--- a/toolchain/test/posix.tmpl.sh
+++ b/toolchain/test/posix.tmpl.sh
@@ -152,7 +152,7 @@ junit() (
TESTS=$((COUNT / 2))
readonly COUNT TESTS
printf '\n' "${TESTS}"
- printf >&2 '1..%i\n' $((TESTS))
+ printf >&2 '1..%i\n' "${TESTS}"
INDEX=1
while ! test -z ${2+x}; do
FILEPATH="${1}"
diff --git a/toolchain/test/rule.bzl b/toolchain/test/rule.bzl
index 3c33f6da3838ec72c85251da05d71e275ffbf1f0..45ea29f38285657f04c9b20c380ad805c2885183 100644
--- a/toolchain/test/rule.bzl
+++ b/toolchain/test/rule.bzl
@@ -32,7 +32,7 @@ Can be overridden to a custom script that receives the following replacements:
- `{{stdout}}`: the expected standard output
- `{{stderr}}`: the expected standard error
""",
- default = ":posix.tmpl.sh",
+ default = ":template",
allow_single_file = True,
),
}
@@ -42,15 +42,17 @@ def implementation(ctx):
fail("Only one toolchain can be provided")
toolchain = ctx.attr.toolchains[0][platform_common.ToolchainInfo]
- executable = ctx.actions.declare_file("{}.executable".format(ctx.label.name))
+ executable = ctx.actions.declare_file("{}.{}".format(ctx.label.name, ctx.file.template.extension))
+
+ substitutions = ctx.actions.template_dict()
+ substitutions.add("{{executable}}", str(toolchain.executable.short_path))
+ substitutions.add("{{stdout}}", str(ctx.file.stdout.short_path))
+ substitutions.add("{{stderr}}", str(ctx.file.stderr.short_path))
+
ctx.actions.expand_template(
template = ctx.file.template,
output = executable,
- substitutions = {
- "{{executable}}": str(toolchain.executable.short_path),
- "{{stdout}}": str(ctx.file.stdout.short_path),
- "{{stderr}}": str(ctx.file.stderr.short_path),
- },
+ computed_substitutions = substitutions,
is_executable = True,
)