Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,22 @@ jobs:

- name: Configure subordinate ID mappings
run: |
username="$(id -un)"
for path in /etc/subuid /etc/subgid; do
sudo touch "$path"
if ! grep -q "^${username}:" "$path"; then
echo "${username}:100000:65536" | sudo tee -a "$path"
fi
for username in "$(id -un)" root; do
for path in /etc/subuid /etc/subgid; do
sudo touch "$path"
if ! grep -q "^${username}:" "$path"; then
echo "${username}:100000:65536" | sudo tee -a "$path"
fi
done
done

- name: Allow unprivileged user namespaces
run: |
sudo sysctl -w kernel.unprivileged_userns_clone=1
if [ -e /proc/sys/kernel/apparmor_restrict_unprivileged_userns ]; then
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
fi

- name: Run Go tests
run: |
go list ./... \
Expand All @@ -59,8 +67,39 @@ jobs:
- name: Install opencode
run: npm install -g "opencode-ai@${OPENCODE_VERSION}"

- name: Resolve native opencode binary
run: |
global_node_root="$(npm root -g)"
for candidate in \
"$global_node_root/opencode-linux-x64/bin/opencode" \
"$global_node_root/opencode-linux-x64-baseline/bin/opencode" \
"$global_node_root/opencode-linux-x64-musl/bin/opencode" \
"$global_node_root/opencode-linux-x64-baseline-musl/bin/opencode" \
"$global_node_root/opencode-ai/node_modules/opencode-linux-x64/bin/opencode" \
"$global_node_root/opencode-ai/node_modules/opencode-linux-x64-baseline/bin/opencode" \
"$global_node_root/opencode-ai/node_modules/opencode-linux-x64-musl/bin/opencode" \
"$global_node_root/opencode-ai/node_modules/opencode-linux-x64-baseline-musl/bin/opencode"; do
if [ -x "$candidate" ]; then
resolved_candidate="$(readlink -f "$candidate")"
sudo install -m 0755 "$resolved_candidate" /usr/local/bin/opencode-bbox
echo "OPENCODE_BIN=/usr/local/bin/opencode-bbox" >> "$GITHUB_ENV"
exit 0
fi
done
echo "failed to resolve native opencode binary under $global_node_root" >&2
exit 1

- name: Prepare opencode smoke PATH
run: |
smoke_path_dir="$GITHUB_WORKSPACE/.opencode-smoke-path"
rm -rf "$smoke_path_dir"
mkdir -p "$smoke_path_dir"
ln -sf /usr/bin/env "$smoke_path_dir/env"
ln -sf /usr/bin/sh "$smoke_path_dir/sh"
echo "OPENCODE_SMOKE_PATH_VALUE=$smoke_path_dir" >> "$GITHUB_ENV"

- name: Run opencode smoke suite
run: ./scripts/opencode-smoke.sh
run: env OPENCODE_BIN="$OPENCODE_BIN" OPENCODE_SMOKE_PATH_VALUE="$OPENCODE_SMOKE_PATH_VALUE" ./scripts/opencode-smoke.sh

- name: Run local release smoke build
run: make release-snapshot
Expand Down
128 changes: 126 additions & 2 deletions opencode_smoke_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,91 @@ func TestOpenCodeSmokeBuilderCaseWritesDockerBuildConfig(t *testing.T) {
func TestOpenCodeSmokeSkipsLoopbackSetupUnsupported(t *testing.T) {
t.Parallel()

output := runOpenCodeSmokeScript(t, "loopback-unsupported")
output := runOpenCodeSmokeScript(t, "mixed-loopback-unsupported")
if !strings.Contains(output, "SKIP loopback setup unsupported: proxy-enforce-copy-env") {
t.Fatalf("expected loopback setup skip in output, got:\n%s", output)
}
if !strings.Contains(output, "PASS expected auth failure: transparent-enforce-explicit-env") {
t.Fatalf("expected non-skipped cases to continue after loopback skip, got:\n%s", output)
}
}

func TestOpenCodeSmokeFailsWhenAllCasesSkip(t *testing.T) {
t.Parallel()

output, err := runOpenCodeSmokeScriptExpectError(t, "loopback-unsupported")
if err == nil {
t.Fatalf("expected all-skipped smoke run to fail, output:\n%s", output)
}
if !strings.Contains(output, "FAIL all selected opencode smoke cases skipped") {
t.Fatalf("expected all-skipped failure in output, got:\n%s", output)
}
}

func TestOpenCodeSmokeUsesSandboxPathOverride(t *testing.T) {
t.Parallel()

sandboxPathDir := filepath.Join(t.TempDir(), "sandbox-path")
if err := os.MkdirAll(sandboxPathDir, 0o755); err != nil {
t.Fatalf("mkdir sandbox path dir: %v", err)
}
for _, tool := range []string{"awk", "grep"} {
target, err := exec.LookPath(tool)
if err != nil {
t.Fatalf("resolve %s: %v", tool, err)
}
if err := os.Symlink(target, filepath.Join(sandboxPathDir, tool)); err != nil {
t.Fatalf("symlink %s: %v", tool, err)
}
}

output, err := runOpenCodeSmokeScriptWithEnv(t, "auth-failure", map[string]string{
"OPENCODE_SMOKE_PATH_VALUE": sandboxPathDir,
"EXPECT_CONFIG_PATH": sandboxPathDir,
})
if err != nil {
t.Fatalf("run opencode smoke script with sandbox path override: %v\n%s", err, output)
}
if !strings.Contains(output, "PASS expected auth failure: transparent-enforce-explicit-env") {
t.Fatalf("expected explicit PATH case to succeed, got:\n%s", output)
}
}

func TestOpenCodeSmokeCopyEnvCasesUseRepoOwnedSandboxPath(t *testing.T) {
t.Parallel()

sandboxPathDir := filepath.Join(t.TempDir(), "sandbox-path")
if err := os.MkdirAll(sandboxPathDir, 0o755); err != nil {
t.Fatalf("mkdir sandbox path dir: %v", err)
}
for _, tool := range []string{"env", "sh"} {
target, err := exec.LookPath(tool)
if err != nil {
t.Fatalf("resolve %s: %v", tool, err)
}
if err := os.Symlink(target, filepath.Join(sandboxPathDir, tool)); err != nil {
t.Fatalf("symlink %s: %v", tool, err)
}
}

output, err := runOpenCodeSmokeScriptWithEnv(t, "auth-failure", map[string]string{
"OPENCODE_SMOKE_PATH_VALUE": sandboxPathDir,
"EXPECT_BBOX_PATH_MODE": "host-not-sandbox",
"EXPECT_COPY_ENV_MARKER": "OPENCODE_SMOKE_MARKER",
"EXPECT_COPY_ENV_CASES_EXPLICIT": "proxy-enforce-copy-env,proxy-audit-copy-env,proxy-enforce-docker-build",
})
if err != nil {
t.Fatalf("run opencode smoke script with copy_env repo-owned sandbox path expectations: %v\n%s", err, output)
}
if !strings.Contains(output, "PASS expected auth failure: proxy-enforce-copy-env") {
t.Fatalf("expected copy_env PATH override case to succeed, got:\n%s", output)
}
}

func runOpenCodeSmokeScript(t *testing.T, fakeBBoxMode string) string {
t.Helper()

output, err := runOpenCodeSmokeScriptExpectError(t, fakeBBoxMode)
output, err := runOpenCodeSmokeScriptWithEnv(t, fakeBBoxMode, nil)
if err != nil {
t.Fatalf("run opencode smoke script: %v\n%s", err, output)
}
Expand All @@ -64,6 +139,11 @@ func runOpenCodeSmokeScript(t *testing.T, fakeBBoxMode string) string {

func runOpenCodeSmokeScriptExpectError(t *testing.T, fakeBBoxMode string) (string, error) {
t.Helper()
return runOpenCodeSmokeScriptWithEnv(t, fakeBBoxMode, nil)
}

func runOpenCodeSmokeScriptWithEnv(t *testing.T, fakeBBoxMode string, extraEnv map[string]string) (string, error) {
t.Helper()

root := moduleRoot(t)
fakeBinDir := filepath.Join(t.TempDir(), "fake-bin")
Expand Down Expand Up @@ -91,6 +171,9 @@ func runOpenCodeSmokeScriptExpectError(t *testing.T, fakeBBoxMode string) (strin
"OPENCODE_SMOKE_SKIP_SUBID_CHECK=1",
"PATH="+fakeBinDir+string(os.PathListSeparator)+os.Getenv("PATH"),
)
for key, value := range extraEnv {
cmd.Env = append(cmd.Env, key+"="+value)
}
output, err := cmd.CombinedOutput()
return string(output), err
}
Expand Down Expand Up @@ -167,21 +250,47 @@ grep -q '"env": \["OPENAI_API_KEY"\]' "$opencode_config" || { echo "missing open
grep -q '"model": "openai/gpt-4.1-mini"' "$opencode_config" || { echo "missing smoke model config" >&2; exit 98; }
! grep -q 'OPENAI_API_KEY=' "$config_path" || { echo "unexpected explicit OPENAI_API_KEY env" >&2; exit 99; }

if [ -n "${EXPECT_BBOX_PATH:-}" ] && [ "${PATH:-}" != "$EXPECT_BBOX_PATH" ]; then
echo "unexpected bbox PATH: ${PATH:-}" >&2
exit 118
fi
if [ "${EXPECT_BBOX_PATH_MODE:-}" = "host-not-sandbox" ] && [ "${PATH:-}" = "${OPENCODE_SMOKE_PATH_VALUE:-}" ]; then
echo "unexpected bbox PATH matches sandbox PATH override: ${PATH:-}" >&2
exit 119
fi

printf 'FAKE_BBOX %s\n' "$case_name"

case "$case_name" in
proxy-enforce-copy-env)
grep -q 'traffic_mode: proxy' "$config_path" || { echo "missing proxy mode" >&2; exit 93; }
grep -q 'policy_mode: enforce' "$config_path" || { echo "missing enforce mode" >&2; exit 100; }
grep -q 'copy_env:' "$config_path" || { echo "missing copy_env block" >&2; exit 101; }
if [ -n "${EXPECT_COPY_ENV_MARKER:-}" ]; then
grep -q " - $EXPECT_COPY_ENV_MARKER" "$config_path" || { echo "missing expected copy_env marker" >&2; exit 120; }
! grep -q ' - PATH$' "$config_path" || { echo "unexpected PATH copy_env entry" >&2; exit 121; }
fi
if [ -n "${EXPECT_COPY_ENV_CASES_EXPLICIT:-}" ]; then
grep -q "PATH=$OPENCODE_SMOKE_PATH_VALUE" "$config_path" || { echo "missing explicit PATH override for copy_env case" >&2; exit 122; }
fi
;;
transparent-enforce-explicit-env)
grep -q 'traffic_mode: transparent' "$config_path" || { echo "missing transparent mode" >&2; exit 102; }
grep -q 'env:' "$config_path" || { echo "missing env block" >&2; exit 103; }
grep -q 'HOME=' "$config_path" || { echo "missing HOME env" >&2; exit 104; }
if [ -n "${EXPECT_CONFIG_PATH:-}" ]; then
grep -q "PATH=$EXPECT_CONFIG_PATH" "$config_path" || { echo "missing expected explicit PATH override" >&2; exit 117; }
fi
;;
proxy-audit-copy-env)
grep -q 'policy_mode: audit' "$config_path" || { echo "missing audit mode" >&2; exit 105; }
if [ -n "${EXPECT_COPY_ENV_MARKER:-}" ]; then
grep -q " - $EXPECT_COPY_ENV_MARKER" "$config_path" || { echo "missing expected copy_env marker" >&2; exit 123; }
! grep -q ' - PATH$' "$config_path" || { echo "unexpected PATH copy_env entry" >&2; exit 124; }
fi
if [ -n "${EXPECT_COPY_ENV_CASES_EXPLICIT:-}" ]; then
grep -q "PATH=$OPENCODE_SMOKE_PATH_VALUE" "$config_path" || { echo "missing explicit PATH override for copy_env case" >&2; exit 125; }
fi
;;
proxy-enforce-docker-build|transparent-audit-docker-build)
grep -q 'docker_build:' "$config_path" || { echo "missing docker_build block" >&2; exit 106; }
Expand All @@ -191,6 +300,13 @@ case "$case_name" in
grep -q 'podman_path:' "$config_path" || { echo "missing podman_path" >&2; exit 110; }
grep -q 'newuidmap_path:' "$config_path" || { echo "missing newuidmap_path" >&2; exit 111; }
grep -q 'newgidmap_path:' "$config_path" || { echo "missing newgidmap_path" >&2; exit 112; }
if [ "$case_name" = "proxy-enforce-docker-build" ] && [ -n "${EXPECT_COPY_ENV_MARKER:-}" ]; then
grep -q " - $EXPECT_COPY_ENV_MARKER" "$config_path" || { echo "missing expected copy_env marker" >&2; exit 126; }
! grep -q ' - PATH$' "$config_path" || { echo "unexpected PATH copy_env entry" >&2; exit 127; }
if [ -n "${EXPECT_COPY_ENV_CASES_EXPLICIT:-}" ]; then
grep -q "PATH=$OPENCODE_SMOKE_PATH_VALUE" "$config_path" || { echo "missing explicit PATH override for docker copy_env case" >&2; exit 128; }
fi
fi
;;
esac

Expand All @@ -217,6 +333,14 @@ case "$mode" in
echo "start sandbox helper: read bbox-bridge-parent: connection reset by peer: bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted" >&2
exit 1
;;
mixed-loopback-unsupported)
if [ "$case_name" = "proxy-enforce-copy-env" ]; then
echo "start sandbox helper: read bbox-bridge-parent: connection reset by peer: bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted" >&2
exit 1
fi
echo "OpenAI API key is missing. Pass it using the 'apiKey' parameter or the OPENAI_API_KEY environment variable." >&2
exit 0
;;
*)
echo "unexpected fake bbox mode: $mode" >&2
exit 110
Expand Down
39 changes: 35 additions & 4 deletions scripts/opencode-smoke.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ PROMPT=${OPENCODE_SMOKE_PROMPT:-Reply with exactly the word OK.}
RUN_TIMEOUT=${OPENCODE_SMOKE_TIMEOUT:-90s}
MODEL_ID=${OPENCODE_SMOKE_MODEL:-openai/gpt-4.1-mini}
SANDBOX_ROOT=${OPENCODE_SMOKE_SANDBOX_ROOT:-/workspace}
SANDBOX_PATH_VALUE=${OPENCODE_SMOKE_PATH_VALUE:-${PATH:-/usr/bin:/bin}}
COPY_ENV_MARKER_KEY=${OPENCODE_SMOKE_COPY_ENV_MARKER_KEY:-OPENCODE_SMOKE_MARKER}

