Repository navigation
Expand file tree
/
Copy pathtest
More file actions
executable file
·215 lines (195 loc) · 7.34 KB
/
Copy pathtest
File metadata and controls
executable file
·215 lines (195 loc) · 7.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
#!/usr/bin/env bash
#
# Runs the full test suite (crystal unit specs + chip-tool end-to-end specs)
# inside docker compose, then tears the environment down.
#
# Works with bash 3.2+ (macOS) and any Linux with docker compose v2
# (or the legacy docker-compose v1 binary).
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT_DIR"
usage() {
cat <<'USAGE'
Usage: ./test [options] [-- <crystal spec args>]
Builds the e2e docker images, starts every example device plus a test runner
(crystal + official chip-tool) with docker compose, runs the unit specs and the
end-to-end specs, prints PASS/FAIL and exits 0 on success / 1 on failure.
Options:
--unit-only Only run the standard crystal specs (spec/)
--e2e-only Only run the end-to-end specs (e2e/spec/)
--no-build Don't rebuild the docker images
--keep Leave the compose environment running after the tests
--clean Also remove volumes (crystal cache) when tearing down
--verbose Stream every container's output (default: test runner only)
--focused Run only explicitly selected E2E services and spec files
--service NAME Compose service required by a focused spec (repeatable)
-h, --help Show this help
Anything after `--` is passed to `crystal spec` for the e2e run, e.g.
./test --e2e-only -- e2e/spec/switch_spec.cr
./test --focused --service door-lock-aliro --service door-lock-verified -- \
e2e/spec/door_lock_aliro_spec.cr e2e/spec/door_lock_verified_spec.cr
./test -- -e "toggles"
Logs from every container are written to tmp/e2e/logs/ after each run.
USAGE
}
RUN_UNIT=1
RUN_E2E=1
BUILD=1
KEEP=0
CLEAN=0
VERBOSE=0
FOCUSED=0
SERVICES=()
SPEC_ARGS=""
SPEC_FILES=0
while [[ $# -gt 0 ]]; do
case "$1" in
--unit-only) RUN_E2E=0; shift ;;
--e2e-only) RUN_UNIT=0; shift ;;
--no-build) BUILD=0; shift ;;
--keep) KEEP=1; shift ;;
--clean) CLEAN=1; shift ;;
--verbose) VERBOSE=1; shift ;;
--focused) FOCUSED=1; RUN_UNIT=0; shift ;;
--service)
if [[ $# -lt 2 || "$2" == -* ]]; then
echo "ERROR: --service requires a Compose service name" >&2; exit 2
fi
SERVICES+=("$2"); shift 2 ;;
-h|--help) usage; exit 0 ;;
--)
shift
SPEC_ARGS="$*"
for arg in "$@"; do
if [[ "$arg" == e2e/spec/*_spec.cr && -f "$arg" ]]; then
SPEC_FILES=$((SPEC_FILES + 1))
elif [[ "$FOCUSED" -eq 1 && ( "$arg" == e2e/spec* || "$arg" == spec || "$arg" == spec/* || "$arg" == *.cr ) ]]; then
echo "ERROR: focused E2E needs existing e2e/spec/*_spec.cr files" >&2
exit 2
fi
done
break ;;
*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
esac
done
if [[ "$FOCUSED" -eq 1 ]]; then
if [[ "${#SERVICES[@]}" -eq 0 || "$SPEC_FILES" -eq 0 || "$BUILD" -eq 0 || "$RUN_E2E" -eq 0 ]]; then
echo "ERROR: --focused requires --service, explicit e2e spec file(s), and an image build" >&2
exit 2
fi
elif [[ "${#SERVICES[@]}" -ne 0 ]]; then
echo "ERROR: --service requires --focused" >&2
exit 2
fi
# ---------------------------------------------------------------------------
# docker compose detection (v2 plugin preferred, v1 binary as a fallback)
# ---------------------------------------------------------------------------
if ! command -v docker >/dev/null 2>&1; then
echo "ERROR: docker is not installed or not on PATH" >&2
exit 2
fi
if docker compose version >/dev/null 2>&1; then
compose() { docker compose "$@"; }
COMPOSE_V2=1
elif command -v docker-compose >/dev/null 2>&1; then
compose() { docker-compose "$@"; }
COMPOSE_V2=0
else
echo "ERROR: neither 'docker compose' nor 'docker-compose' is available" >&2
exit 2
fi
if ! docker info >/dev/null 2>&1; then
echo "ERROR: the docker daemon is not running or not reachable" >&2
exit 2
fi
if [[ "$FOCUSED" -eq 1 ]]; then
if [[ "$COMPOSE_V2" -eq 0 ]]; then
echo "ERROR: --focused requires Docker Compose v2" >&2; exit 2
fi
if ! command -v python3 >/dev/null 2>&1; then
echo "ERROR: --focused needs Python 3 to resolve Compose services" >&2; exit 2
fi
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-matter-e2e-focused-$$}"
export MATTER_E2E_DEVICE_IMAGE="matter-e2e-focused-device-$$"
export MATTER_E2E_TESTS_IMAGE="matter-e2e-focused-tests-$$"
export MATTER_E2E_BUILD_JOBS="${MATTER_E2E_BUILD_JOBS:-1}"
MATTER_E2E_BINARIES="$(compose config --format json | python3 e2e/focused_services.py "${SERVICES[@]}")" || exit 2
if [[ -z "$MATTER_E2E_BINARIES" ]]; then
echo "ERROR: --focused needs at least one Crystal device service" >&2; exit 2
fi
MATTER_E2E_BINARIES="device_validation $MATTER_E2E_BINARIES"
export MATTER_E2E_BINARIES
echo "==> Focused services: ${SERVICES[*]}"
echo "==> Focused binaries: $MATTER_E2E_BINARIES"
else
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-matter-e2e}"
fi
export E2E_RUN_UNIT="$RUN_UNIT"
export E2E_RUN_E2E="$RUN_E2E"
export E2E_SPEC_ARGS="$SPEC_ARGS"
# The official SDK recipient's initial commissioning window lasts 15 minutes.
# Give each run a fresh trigger so that recipient starts when its spec begins.
export MATTER_E2E_RUN_ID="$(date +%s)-$$"
LOG_DIR="$ROOT_DIR/tmp/e2e/logs"
mkdir -p "$LOG_DIR"
RUN_LOG="$LOG_DIR/compose-$(date +%Y%m%d-%H%M%S).log"
RESULT=1
teardown() {
# Always capture logs first: the device output (pairing codes, protocol
# debug logging) is the most useful thing to have when a test fails.
compose logs --no-color --timestamps >"$RUN_LOG" 2>&1 || true
if [[ "$KEEP" -eq 1 ]]; then
echo "==> Leaving compose environment running (project: $COMPOSE_PROJECT_NAME)"
echo " Tear down with: docker compose -p $COMPOSE_PROJECT_NAME down"
else
echo "==> Tearing down compose environment"
if [[ "$CLEAN" -eq 1 ]]; then
compose down --remove-orphans --volumes >/dev/null 2>&1 || true
else
compose down --remove-orphans >/dev/null 2>&1 || true
fi
if [[ "$FOCUSED" -eq 1 ]]; then
docker image rm "$MATTER_E2E_DEVICE_IMAGE" "$MATTER_E2E_TESTS_IMAGE" >/dev/null 2>&1 || true
fi
fi
echo "==> Container logs saved to: ${RUN_LOG#"$ROOT_DIR"/}"
if [[ "$RESULT" -eq 0 ]]; then
echo "==> PASS"
else
echo "==> FAIL (exit code $RESULT)"
fi
exit "$RESULT"
}
trap teardown EXIT
trap 'RESULT=130; exit 130' INT TERM
# Make sure nothing is left over from a previous (possibly --keep) run so every
# device starts un-commissioned.
compose down --remove-orphans >/dev/null 2>&1 || true
UP_ARGS=(--exit-code-from tests --abort-on-container-exit)
if [[ "$BUILD" -eq 1 ]]; then
echo "==> Building images"
# Both targets share builder layers. Exporting them concurrently can race in
# Docker's snapshot store even though the compiler cache mount is locked.
compose build switch
compose build tests
fi
if [[ "$FOCUSED" -eq 1 ]]; then
echo "==> Starting selected devices"
compose up --no-build --no-deps -d --wait "${SERVICES[@]}"
echo "==> Running selected E2E specs"
set +e
compose run --rm --no-deps --use-aliases tests
RESULT=$?
set -e
exit "$RESULT"
fi
# Only stream the test runner's output unless --verbose (legacy docker-compose v1
# has no --attach, in which case everything is streamed).
if [[ "$VERBOSE" -eq 0 ]] && compose up --help 2>&1 | grep -q -- '--attach '; then
UP_ARGS+=(--attach tests)
fi
echo "==> Starting devices and running tests"
set +e
compose up --no-build "${UP_ARGS[@]}"
RESULT=$?
set -e