Skip to content

bug:issue-4-v127-r2 ref:v1.2.7 scope:deploy-repro fake_root:1 #7

bug:issue-4-v127-r2 ref:v1.2.7 scope:deploy-repro fake_root:1

bug:issue-4-v127-r2 ref:v1.2.7 scope:deploy-repro fake_root:1 #7

Workflow file for this run

name: Bug Retest (Android-Constrained Simulation)
on:
workflow_dispatch:
inputs:
bug_id:
description: "Bug ID or short label (used in run name/log only)"
required: true
type: string
target_ref:
description: "Git ref to test (branch/tag/SHA)"
required: true
default: main
type: string
scope:
description: "Retest scope"
required: true
default: permissions
type: choice
options:
- permissions
- integration
- deploy-repro
- all
pytest_k:
description: "Optional pytest -k expression (for targeted bug retest)"
required: false
default: ""
type: string
fake_root:
description: "Simulate Android fake-root mode"
required: true
default: "1"
type: choice
options:
- "1"
- "0"
deploy_expect:
description: "Only for deploy-repro: expected outcome"
required: true
default: reproduced
type: choice
options:
- reproduced
- fixed
run-name: >-
bug:${{ inputs.bug_id }}
ref:${{ inputs.target_ref }}
scope:${{ inputs.scope }}
fake_root:${{ inputs.fake_root }}
permissions:
contents: read
concurrency:
group: bug-retest-${{ github.workflow }}-${{ inputs.bug_id }}
cancel-in-progress: true
jobs:
retest:
runs-on: ubuntu-latest
timeout-minutes: 30
env:
PYTHONDONTWRITEBYTECODE: "1"
ANDROID_DATA: /data
TERMUX_VERSION: ci-simulated
PREFIX: /data/data/com.termux/files/usr
ANDROID_DOCKER_FAKE_ROOT: ${{ inputs.fake_root }}
ANDROID_DOCKER_LINK2SYMLINK: "0"
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PYTEST_ADDOPTS: "-ra"
REPRO_TIMEOUT_SECONDS: "240"
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ inputs.target_ref }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip
cache-dependency-path: requirements.txt
- name: Enforce non-root execution
shell: bash
run: |
set -euo pipefail
echo "uid=$(id -u) user=$(id -un)"
if [ "$(id -u)" -eq 0 ]; then
echo "::error::CI retest must run as non-root to mirror Android constraints."
exit 1
fi
- name: Install dependencies (non-root)
shell: bash
run: |
set -euo pipefail
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install PyYAML
- name: Ensure local artifact directory
shell: bash
run: mkdir -p artifacts
- name: Bootstrap proot without sudo (integration/deploy only)
if: ${{ inputs.scope == 'integration' || inputs.scope == 'all' || inputs.scope == 'deploy-repro' }}
shell: bash
run: |
set -euo pipefail
if command -v proot >/dev/null 2>&1; then
proot --version || true
exit 0
fi
echo "proot not found; trying user-space bootstrap via apt package download..."
workdir="${RUNNER_TEMP}/proot_pkg"
mkdir -p "${workdir}" "$HOME/.local/bin"
cd "${workdir}"
if command -v apt-get >/dev/null 2>&1; then
apt-get download proot || true
fi
deb_file="$(ls -1 proot_*_amd64.deb 2>/dev/null | head -n1 || true)"
if [ -n "${deb_file}" ] && command -v dpkg-deb >/dev/null 2>&1; then
dpkg-deb -x "${deb_file}" extracted
if [ -x extracted/usr/bin/proot ]; then
cp extracted/usr/bin/proot "$HOME/.local/bin/proot"
chmod +x "$HOME/.local/bin/proot"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
fi
fi
if command -v proot >/dev/null 2>&1; then
proot --version || true
exit 0
fi
echo "::error::proot is required for integration scope but is unavailable without root-level package install."
echo "::error::Use permissions scope, or provide an environment where proot is preinstalled."
exit 2
- name: Run permissions retest
if: ${{ inputs.scope == 'permissions' || inputs.scope == 'all' }}
shell: bash
run: |
set -euo pipefail
k_expr="${{ inputs.pytest_k }}"
if [ -n "${k_expr}" ]; then
python -m pytest tests/test_android_permissions.py -v -k "${k_expr}" \
--junitxml=artifacts/permissions.xml
else
python -m pytest tests/test_android_permissions.py -v \
--junitxml=artifacts/permissions.xml
fi
- name: Run integration retest
if: ${{ inputs.scope == 'integration' || inputs.scope == 'all' }}
shell: bash
run: |
set -euo pipefail
k_expr="${{ inputs.pytest_k }}"
if [ -n "${k_expr}" ]; then
python -m pytest tests/test_android_integration.py -v -s -k "${k_expr}" \
--junitxml=artifacts/integration.xml
else
python -m pytest tests/test_android_integration.py -v -s \
--junitxml=artifacts/integration.xml
fi
- name: Run deploy-path issue retest
if: ${{ inputs.scope == 'deploy-repro' }}
timeout-minutes: 8
shell: bash
run: |
set -euo pipefail
set +e
if [ -x scripts/repro_issue4_deploy.sh ]; then
if command -v timeout >/dev/null 2>&1; then
timeout --kill-after=30s 7m bash scripts/repro_issue4_deploy.sh > artifacts/deploy-repro.stdout.log 2>&1
else
bash scripts/repro_issue4_deploy.sh > artifacts/deploy-repro.stdout.log 2>&1
fi
code=$?
else
IMAGE="registry.cn-hangzhou.aliyuncs.com/hass-panel/hass-panel:latest"
WORK_DIR="$PWD/.repro-issue4"
CACHE_DIR="$PWD/.cache/issue4"
COMPOSE_FILE="$WORK_DIR/docker-compose.yml"
LOG_FILE="$WORK_DIR/repro.log"
mkdir -p "$WORK_DIR/data" "$CACHE_DIR"
export IMAGE COMPOSE_FILE
python - <<'PY'
import os
import textwrap
from pathlib import Path
compose = textwrap.dedent(
f"""\
version: '3'
services:
hass-panel:
container_name: hass-panel
image: {os.environ['IMAGE']}
restart: unless-stopped
network_mode: host
volumes:
- ./data:/config/hass-panel
"""
)
Path(os.environ["COMPOSE_FILE"]).write_text(compose, encoding="utf-8")
PY
export ANDROID_DATA=/data
export TERMUX_VERSION=ci-simulated
export PREFIX=/data/data/com.termux/files/usr
export ANDROID_DOCKER_FAKE_ROOT=${{ inputs.fake_root }}
export ANDROID_DOCKER_LINK2SYMLINK=0
if command -v timeout >/dev/null 2>&1; then
timeout --kill-after=20s "${REPRO_TIMEOUT_SECONDS}s" \
python -m android_docker.docker_compose_cli --cache-dir "$CACHE_DIR" -f "$COMPOSE_FILE" up >"$LOG_FILE" 2>&1
else
python -m android_docker.docker_compose_cli --cache-dir "$CACHE_DIR" -f "$COMPOSE_FILE" up >"$LOG_FILE" 2>&1
fi
cmd_code=$?
tail -n 120 "$LOG_FILE" > artifacts/deploy-repro.stdout.log || true
if grep -q "proot error: '/bin/bash' is not executable" "$LOG_FILE"; then
code=0
else
code=2
fi
echo "fallback compose exit code: ${cmd_code}" >> artifacts/deploy-repro.stdout.log
fi
set -e
expected="${{ inputs.deploy_expect }}"
echo "deploy script exit code: ${code}, expected: ${expected}"
tail -n 120 artifacts/deploy-repro.stdout.log || true
if [ "${expected}" = "reproduced" ] && [ "${code}" -eq 0 ]; then
echo "Result matches expectation: issue reproduced."
exit 0
fi
if [ "${expected}" = "fixed" ] && [ "${code}" -eq 2 ]; then
echo "Result matches expectation: issue not reproduced (fixed)."
exit 0
fi
echo "::error::Deploy repro result does not match expected outcome."
exit 1
- name: Collect deploy repro logs
if: ${{ always() && inputs.scope == 'deploy-repro' }}
shell: bash
run: |
set -euo pipefail
if [ -f .repro-issue4/repro.log ]; then
cp .repro-issue4/repro.log artifacts/deploy-repro.log
fi
- name: Upload retest artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: bug-retest-${{ inputs.bug_id }}-${{ github.run_number }}
path: artifacts/
if-no-files-found: warn
retention-days: 3