瀏覽代碼

Adjust test harness to run many tests successfully on Windows

Tianon Gravi 5 年之前
父節點
當前提交
f8c09a08b2

+ 11 - 3
test/clean.sh

@@ -1,4 +1,12 @@
-#!/bin/bash
-set -e
+#!/usr/bin/env bash
+set -Eeuo pipefail
 
-docker images 'librarytest/*' | awk 'NR>1 { print $1":"$2 }' | xargs -r docker rmi
+docker image ls --digests --no-trunc --format '
+	{{- if ne .Tag "<none>" -}}
+		{{- .Repository -}} : {{- .Tag -}}
+	{{- else if ne .Digest "<none>" -}}
+		{{- .Repository -}} @ {{- .Digest -}}
+	{{- else -}}
+		{{- .ID -}}
+	{{- end -}}
+' 'librarytest/*' | xargs -rt docker image rm

+ 16 - 3
test/config.sh

@@ -11,8 +11,6 @@ globalTests+=(
 # for "explicit" images, only run tests that are explicitly specified for that image/variant
 explicitTests+=(
 	[:onbuild]=1
-	[:nanoserver]=1
-	[:windowsservercore]=1
 )
 imageTests[:onbuild]+='
 	override-cmd
@@ -272,6 +270,14 @@ globalExcludeTests+=(
 	[swarm_utc]=1
 	[traefik_utc]=1
 
+	# windows!
+	[:nanoserver_cve-2014--shellshock]=1
+	[:nanoserver_no-hard-coded-passwords]=1
+	[:nanoserver_utc]=1
+	[:windowsservercore_cve-2014--shellshock]=1
+	[:windowsservercore_no-hard-coded-passwords]=1
+	[:windowsservercore_utc]=1
+
 	[hello-world_no-hard-coded-passwords]=1
 	[nats_no-hard-coded-passwords]=1
 	[nats-streaming_no-hard-coded-passwords]=1
@@ -282,9 +288,10 @@ globalExcludeTests+=(
 	# https://github.com/docker-library/official-images/pull/1721#issuecomment-234128477
 	[clearlinux_no-hard-coded-passwords]=1
 
-	# alpine/slim openjdk images are headless and so can't do font stuff
+	# alpine/slim/nanoserver openjdk images are headless and so can't do font stuff
 	[openjdk:alpine_java-uimanager-font]=1
 	[openjdk:slim_java-uimanager-font]=1
+	[openjdk:nanoserver_java-uimanager-font]=1
 	# and adoptopenjdk has opted not to
 	[adoptopenjdk_java-uimanager-font]=1
 
@@ -299,4 +306,10 @@ globalExcludeTests+=(
 	# the Swift slim images are not expected to be able to run the swift-hello-world test because it involves compiling Swift code. The slim images are for running an already built binary.
 	# https://github.com/docker-library/official-images/pull/6302#issuecomment-512181863
 	[swift:slim_swift-hello-world]=1
+
+	# TODO adjust MongoDB tests to use docker networks instead of links so they can work on Windows (and consider using PowerShell to generate appropriate certificates for TLS tests instead of openssl)
+	[mongo:windowsservercore_mongo-basics]=1
+	[mongo:windowsservercore_mongo-auth-basics]=1
+	[mongo:windowsservercore_mongo-tls-basics]=1
+	[mongo:windowsservercore_mongo-tls-auth]=1
 )

+ 43 - 31
test/run.sh

@@ -1,5 +1,5 @@
-#!/bin/bash
-set -e
+#!/usr/bin/env bash
+set -Eeuo pipefail
 
 dir="$(dirname "$(readlink -f "$BASH_SOURCE")")"
 
@@ -46,7 +46,7 @@ while true; do
 	esac
 done
 
-if [ $# -eq 0 ]; then
+if [ "$#" -eq 0 ]; then
 	usage >&2
 	exit 1
 fi
@@ -59,10 +59,21 @@ declare -A globalExcludeTests=()
 declare -A explicitTests=()
 
 # if there are no user-specified configs, use the default config
-if [ ${#configs} -eq 0 ]; then
-	configs+=("$dir/config.sh")
+if [ "${#configs[@]}" -eq 0 ]; then
+	configs+=( "$dir/config.sh" )
 fi
 
+case "$(uname -o)" in
+	Msys)
+		# https://stackoverflow.com/a/34386471/433558
+		# https://github.com/docker/toolbox/blob/6960f28d5b01857d69b2095a02949264f09d3e57/windows/start.sh#L104-L107
+		docker() {
+			MSYS_NO_PATHCONV=1 command docker "$@"
+		}
+		export -f docker
+		;;
+esac
+
 # load the configs
 declare -A testPaths=()
 for conf in "${configs[@]}"; do
@@ -72,7 +83,7 @@ for conf in "${configs[@]}"; do
 	confDir="$(dirname "$conf")"
 
 	for testName in ${globalTests[@]} ${imageTests[@]}; do
-		[ "${testPaths[$testName]}" ] && continue
+		[ -n "${testPaths[$testName]:-}" ] && continue
 
 		if [ -d "$confDir/tests/$testName" ]; then
 			# Test directory found relative to the conf file
@@ -116,11 +127,11 @@ for dockerImage in "$@"; do
 			variant='psmdb'
 			;;
 		*nanoserver*)
-			# all nanoserver variants are windows and should have explict tests
+			# all nanoserver variants are windows
 			variant='nanoserver'
 			;;
 		*windowsservercore*)
-			# all servercore variants are windows and should have explict tests
+			# all servercore variants are windows
 			variant='windowsservercore'
 			;;
 	esac
@@ -131,10 +142,10 @@ for dockerImage in "$@"; do
 	fi
 	
 	for possibleAlias in \
-		"${testAlias[$repo:$variant]}" \
-		"${testAlias[$repo]}" \
-		"${testAlias[$testRepo:$variant]}" \
-		"${testAlias[$testRepo]}" \
+		"${testAlias[$repo:$variant]:-}" \
+		"${testAlias[$repo]:-}" \
+		"${testAlias[$testRepo:$variant]:-}" \
+		"${testAlias[$testRepo]:-}" \
 	; do
 		if [ -n "$possibleAlias" ]; then
 			testRepo="$possibleAlias"
@@ -144,9 +155,9 @@ for dockerImage in "$@"; do
 	
 	explicitVariant=
 	if [ \
-		"${explicitTests[:$variant]}" \
-		-o "${explicitTests[$repo:$variant]}" \
-		-o "${explicitTests[$testRepo:$variant]}" \
+		"${explicitTests[:$variant]:-}" \
+		-o "${explicitTests[$repo:$variant]:-}" \
+		-o "${explicitTests[$testRepo:$variant]:-}" \
 	]; then
 		explicitVariant=1
 	fi
@@ -156,41 +167,41 @@ for dockerImage in "$@"; do
 		testCandidates+=( "${globalTests[@]}" )
 	fi
 	testCandidates+=(
-		${imageTests[:$variant]}
+		${imageTests[:$variant]:-}
 	)
 	if [ -z "$explicitVariant" ]; then
 		testCandidates+=(
-			${imageTests[$testRepo]}
+			${imageTests[$testRepo]:-}
 		)
 	fi
 	testCandidates+=(
-		${imageTests[$testRepo:$variant]}
+		${imageTests[$testRepo:$variant]:-}
 	)
 	if [ "$testRepo" != "$repo" ]; then
 		if [ -z "$explicitVariant" ]; then
 			testCandidates+=(
-				${imageTests[$repo]}
+				${imageTests[$repo]:-}
 			)
 		fi
 		testCandidates+=(
-			${imageTests[$repo:$variant]}
+			${imageTests[$repo:$variant]:-}
 		)
 	fi
 	
 	tests=()
 	for t in "${testCandidates[@]}"; do
-		if [ ${#argTests[@]} -gt 0 -a -z "${argTests[$t]}" ]; then
+		if [ "${#argTests[@]}" -gt 0 ] && [ -z "${argTests[$t]:-}" ]; then
 			# skipping due to -t
 			continue
 		fi
 		
 		if [ \
-			! -z "${globalExcludeTests[${testRepo}_$t]}" \
-			-o ! -z "${globalExcludeTests[${testRepo}:${variant}_$t]}" \
-			-o ! -z "${globalExcludeTests[:${variant}_$t]}" \
-			-o ! -z "${globalExcludeTests[${repo}_$t]}" \
-			-o ! -z "${globalExcludeTests[${repo}:${variant}_$t]}" \
-			-o ! -z "${globalExcludeTests[:${variant}_$t]}" \
+			! -z "${globalExcludeTests[${testRepo}_$t]:-}" \
+			-o ! -z "${globalExcludeTests[${testRepo}:${variant}_$t]:-}" \
+			-o ! -z "${globalExcludeTests[:${variant}_$t]:-}" \
+			-o ! -z "${globalExcludeTests[${repo}_$t]:-}" \
+			-o ! -z "${globalExcludeTests[${repo}:${variant}_$t]:-}" \
+			-o ! -z "${globalExcludeTests[:${variant}_$t]:-}" \
 		]; then
 			# skipping due to exclude
 			continue
@@ -223,10 +234,11 @@ for dockerImage in "$@"; do
 		scriptDir="${testPaths[$t]}"
 		if [ -d "$scriptDir" ]; then
 			script="$scriptDir/run.sh"
-			if [ -x "$script" -a ! -d "$script" ]; then
+			if [ -x "$script" ] && [ ! -d "$script" ]; then
 				# TODO dryRun logic
-				if output="$("$script" $dockerImage)"; then
-					if [ -f "$scriptDir/expected-std-out.txt" ] && ! d="$(echo "$output" | diff -u "$scriptDir/expected-std-out.txt" - 2>/dev/null)"; then
+				if output="$("$script" "$dockerImage")"; then
+					output="$(tr -d '\r' <<<"$output")" # Windows gives us \r\n ...  :D
+					if [ -f "$scriptDir/expected-std-out.txt" ] && ! d="$(diff -u "$scriptDir/expected-std-out.txt" - <<<"$output" 2>/dev/null)"; then
 						echo 'failed; unexpected output:'
 						echo "$d"
 						didFail=1
@@ -252,6 +264,6 @@ for dockerImage in "$@"; do
 	done
 done
 
-if [ "$didFail" ]; then
+if [ -n "$didFail" ]; then
 	exit 1
 fi

+ 1 - 1
test/tests/docker-build.sh

@@ -30,4 +30,4 @@ fi
 
 cp -RL "$dir" "$tmp/dir"
 
-docker build -t "$imageTag" "$tmp" > /dev/null
+command docker build -t "$imageTag" "$tmp" > /dev/null

+ 7 - 1
test/tests/hylang-sh/container.hy

@@ -4,8 +4,14 @@
 
 (import subprocess sys)
 (subprocess.check_call [sys.executable "-m" "pip" "install" "-q" "sh"])
+(import platform)
+
+(comment Windows is not supported by sh (sad day))
+(comment https://github.com/amoffat/sh/blob/608f4c3bf5ad75ad40035d03a9c5ffcce0898f07/sh.py#L33-L36)
+(if (= (.system platform) "Windows")
+  (defn echo [dashn num] (return num))
+  (import [sh [echo]]))
 
-(import [sh [echo]])
 (->
   (+
     (int (echo "-n" 21))

+ 20 - 18
test/tests/mongo-basics/run.sh

@@ -22,31 +22,33 @@ if docker info --format '{{ join .SecurityOptions "\n" }}' 2>/dev/null |tac|tac|
 
 	# need set_mempolicy syscall to be able to do numactl for mongodb
 	# if "set_mempolicy" is not in the always allowed list, add it
-	extraSeccomp="$(echo "$seccomp" | docker run -i --rm "$jqImage" --tab '
-		.syscalls[] |= if (
-			.action == "SCMP_ACT_ALLOW"
-			and .args == []
-			and .comment == ""
-			and .includes == {}
-			and .excludes == {}
-		) then (
-			if ( .names | index("set_mempolicy") ) > 0 then
+	extraSeccomp="$(
+		docker run -i --rm "$jqImage" --tab '
+			.syscalls[] |= if (
+				.action == "SCMP_ACT_ALLOW"
+				and .args == []
+				and .comment == ""
+				and .includes == {}
+				and .excludes == {}
+			) then (
+				if ( .names | index("set_mempolicy") ) > 0 then
+					.
+				else (
+					.names |= . + ["set_mempolicy"]
+				) end
+			)
+			else
 				.
-			else (
-				.names |= . + ["set_mempolicy"]
-			) end
-		)
-		else
-			.
-		end
-	')"
+			end
+		' <<<"$seccomp"
+	)"
 else
 	echo >&2 'warning: the current Docker daemon does not appear to support seccomp'
 fi
 
 docker_run_seccomp() {
 	if [ "$haveSeccomp" ]; then
-		docker run --security-opt seccomp=<(echo "$extraSeccomp") "$@"
+		docker run --security-opt seccomp=<(cat <<<"$extraSeccomp") "$@"
 	else
 		docker run "$@"
 	fi

+ 13 - 4
test/tests/override-cmd/run.sh

@@ -1,5 +1,5 @@
-#!/bin/bash
-set -eo pipefail
+#!/usr/bin/env bash
+set -Eeuo pipefail
 
 image="$1"
 
@@ -8,13 +8,22 @@ image="$1"
 
 hello="world-$RANDOM-$RANDOM"
 
+cmd=( echo "Hello $hello" )
+case "$image" in
+	*windowsservercore* | *nanoserver*)
+		cmd=( cmd /Q /S /C "${cmd[*]}" )
+		;;
+esac
+
 # test first with --entrypoint to verify that we even have echo (tests for single-binary images FROM scratch, essentially)
-if ! testOutput="$(docker run --rm --entrypoint echo "$image" "Hello $hello" 2>/dev/null)"; then
+if ! testOutput="$(docker run --rm --entrypoint "${cmd[0]}" "$image" "${cmd[@]:1}" 2>/dev/null)"; then
 	echo >&2 'image does not appear to contain "echo" -- assuming single-binary image'
 	exit
 fi
+testOutput="$(tr -d '\r' <<<"$testOutput")" # Windows gives us \r\n ...  :D
 [ "$testOutput" = "Hello $hello" ]
 
 # now test with normal command to verify the default entrypoint is OK
-output="$(docker run --rm "$image" echo "Hello $hello")"
+output="$(docker run --rm "$image" "${cmd[@]}")"
+output="$(tr -d '\r' <<<"$output")" # Windows gives us \r\n ...  :D
 [ "$output" = "Hello $hello" ]

+ 28 - 0
test/tests/python-hy/container.cmd

@@ -0,0 +1,28 @@
+@echo off
+
+for %%p in ( pypy3 pypy python3 python ) do (
+	%%p --version >nul 2>&1 && (
+		set python=%%p
+		goto found
+	)
+)
+echo unable to run Hy test -- seems this image does not contain Python? >&2
+exit /b 1
+
+:found
+%python% --version >nul 2>&1 || exit /b %errorlevel%
+
+rem Hy is complicated, and uses Python's internal AST representation directly, and thus Hy releases usually lag behind a little on major Python releases (and we don't want that to gum up our tests)
+rem see https://github.com/hylang/hy/issues/1111 for example breakage
+%python% -c "import sys; exit((sys.version_info[0] == 3 and sys.version_info[1] >= 8) or sys.version_info[0] > 3)" || (
+	echo skipping Hy test -- not allowed on Python 3.8+ ^(yet!^) >&2
+	rem cheaters gunna cheat
+	type expected-std-out.txt
+	exit /b 0
+)
+
+pip install -q "hy==0.17.0" || exit /b %errorlevel%
+
+hy ./container.hy || exit /b %errorlevel%
+
+exit /b 0

+ 5 - 3
test/tests/python-hy/container.sh

@@ -1,5 +1,5 @@
 #!/bin/sh
-set -e
+set -eu
 
 python=
 for c in pypy3 pypy python3 python; do
@@ -18,9 +18,11 @@ fi
 # see https://github.com/hylang/hy/issues/1111 for example breakage
 if ! "$python" -c 'import sys; exit((sys.version_info[0] == 3 and sys.version_info[1] >= 8) or sys.version_info[0] > 3)'; then
 	echo >&2 'skipping Hy test -- not allowed on Python 3.8+ (yet!)'
-	cat expected-std-out.txt # cheaters gunna cheat
+	# cheaters gunna cheat
+	cat expected-std-out.txt
 	exit
 fi
 
-pip install -q 'hy==0.16.0'
+pip install -q 'hy==0.17.0'
+
 hy ./container.hy

+ 16 - 14
test/tests/python-imports/container.py

@@ -1,23 +1,25 @@
-import curses
-import readline
-
-import bz2
-assert(bz2.decompress(bz2.compress(b'IT WORKS IT WORKS IT WORKS')) == b'IT WORKS IT WORKS IT WORKS')
-
 import platform
 
+isWindows = platform.system() == 'Windows'
 isNotPypy = platform.python_implementation() != 'PyPy'
 isCaveman = platform.python_version_tuple()[0] == '2'
 
-if isCaveman:
-    import gdbm
-else:
-    import dbm.gnu
+if not isWindows:
+    import curses
+    import readline
 
-    if isNotPypy:
-        # PyPy and Python 2 don't support lzma
-        import lzma
-        assert(lzma.decompress(lzma.compress(b'IT WORKS IT WORKS IT WORKS')) == b'IT WORKS IT WORKS IT WORKS')
+    if isCaveman:
+        import gdbm
+    else:
+        import dbm.gnu
+
+        if isNotPypy:
+            # PyPy and Python 2 don't support lzma
+            import lzma
+            assert(lzma.decompress(lzma.compress(b'IT WORKS IT WORKS IT WORKS')) == b'IT WORKS IT WORKS IT WORKS')
+
+import bz2
+assert(bz2.decompress(bz2.compress(b'IT WORKS IT WORKS IT WORKS')) == b'IT WORKS IT WORKS IT WORKS')
 
 import zlib
 assert(zlib.decompress(zlib.compress(b'IT WORKS IT WORKS IT WORKS')) == b'IT WORKS IT WORKS IT WORKS')

+ 14 - 10
test/tests/run-in-container.sh

@@ -1,11 +1,11 @@
-#!/bin/bash
-set -eo pipefail
+#!/usr/bin/env bash
+set -Eeuo pipefail
 
 # NOT INTENDED TO BE USED AS A TEST "run.sh" DIRECTLY
 # SEE OTHER "run-*-in-container.sh" SCRIPTS FOR USAGE
 
 # arguments to docker
-args=()
+args=( --rm )
 opts="$(getopt -o '+' --long 'docker-arg:' -- "$@")"
 eval set -- "$opts"
 
@@ -34,7 +34,8 @@ entrypoint="$1"
 shift
 
 # do some fancy footwork so that if testDir is /a/b/c, we mount /a/b and use c as the working directory (so relative symlinks work one level up)
-thisDir="$(dirname "$(readlink -f "$BASH_SOURCE")")"
+thisDir="$(readlink -f "$BASH_SOURCE")"
+thisDir="$(dirname "$thisDir")"
 testDir="$(readlink -f "$testDir")"
 testBase="$(basename "$testDir")"
 hostMount="$(dirname "$testDir")"
@@ -47,15 +48,12 @@ newImage="$("$thisDir/image-name.sh" librarytest/run-in-container "$image--$test
 FROM $image
 COPY dir $containerMount
 WORKDIR $workdir
-ENTRYPOINT ["$entrypoint"]
 EOD
 
-args+=( --rm )
-
 # there is strong potential for nokogiri+overlayfs failure
 # see https://github.com/docker-library/ruby/issues/55
-gemHome="$(docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$newImage" | awk -F '=' '$1 == "GEM_HOME" { print $2; exit }')"
-if [ "$gemHome" ]; then
+gemHome="$(docker image inspect --format '{{- range .Config.Env -}}{{- println . -}}{{- end -}}' "$newImage" | awk -F '=' '$1 == "GEM_HOME" { print $2; exit }')"
+if [ -n "$gemHome" ]; then
 	# must be a Ruby image
 	driver="$(docker info --format '{{ .Driver }}' 2>/dev/null)"
 	if [ "$driver" = 'overlay' ]; then
@@ -64,4 +62,10 @@ if [ "$gemHome" ]; then
 	fi
 fi
 
-exec docker run "${args[@]}" "$newImage" "$@"
+args+=( --entrypoint "$entrypoint" )
+
+# we can't use "exec" here because Windows needs to override "docker" with a function that sets "MSYS_NO_PATHCONV" (see "test/run.sh" for where that's defined)
+if ! docker run "${args[@]}" "$newImage" "$@"; then
+	exit 1
+fi
+exit 0

+ 13 - 19
test/tests/run-java-in-container.sh

@@ -1,5 +1,5 @@
-#!/bin/bash
-set -e
+#!/usr/bin/env bash
+set -Eeuo pipefail
 
 testDir="$(readlink -f "$(dirname "$BASH_SOURCE")")"
 runDir="$(dirname "$(readlink -f "$BASH_SOURCE")")"
@@ -8,21 +8,15 @@ image="$1"
 # TODO make this work for ibmjava too (jre or sfj -> sdk)
 jdk="${image/jre/jdk}"
 
-volume="$(docker volume create)"
-trap "docker volume rm '$volume' &> /dev/null" EXIT
+newImage="$("$runDir/image-name.sh" librarytest/java-hello-world "$image")"
+"$runDir/docker-build.sh" "$testDir" "$newImage" <<EOD
+FROM $jdk AS jdk
+WORKDIR /container
+COPY dir/container.java ./
+RUN javac ./container.java
+FROM $image
+COPY --from=jdk /container /container
+WORKDIR /container
+EOD
 
-# jdk image to build java class
-"$runDir/run-in-container.sh" \
-	--docker-arg "--volume=$volume:/container/" \
-	-- \
-	"$testDir" \
-	"$jdk" \
-	javac -d /container/ ./container.java
-
-# jre image to run class
-"$runDir/run-in-container.sh" \
-	--docker-arg "--volume=$volume:/container/" \
-	-- \
-	"$testDir" \
-	"$image" \
-	java -cp /container/ container
+docker run --rm "$newImage" java -cp . container

+ 33 - 12
test/tests/run-python-in-container.sh

@@ -1,15 +1,36 @@
-#!/bin/bash
-set -e
+#!/usr/bin/env bash
+set -Eeuo pipefail
 
-testDir="$(readlink -f "$(dirname "$BASH_SOURCE")")"
-runDir="$(dirname "$(readlink -f "$BASH_SOURCE")")"
+testDir="$(dirname "$BASH_SOURCE")"
+testDir="$(readlink -f "$testDir")"
+runDir="$(readlink -f "$BASH_SOURCE")"
+runDir="$(dirname "$runDir")"
 
-source "$runDir/run-in-container.sh" "$testDir" "$1" sh -ec '
-	for c in pypy3 pypy python3 python; do
-		if [ -x "/usr/local/bin/$c" ]; then
-			exec "/usr/local/bin/$c" "$@"
+case "$1" in
+	*windowsservercore* | *nanoserver*)
+		# https://stackoverflow.com/q/34491463/433558 -- cmd doesn't process past the first newline in the string passed on the command line, even though CreateProcess supports passing newlines??
+		# https://stackoverflow.com/a/52003129/433558 -- no goto / labels in cmd argument either??  what's even the point??
+		# -- "And, in any case, remember that the longest command line you can write is 8191 characters long."  ...
+		# cmd /C 'for %x in ( foo bar baz ) do ( echo %x ) & echo hi' runs '( echo %x ) & echo hi' every iteration.........
+		# so we'll just run twice and use the bash we're in to do the "difficult" work of a fallback when python can't be found... (even though every container has a higher cost on Windows ;.;)
+		python="$(docker run --rm --entrypoint cmd "$1" /Q /S /C 'for %p in ( pypy3 pypy python3 python ) do ( %p --version >nul 2>&1 && echo %p && exit 0 )' | tr -d '\r')"
+		python="${python% }" # "echo %p && ..." will print the trailing space because cmd...
+		if [ -z "$python" ]; then
+			echo >&2 'error: unable to determine how to run python'
+			exit 1
 		fi
-	done
-	echo >&2 "error: unable to determine how to run python"
-	exit 1
-' -- ./container.py
+		source "$runDir/run-in-container.sh" "$testDir" "$1" "$python" container.py
+		;;
+
+	*)
+		source "$runDir/run-in-container.sh" "$testDir" "$1" sh -ec '
+			for c in pypy3 pypy python3 python; do
+				if [ -x "/usr/local/bin/$c" ]; then
+					exec "/usr/local/bin/$c" "$@"
+				fi
+			done
+			echo >&2 "error: unable to determine how to run python"
+			exit 1
+		' -- ./container.py
+		;;
+esac

+ 17 - 5
test/tests/run-sh-in-container.sh

@@ -1,7 +1,19 @@
-#!/bin/bash
-set -e
+#!/usr/bin/env bash
+set -Eeuo pipefail
 
-testDir="$(readlink -f "$(dirname "$BASH_SOURCE")")"
-runDir="$(dirname "$(readlink -f "$BASH_SOURCE")")"
+testDir="$(dirname "$BASH_SOURCE")"
+testDir="$(readlink -f "$testDir")"
+runDir="$(readlink -f "$BASH_SOURCE")"
+runDir="$(dirname "$runDir")"
 
-source "$runDir/run-in-container.sh" "$testDir" "$1" sh ./container.sh
+case "$1" in
+	*windowsservercore* | *nanoserver*)
+		[ -f "$testDir/container.cmd" ]
+		source "$runDir/run-in-container.sh" "$testDir" "$1" cmd /Q /S /C '.\container.cmd'
+		;;
+
+	*)
+		[ -f "$testDir/container.sh" ]
+		source "$runDir/run-in-container.sh" "$testDir" "$1" sh ./container.sh
+		;;
+esac