case_filter=${OPENCODE_SMOKE_CASES:-}
total_cases=0
passed_cases=0
skipped_cases=0

resolve_repo_path() {
path="$1"
Expand Down Expand Up @@ -88,6 +93,7 @@ run_case() {
* ) return 0 ;;
esac
fi
total_cases=$((total_cases + 1))

echo "RUN $case_name"

Expand Down Expand Up @@ -134,6 +140,20 @@ exit 0
'
write_executable "$builder_tools_dir/podman" '#!/bin/sh
set -eu
while [ "$#" -ge 1 ]; do
case "$1" in
--*)
shift
;;
unshare)
break
;;
*)
echo "unexpected podman invocation: $*" >&2
exit 64
;;
esac
done
[ "$#" -ge 1 ] && [ "$1" = "unshare" ] || {
echo "unexpected podman invocation: $*" >&2
exit 64
Expand Down Expand Up @@ -163,11 +183,10 @@ newgidmap=$builder_tools_dir/newgidmap
echo " read_only: false"
echo "env:"
echo " - HOME=$sandbox_home"
echo " - PATH=$SANDBOX_PATH_VALUE"
if [ "$env_mode" = "copy" ]; then
echo "copy_env:"
echo " - PATH"
else
echo " - PATH=${PATH:-/usr/bin:/bin}"
echo " - $COPY_ENV_MARKER_KEY"
fi
if [ "$docker_build_enabled" = "true" ]; then
echo "docker_build:"
Expand All @@ -183,7 +202,7 @@ newgidmap=$builder_tools_dir/newgidmap
status=0
(
cd "$repo_root"
"$timeout_bin" "$RUN_TIMEOUT" "$bbox_bin" --config "$bbox_config" -- "$OPENCODE_BIN" run --pure --print-logs --model "$MODEL_ID" "$PROMPT"
"$timeout_bin" "$RUN_TIMEOUT" env "$COPY_ENV_MARKER_KEY=$case_name" "$bbox_bin" --config "$bbox_config" -- "$OPENCODE_BIN" run --pure --print-logs --model "$MODEL_ID" "$PROMPT"
) >"$output_file" 2>&1 || status=$?

output=$(cat "$output_file")
Expand All @@ -192,6 +211,7 @@ newgidmap=$builder_tools_dir/newgidmap
case "$output_lc" in
*"loopback: failed rtm_newaddr: operation not permitted"*)
echo "SKIP loopback setup unsupported: $case_name"
skipped_cases=$((skipped_cases + 1))
rm -rf "$case_dir"
return 0
;;
Expand All @@ -207,6 +227,7 @@ newgidmap=$builder_tools_dir/newgidmap
case "$output_lc" in
*auth*|*credential*|*"api key"*|*login*|*provider*|*missing*)
echo "PASS expected auth failure: $case_name"
passed_cases=$((passed_cases + 1))
;;
*)
if [ "$status" -eq 0 ]; then
Expand All @@ -228,3 +249,13 @@ run_case "transparent-enforce-explicit-env" "transparent" "enforce" "explicit" "
run_case "proxy-audit-copy-env" "proxy" "audit" "copy" "false"
run_case "proxy-enforce-docker-build" "proxy" "enforce" "copy" "true"
run_case "transparent-audit-docker-build" "transparent" "audit" "explicit" "true"

if [ "$total_cases" -eq 0 ]; then
echo "FAIL no opencode smoke cases selected" >&2
exit 1
fi

if [ "$passed_cases" -eq 0 ]; then
echo "FAIL all selected opencode smoke cases skipped" >&2
exit 1
fi
Loading