bug:issue-4-v127-r2 ref:v1.2.7 scope:deploy-repro fake_root:1 #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |