Skip to content

Commit 99415bb

Browse files
committed
chore(common): add unit tests for compile
1 parent 550cc3c commit 99415bb

2 files changed

Lines changed: 242 additions & 1 deletion

File tree

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ ROOT_DIR := $(CURDIR)
66
BUILDPACK_DIR := $(ROOT_DIR)
77
PYTHON_BIN ?= python3
88

9-
.PHONY: test-buildpack clean-test-buildpack test-detect smoke-test start-local
9+
.PHONY: test-buildpack clean-test-buildpack test-detect test-compile smoke-test start-local
1010

1111
# Reset the temporary staging directories used for local buildpack testing.
1212
clean-test-buildpack:
@@ -16,6 +16,10 @@ clean-test-buildpack:
1616
test-detect:
1717
@bash ./test/unit/detect_test.sh
1818

19+
# Run unit tests for the compile script with stubbed python and uv commands.
20+
test-compile:
21+
@bash ./test/unit/compile_test.sh
22+
1923
# Run the sample app through detect, compile, and release using the same
2024
# temporary directories each time so local testing is repeatable.
2125
test-buildpack: clean-test-buildpack

test/unit/compile_test.sh

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
6+
COMPILE_SCRIPT="$ROOT_DIR/bin/compile"
7+
TMP_DIR="$(mktemp -d /tmp/compile-test.XXXXXX)"
8+
TEST_ROOT="$TMP_DIR/test-root"
9+
FAKE_BIN_DIR="$TMP_DIR/fake-bin"
10+
11+
cleanup() {
12+
rm -rf "$TMP_DIR"
13+
}
14+
15+
trap cleanup EXIT
16+
17+
assert_exit_code() {
18+
local actual="$1"
19+
local expected="$2"
20+
local message="$3"
21+
22+
if [ "$actual" -ne "$expected" ]; then
23+
echo "FAIL: $message"
24+
echo "Expected exit code: $expected"
25+
echo "Actual exit code: $actual"
26+
exit 1
27+
fi
28+
}
29+
30+
assert_contains() {
31+
local actual="$1"
32+
local expected="$2"
33+
local message="$3"
34+
35+
if [[ "$actual" != *"$expected"* ]]; then
36+
echo "FAIL: $message"
37+
echo "Expected to find: $expected"
38+
echo "Actual output: $actual"
39+
exit 1
40+
fi
41+
}
42+
43+
assert_file_contains() {
44+
local file_path="$1"
45+
local expected="$2"
46+
local message="$3"
47+
48+
if ! grep -Fq -- "$expected" "$file_path"; then
49+
echo "FAIL: $message"
50+
echo "Expected to find: $expected"
51+
echo "In file: $file_path"
52+
echo "Actual file contents:"
53+
cat "$file_path"
54+
exit 1
55+
fi
56+
}
57+
58+
assert_path_exists() {
59+
local file_path="$1"
60+
local message="$2"
61+
62+
if [ ! -e "$file_path" ]; then
63+
echo "FAIL: $message"
64+
echo "Missing path: $file_path"
65+
exit 1
66+
fi
67+
}
68+
69+
setup_fake_commands() {
70+
mkdir -p "$FAKE_BIN_DIR"
71+
72+
cat > "$FAKE_BIN_DIR/python3" <<'EOF'
73+
#!/usr/bin/env bash
74+
set -euo pipefail
75+
76+
if [ "$#" -ge 2 ] && [ "$1" = "-c" ] && [ "$2" = "import sys; print(f\"{sys.version_info.major}.{sys.version_info.minor}\")" ]; then
77+
printf '3.13\n'
78+
exit 0
79+
fi
80+
81+
if [ "$#" -ge 3 ] && [ "$1" = "-m" ] && [ "$2" = "pip" ]; then
82+
log_file="${TEST_ROOT}/pip.log"
83+
mkdir -p "$(dirname "$log_file")"
84+
printf '%s\n' "$*" >> "$log_file"
85+
86+
target_dir=""
87+
requirements_file=""
88+
89+
while [ "$#" -gt 0 ]; do
90+
case "$1" in
91+
--target)
92+
shift
93+
target_dir="$1"
94+
;;
95+
-r)
96+
shift
97+
requirements_file="$1"
98+
;;
99+
esac
100+
shift || true
101+
done
102+
103+
if [ -n "$target_dir" ]; then
104+
mkdir -p "$target_dir"
105+
touch "$target_dir/fake-installed-package.txt"
106+
fi
107+
108+
if [ -n "$requirements_file" ]; then
109+
printf '%s\n' "$requirements_file" > "${TEST_ROOT}/last-requirements-file.txt"
110+
fi
111+
112+
exit 0
113+
fi
114+
115+
echo "Unexpected python3 invocation: $*" >&2
116+
exit 1
117+
EOF
118+
119+
cat > "$FAKE_BIN_DIR/uv" <<'EOF'
120+
#!/usr/bin/env bash
121+
set -euo pipefail
122+
123+
log_file="${TEST_ROOT}/uv.log"
124+
mkdir -p "$(dirname "$log_file")"
125+
printf '%s\n' "$*" >> "$log_file"
126+
127+
if [ "$1" = "export" ]; then
128+
output_file=""
129+
130+
while [ "$#" -gt 0 ]; do
131+
case "$1" in
132+
-o)
133+
shift
134+
output_file="$1"
135+
;;
136+
esac
137+
shift || true
138+
done
139+
140+
cat > "$output_file" <<'REQ'
141+
fastapi==0.135.3
142+
uvicorn==0.44.0
143+
REQ
144+
exit 0
145+
fi
146+
147+
echo "Unexpected uv invocation: $*" >&2
148+
exit 1
149+
EOF
150+
151+
chmod +x "$FAKE_BIN_DIR/python3" "$FAKE_BIN_DIR/uv"
152+
}
153+
154+
run_compile() {
155+
local build_dir="$1"
156+
local cache_dir="$2"
157+
local env_dir="$3"
158+
local output_file="$TEST_ROOT/output.txt"
159+
160+
set +e
161+
PATH="$FAKE_BIN_DIR:/usr/bin:/bin" TEST_ROOT="$TEST_ROOT" "$COMPILE_SCRIPT" "$build_dir" "$cache_dir" "$env_dir" >"$output_file" 2>&1
162+
status=$?
163+
set -e
164+
165+
output="$(cat "$output_file")"
166+
}
167+
168+
test_compile_succeeds_for_locked_uv_project() {
169+
# Arrange
170+
local build_dir="$TEST_ROOT/success/build"
171+
local cache_dir="$TEST_ROOT/success/cache"
172+
local env_dir="$TEST_ROOT/success/env"
173+
local site_packages_dir="$build_dir/.python_packages/lib/python3.13/site-packages"
174+
local profile_file="$build_dir/.profile.d/python.sh"
175+
local export_file="$build_dir/.uv-export-requirements.txt"
176+
177+
mkdir -p "$build_dir" "$cache_dir" "$env_dir"
178+
touch "$build_dir/pyproject.toml" "$build_dir/uv.lock"
179+
setup_fake_commands
180+
181+
# Act
182+
run_compile "$build_dir" "$cache_dir" "$env_dir"
183+
184+
# Assert
185+
assert_exit_code "$status" 0 "compile should succeed for a locked uv project"
186+
assert_contains "$output" "Detected uv project with lockfile. Installing dependencies with uv." "compile should announce supported uv projects"
187+
assert_path_exists "$site_packages_dir" "compile should create the staged site-packages directory"
188+
assert_path_exists "$profile_file" "compile should write a profile script for runtime imports"
189+
assert_path_exists "$export_file" "compile should write the exported requirements file"
190+
assert_file_contains "$profile_file" "$site_packages_dir" "profile script should add staged dependencies to PYTHONPATH"
191+
assert_file_contains "$TEST_ROOT/uv.log" "export --locked --format requirements-txt --no-emit-local -o $export_file" "compile should export locked third-party dependencies"
192+
assert_file_contains "$TEST_ROOT/pip.log" "-m pip install uv" "compile should install uv with pip"
193+
assert_file_contains "$TEST_ROOT/pip.log" "--no-deps --target $site_packages_dir -r $export_file" "compile should install exported dependencies into the staged site-packages directory"
194+
}
195+
196+
test_compile_adds_src_directory_to_pythonpath_when_present() {
197+
# Arrange
198+
local build_dir="$TEST_ROOT/src-layout/build"
199+
local cache_dir="$TEST_ROOT/src-layout/cache"
200+
local env_dir="$TEST_ROOT/src-layout/env"
201+
local profile_file="$build_dir/.profile.d/python.sh"
202+
203+
mkdir -p "$build_dir/src" "$cache_dir" "$env_dir"
204+
touch "$build_dir/pyproject.toml" "$build_dir/uv.lock"
205+
setup_fake_commands
206+
207+
# Act
208+
run_compile "$build_dir" "$cache_dir" "$env_dir"
209+
210+
# Assert
211+
assert_exit_code "$status" 0 "compile should succeed for src-layout projects"
212+
assert_file_contains "$profile_file" "$build_dir/src" "profile script should add src layout projects to PYTHONPATH"
213+
}
214+
215+
test_compile_fails_when_lockfile_is_missing() {
216+
# Arrange
217+
local build_dir="$TEST_ROOT/missing-lock/build"
218+
local cache_dir="$TEST_ROOT/missing-lock/cache"
219+
local env_dir="$TEST_ROOT/missing-lock/env"
220+
221+
mkdir -p "$build_dir" "$cache_dir" "$env_dir"
222+
touch "$build_dir/pyproject.toml"
223+
setup_fake_commands
224+
225+
# Act
226+
run_compile "$build_dir" "$cache_dir" "$env_dir"
227+
228+
# Assert
229+
assert_exit_code "$status" 1 "compile should fail when uv.lock is missing"
230+
assert_contains "$output" "No supported uv project found. Expected both pyproject.toml and uv.lock." "compile should explain why unsupported projects fail"
231+
}
232+
233+
test_compile_succeeds_for_locked_uv_project
234+
test_compile_adds_src_directory_to_pythonpath_when_present
235+
test_compile_fails_when_lockfile_is_missing
236+
237+
echo "PASS: compile unit tests"

0 commit comments

Comments
 (0)