Add Home Assistant Stelloauth app #1
@@ -100,3 +100,27 @@ def test_dockerfile_patches_parent_cloakserve_instead_of_copying_a_binary() -> N
|
|||||||
def test_runtime_accepts_docker_port_unpublished_status() -> None:
|
def test_runtime_accepts_docker_port_unpublished_status() -> None:
|
||||||
runtime_test = (ROOT / "tests/test_runtime.sh").read_text(encoding="utf-8")
|
runtime_test = (ROOT / "tests/test_runtime.sh").read_text(encoding="utf-8")
|
||||||
assert 'docker port "$container" 9222/tcp 2>/dev/null || true' in runtime_test
|
assert 'docker port "$container" 9222/tcp 2>/dev/null || true' in runtime_test
|
||||||
|
|
||||||
|
|
||||||
|
def test_runtime_requires_process_baseline_after_cdp_close_and_zero_stopped_pid() -> None:
|
||||||
|
runtime_test = (ROOT / "tests/test_runtime.sh").read_text(encoding="utf-8")
|
||||||
|
assert 'docker top "$container" -eo pid,args' in runtime_test
|
||||||
|
for prefix in ("first", "second"):
|
||||||
|
container = f"${prefix}_container"
|
||||||
|
baseline = (
|
||||||
|
f'{prefix}_baseline="$(capture_process_baseline "{container}")"'
|
||||||
|
)
|
||||||
|
ready_index = runtime_test.index(
|
||||||
|
f'wait_ready "{container}" "${prefix}_port"'
|
||||||
|
)
|
||||||
|
baseline_index = runtime_test.index(baseline)
|
||||||
|
close_index = runtime_test.index(
|
||||||
|
f'probe_and_close_cdp "{container}"', baseline_index
|
||||||
|
)
|
||||||
|
return_index = runtime_test.index(
|
||||||
|
f'assert_processes_return_to_baseline "{container}" "${prefix}_baseline"',
|
||||||
|
close_index,
|
||||||
|
)
|
||||||
|
assert ready_index < baseline_index < close_index < return_index
|
||||||
|
assert "{{.State.Pid}}" in runtime_test
|
||||||
|
assert '[ "$state" = "exited 0 0" ]' in runtime_test
|
||||||
|
|||||||
+76
-2
@@ -108,6 +108,76 @@ wait_ready() {
|
|||||||
fail "$container did not become ready within 90 seconds"
|
fail "$container did not become ready within 90 seconds"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
normalized_process_commands() {
|
||||||
|
local container="$1"
|
||||||
|
local top_file="${tmp_dir}/${container}-processes.txt"
|
||||||
|
docker top "$container" -eo pid,args > "$top_file"
|
||||||
|
python3 - "$top_file" <<'PY'
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
commands = []
|
||||||
|
for raw_line in pathlib.Path(sys.argv[1]).read_text(encoding="utf-8").splitlines()[1:]:
|
||||||
|
fields = raw_line.split(maxsplit=1)
|
||||||
|
command = " ".join(fields[1].split()) if len(fields) == 2 else ""
|
||||||
|
lowered = command.lower()
|
||||||
|
if "/usr/local/bin/addon-supervisor" in command:
|
||||||
|
commands.append("addon-supervisor")
|
||||||
|
elif "/usr/local/bin/cloakserve" in command:
|
||||||
|
commands.append("cloakserve")
|
||||||
|
elif "/usr/local/bin/stelloauth" in command:
|
||||||
|
commands.append("stelloauth")
|
||||||
|
elif (
|
||||||
|
("/run/rosetta/rosetta" in lowered or "qemu-" in lowered)
|
||||||
|
and "chrome" not in lowered
|
||||||
|
and "chromium" not in lowered
|
||||||
|
and "headless_shell" not in lowered
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
elif command:
|
||||||
|
commands.append(f"unexpected:{command}")
|
||||||
|
|
||||||
|
print("\n".join(sorted(commands)))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
capture_process_baseline() {
|
||||||
|
local container="$1"
|
||||||
|
local expected current
|
||||||
|
local deadline=$((SECONDS + 10))
|
||||||
|
expected=$'addon-supervisor\ncloakserve\nstelloauth'
|
||||||
|
while (( SECONDS < deadline )); do
|
||||||
|
current="$(normalized_process_commands "$container")"
|
||||||
|
if [ "$current" = "$expected" ]; then
|
||||||
|
printf '%s\n' "$current"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
sleep 0.25
|
||||||
|
done
|
||||||
|
printf 'expected startup process baseline:\n%s\ncurrent process commands:\n%s\n' \
|
||||||
|
"$expected" "$current" >&2
|
||||||
|
docker top "$container" >&2
|
||||||
|
fail "$container did not reach the expected startup process baseline"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_processes_return_to_baseline() {
|
||||||
|
local container="$1"
|
||||||
|
local baseline="$2"
|
||||||
|
local current
|
||||||
|
local deadline=$((SECONDS + 10))
|
||||||
|
while (( SECONDS < deadline )); do
|
||||||
|
current="$(normalized_process_commands "$container")"
|
||||||
|
if [ "$current" = "$baseline" ]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
sleep 0.25
|
||||||
|
done
|
||||||
|
printf 'expected process baseline after CDP close:\n%s\ncurrent process commands:\n%s\n' \
|
||||||
|
"$baseline" "$current" >&2
|
||||||
|
docker top "$container" >&2
|
||||||
|
fail "$container retained browser or profile processes after CDP close"
|
||||||
|
}
|
||||||
|
|
||||||
assert_loopback_cdp_listener() {
|
assert_loopback_cdp_listener() {
|
||||||
local container="$1"
|
local container="$1"
|
||||||
local artifact="$2"
|
local artifact="$2"
|
||||||
@@ -243,8 +313,8 @@ import sys
|
|||||||
if float(sys.argv[1]) > 10.0:
|
if float(sys.argv[1]) > 10.0:
|
||||||
raise SystemExit(f"container stop exceeded 10 seconds: {sys.argv[1]}")
|
raise SystemExit(f"container stop exceeded 10 seconds: {sys.argv[1]}")
|
||||||
PY
|
PY
|
||||||
state="$(docker inspect --format '{{.State.Status}} {{.State.ExitCode}}' "$container")"
|
state="$(docker inspect --format '{{.State.Status}} {{.State.ExitCode}} {{.State.Pid}}' "$container")"
|
||||||
[ "$state" = "exited 0" ] || fail "$container state is $state, want exited 0"
|
[ "$state" = "exited 0 0" ] || fail "$container state is $state, want exited 0 with PID 0"
|
||||||
}
|
}
|
||||||
|
|
||||||
mkdir -p "$artifacts_dir"
|
mkdir -p "$artifacts_dir"
|
||||||
@@ -262,9 +332,11 @@ fi
|
|||||||
start_container "$first_container"
|
start_container "$first_container"
|
||||||
first_port="$(host_port "$first_container")"
|
first_port="$(host_port "$first_container")"
|
||||||
wait_ready "$first_container" "$first_port"
|
wait_ready "$first_container" "$first_port"
|
||||||
|
first_baseline="$(capture_process_baseline "$first_container")"
|
||||||
assert_no_9222_mapping "$first_container"
|
assert_no_9222_mapping "$first_container"
|
||||||
assert_loopback_cdp_listener "$first_container" "${artifacts_dir}/runtime-proc-net-tcp.txt"
|
assert_loopback_cdp_listener "$first_container" "${artifacts_dir}/runtime-proc-net-tcp.txt"
|
||||||
probe_and_close_cdp "$first_container"
|
probe_and_close_cdp "$first_container"
|
||||||
|
assert_processes_return_to_baseline "$first_container" "$first_baseline"
|
||||||
post_invalid_worker "$first_port" "${artifacts_dir}/runtime-invalid-worker-response.json"
|
post_invalid_worker "$first_port" "${artifacts_dir}/runtime-invalid-worker-response.json"
|
||||||
scan_logs "$first_container"
|
scan_logs "$first_container"
|
||||||
stop_and_assert "$first_container" "${artifacts_dir}/runtime-first-stop.txt"
|
stop_and_assert "$first_container" "${artifacts_dir}/runtime-first-stop.txt"
|
||||||
@@ -273,9 +345,11 @@ scan_logs "$first_container"
|
|||||||
start_container "$second_container"
|
start_container "$second_container"
|
||||||
second_port="$(host_port "$second_container")"
|
second_port="$(host_port "$second_container")"
|
||||||
wait_ready "$second_container" "$second_port"
|
wait_ready "$second_container" "$second_port"
|
||||||
|
second_baseline="$(capture_process_baseline "$second_container")"
|
||||||
assert_no_9222_mapping "$second_container"
|
assert_no_9222_mapping "$second_container"
|
||||||
assert_loopback_cdp_listener "$second_container" "${artifacts_dir}/runtime-restart-proc-net-tcp.txt"
|
assert_loopback_cdp_listener "$second_container" "${artifacts_dir}/runtime-restart-proc-net-tcp.txt"
|
||||||
probe_and_close_cdp "$second_container"
|
probe_and_close_cdp "$second_container"
|
||||||
|
assert_processes_return_to_baseline "$second_container" "$second_baseline"
|
||||||
|
|
||||||
sleep 5
|
sleep 5
|
||||||
docker stats --no-stream "$second_container" > "${artifacts_dir}/runtime-docker-stats.txt"
|
docker stats --no-stream "$second_container" > "${artifacts_dir}/runtime-docker-stats.txt"
|
||||||
|
|||||||
Reference in New Issue
Block a user