Skip to content

Commit f366418

Browse files
committed
test: Add first test build
* Add the first test build due to the Github issue kylemanna#33
1 parent f4b6b3c commit f366418

9 files changed

+361
-0
lines changed

test/README.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Tests
2+
3+
Philosophy is to not re-invent the wheel while allowing users to quickly test repository specific tests.
4+
5+
Example invocation from top-level of repository:
6+
7+
docker build -t bitcoind .
8+
tests/run.sh bitcoind
9+
10+
More details: https://github.com/docker-library/official-images/tree/master/test
11+
12+
## Continuous Integration
13+
14+
The set of scripts defined by `config.sh` are run every time a pull request or push to the repository is made.
15+
16+
## Maintenance
17+
18+
Periodically these scripts may need to be synchronized with their upsteam source. Would be nice to be able to just use them from upstream if it such a feature is added later to avoid having to copy them in place.

test/config.sh

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
set -e
3+
4+
testAlias+=(
5+
[bitcoind:trusty]='bitcoind'
6+
)
7+
8+
imageTests+=(
9+
[bitcoind]='
10+
rpcpassword
11+
'
12+
)

test/run.sh

+202
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
#!/bin/bash
2+
set -e
3+
4+
dir="$(dirname "$(readlink -f "$BASH_SOURCE")")"
5+
6+
self="$(basename "$0")"
7+
8+
usage() {
9+
cat <<EOUSAGE
10+
11+
usage: $self [-t test ...] image:tag [...]
12+
ie: $self debian:wheezy
13+
$self -t utc python:3
14+
$self -t utc python:3 -t python-hy
15+
16+
This script processes the specified Docker images to test their running
17+
environments.
18+
EOUSAGE
19+
}
20+
21+
# arg handling
22+
opts="$(getopt -o 'ht:c:?' --long 'dry-run,help,test:,config:' -- "$@" || { usage >&2 && false; })"
23+
eval set -- "$opts"
24+
25+
declare -A argTests=()
26+
declare -a configs=()
27+
dryRun=
28+
while true; do
29+
flag=$1
30+
shift
31+
case "$flag" in
32+
--dry-run) dryRun=1 ;;
33+
--help|-h|'-?') usage && exit 0 ;;
34+
--test|-t) argTests["$1"]=1 && shift ;;
35+
--config|-c) configs+=("$(readlink -f "$1")") && shift ;;
36+
--) break ;;
37+
*)
38+
{
39+
echo "error: unknown flag: $flag"
40+
usage
41+
} >&2
42+
exit 1
43+
;;
44+
esac
45+
done
46+
47+
if [ $# -eq 0 ]; then
48+
usage >&2
49+
exit 1
50+
fi
51+
52+
# declare configuration variables
53+
declare -a globalTests=()
54+
declare -A testAlias=()
55+
declare -A imageTests=()
56+
declare -A globalExcludeTests=()
57+
declare -A explicitTests=()
58+
59+
# if there are no user-specified configs, use the default config
60+
if [ ${#configs} -eq 0 ]; then
61+
configs+=("$dir/config.sh")
62+
fi
63+
64+
# load the configs
65+
declare -A testPaths=()
66+
for conf in "${configs[@]}"; do
67+
. "$conf"
68+
69+
# Determine the full path to any newly-declared tests
70+
confDir="$(dirname "$conf")"
71+
72+
for testName in ${globalTests[@]} ${imageTests[@]}; do
73+
[ "${testPaths[$testName]}" ] && continue
74+
75+
if [ -d "$confDir/tests/$testName" ]; then
76+
# Test directory found relative to the conf file
77+
testPaths[$testName]="$confDir/tests/$testName"
78+
elif [ -d "$dir/tests/$testName" ]; then
79+
# Test directory found in the main tests/ directory
80+
testPaths[$testName]="$dir/tests/$testName"
81+
fi
82+
done
83+
done
84+
85+
didFail=
86+
for dockerImage in "$@"; do
87+
echo "testing $dockerImage"
88+
89+
if ! docker inspect "$dockerImage" &> /dev/null; then
90+
echo $'\timage does not exist!'
91+
didFail=1
92+
continue
93+
fi
94+
95+
repo="${dockerImage%:*}"
96+
tagVar="${dockerImage#*:}"
97+
#version="${tagVar%-*}"
98+
variant="${tagVar##*-}"
99+
100+
testRepo=$repo
101+
[ -z "${testAlias[$repo]}" ] || testRepo="${testAlias[$repo]}"
102+
103+
explicitVariant=
104+
if [ \
105+
"${explicitTests[:$variant]}" \
106+
-o "${explicitTests[$repo:$variant]}" \
107+
-o "${explicitTests[$testRepo:$variant]}" \
108+
]; then
109+
explicitVariant=1
110+
fi
111+
112+
testCandidates=()
113+
if [ -z "$explicitVariant" ]; then
114+
testCandidates+=( "${globalTests[@]}" )
115+
fi
116+
testCandidates+=(
117+
${imageTests[:$variant]}
118+
)
119+
if [ -z "$explicitVariant" ]; then
120+
testCandidates+=(
121+
${imageTests[$testRepo]}
122+
)
123+
fi
124+
testCandidates+=(
125+
${imageTests[$testRepo:$variant]}
126+
)
127+
if [ "$testRepo" != "$repo" ]; then
128+
if [ -z "$explicitVariant" ]; then
129+
testCandidates+=(
130+
${imageTests[$repo]}
131+
)
132+
fi
133+
testCandidates+=(
134+
${imageTests[$repo:$variant]}
135+
)
136+
fi
137+
138+
tests=()
139+
for t in "${testCandidates[@]}"; do
140+
if [ ${#argTests[@]} -gt 0 -a -z "${argTests[$t]}" ]; then
141+
# skipping due to -t
142+
continue
143+
fi
144+
145+
if [ \
146+
! -z "${globalExcludeTests[${testRepo}_$t]}" \
147+
-o ! -z "${globalExcludeTests[${testRepo}:${variant}_$t]}" \
148+
-o ! -z "${globalExcludeTests[:${variant}_$t]}" \
149+
-o ! -z "${globalExcludeTests[${repo}_$t]}" \
150+
-o ! -z "${globalExcludeTests[${repo}:${variant}_$t]}" \
151+
-o ! -z "${globalExcludeTests[:${variant}_$t]}" \
152+
]; then
153+
# skipping due to exclude
154+
continue
155+
fi
156+
157+
tests+=( "$t" )
158+
done
159+
160+
currentTest=0
161+
totalTest="${#tests[@]}"
162+
for t in "${tests[@]}"; do
163+
(( currentTest+=1 ))
164+
echo -ne "\t'$t' [$currentTest/$totalTest]..."
165+
166+
# run test against dockerImage here
167+
# find the script for the test
168+
scriptDir="${testPaths[$t]}"
169+
if [ -d "$scriptDir" ]; then
170+
script="$scriptDir/run.sh"
171+
if [ -x "$script" -a ! -d "$script" ]; then
172+
# TODO dryRun logic
173+
if output="$("$script" $dockerImage)"; then
174+
if [ -f "$scriptDir/expected-std-out.txt" ] && ! d="$(echo "$output" | diff -u "$scriptDir/expected-std-out.txt" - 2>/dev/null)"; then
175+
echo 'failed; unexpected output:'
176+
echo "$d"
177+
didFail=1
178+
else
179+
echo 'passed'
180+
fi
181+
else
182+
echo 'failed'
183+
didFail=1
184+
fi
185+
else
186+
echo "skipping"
187+
echo >&2 "error: $script missing, not executable or is a directory"
188+
didFail=1
189+
continue
190+
fi
191+
else
192+
echo "skipping"
193+
echo >&2 "error: unable to locate test '$t'"
194+
didFail=1
195+
continue
196+
fi
197+
done
198+
done
199+
200+
if [ "$didFail" ]; then
201+
exit 1
202+
fi

test/tests/docker-build.sh

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# wrapper around "docker build" that creates a temporary directory and copies files into it first so that arbitrary host directories can be copied into containers without bind mounts, but accepts a Dockerfile on stdin
5+
6+
# usage: ./docker-build.sh some-host-directory some-new-image:some-tag <<EOD
7+
# FROM ...
8+
# COPY dir/... /.../
9+
# EOD
10+
# ie: ./docker-build.sh .../hylang-hello-world librarytest/hylang <<EOD
11+
# FROM hylang
12+
# COPY dir/container.hy /dir/
13+
# CMD ["hy", "/dir/container.hy"]
14+
# EOD
15+
16+
dir="$1"; shift
17+
[ -d "$dir" ]
18+
19+
imageTag="$1"; shift
20+
21+
tmp="$(mktemp -t -d docker-library-test-build-XXXXXXXXXX)"
22+
trap "rm -rf '$tmp'" EXIT
23+
24+
cat > "$tmp/Dockerfile"
25+
26+
from="$(awk -F '[ \t]+' 'toupper($1) == "FROM" { print $2; exit }' "$tmp/Dockerfile")"
27+
onbuilds="$(docker inspect -f '{{len .Config.OnBuild}}' "$from")"
28+
if [ "$onbuilds" -gt 0 ]; then
29+
# crap, the image we want to build has some ONBUILD instructions
30+
# those are kind of going to ruin our day
31+
# let's do some hacks to strip those bad boys out in a new fake layer
32+
"$(dirname "$(readlink -f "$BASH_SOURCE")")/remove-onbuild.sh" "$from" "$imageTag"
33+
awk -F '[ \t]+' 'toupper($1) == "FROM" { $2 = "'"$imageTag"'" } { print }' "$tmp/Dockerfile" > "$tmp/Dockerfile.new"
34+
mv "$tmp/Dockerfile.new" "$tmp/Dockerfile"
35+
fi
36+
37+
cp -RL "$dir" "$tmp/dir"
38+
39+
docker build -t "$imageTag" "$tmp" > /dev/null

test/tests/image-name.sh

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# usage: ./image-name.sh librarytest/something some/image:some-tag
5+
# output: librarytest/something:some-image-some-tag
6+
7+
base="$1"; shift
8+
tag="$1"; shift
9+
10+
echo "$base:$(echo "$tag" | sed 's![:/]!-!g')"

test/tests/rpcpassword/container.sh

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Generate the password the first time
5+
btc_init 2>/dev/null
6+
eval `grep rpcpassword $HOME/.bitcoin/bitcoin.conf`
7+
rpcpassword1=$rpcpassword
8+
9+
# Generate the password again
10+
rm ~/.bitcoin/bitcoin.conf
11+
btc_init 2>/dev/null
12+
eval `grep rpcpassword $HOME/.bitcoin/bitcoin.conf`
13+
rpcpassword2=$rpcpassword
14+
15+
16+
# Check that password looks like a auto-generated base64 random value or better
17+
if [ ${#rpcpassword} -lt 16 ]; then
18+
echo "FAIL: RPC Password does not appear long enough" >&2
19+
exit 1
20+
fi
21+
22+
# Check that each password was at least different
23+
if [ "$rpcpassword1" = "$rpcpassword2" ]; then
24+
echo "FAIL: RPC Password does not appear to be random" >&2
25+
exit 2
26+
fi

test/tests/rpcpassword/run.sh

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../run-bash-in-container.sh

test/tests/run-bash-in-container.sh

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
set -e
3+
4+
testDir="$(readlink -f "$(dirname "$BASH_SOURCE")")"
5+
runDir="$(dirname "$(readlink -f "$BASH_SOURCE")")"
6+
7+
source "$runDir/run-in-container.sh" "$testDir" "$1" bash ./container.sh

test/tests/run-in-container.sh

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# NOT INTENDED TO BE USED AS A TEST "run.sh" DIRECTLY
5+
# SEE OTHER "run-*-in-container.sh" SCRIPTS FOR USAGE
6+
7+
testDir="$1"
8+
shift
9+
10+
image="$1"
11+
shift
12+
entrypoint="$1"
13+
shift
14+
15+
# 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)
16+
thisDir="$(dirname "$(readlink -f "$BASH_SOURCE")")"
17+
testDir="$(readlink -f "$testDir")"
18+
testBase="$(basename "$testDir")"
19+
hostMount="$(dirname "$testDir")"
20+
containerMount="/tmp/test-dir"
21+
workdir="$containerMount/$testBase"
22+
# TODO should we be doing something fancy with $BASH_SOURCE instead so we can be arbitrarily deep and mount the top level always?
23+
24+
newImage="$("$thisDir/image-name.sh" librarytest/run-in-container "$image--$testBase")"
25+
"$thisDir/docker-build.sh" "$hostMount" "$newImage" <<EOD
26+
FROM $image
27+
COPY dir $containerMount
28+
WORKDIR $workdir
29+
ENTRYPOINT ["$entrypoint"]
30+
EOD
31+
32+
args=( --rm )
33+
34+
# there is strong potential for nokogiri+overlayfs failure
35+
# see https://github.com/docker-library/ruby/issues/55
36+
gemHome="$(docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' "$newImage" | awk -F '=' '$1 == "GEM_HOME" { print $2; exit }')"
37+
if [ "$gemHome" ]; then
38+
# must be a Ruby image
39+
driver="$(docker info | awk -F ': ' '$1 == "Storage Driver" { print $2; exit }')"
40+
if [ "$driver" = 'overlay' ]; then
41+
# let's add a volume (_not_ a bind mount) on GEM_HOME to work around nokogiri+overlayfs issues
42+
args+=( -v "$gemHome" )
43+
fi
44+
fi
45+
46+
exec docker run "${args[@]}" "$newImage" "$@"

0 commit comments

Comments
 (0)