From f10e343611df31bbb91b8387f26e5d1b23a97ace Mon Sep 17 00:00:00 2001 From: Avi Fenesh Date: Wed, 12 Aug 2026 00:23:07 +0300 Subject: [PATCH] fix: allow degraded compositor installs --- install.sh | 66 +++++++++++++++++++++++------ scripts/install_sh_test.sh | 87 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 14 deletions(-) diff --git a/install.sh b/install.sh index ce22121..1f41192 100755 --- a/install.sh +++ b/install.sh @@ -591,6 +591,15 @@ install_gnome_extension() { # Step 8: doctor readiness check # ----------------------------------------------------------------------------- +doctor_install_prerequisites_ready_raw() { + local out="$1" field + for field in can_register_mcp_tools can_build_accessibility_tree can_send_development_input; do + if ! printf '%s' "${out}" | grep -qE "\"${field}\"[[:space:]]*:[[:space:]]*true([[:space:],}]|$)"; then + return 1 + fi + done +} + run_doctor() { log_section "Step 8/9 — doctor readiness" if [[ ${SKIP_DOCTOR} -eq 1 ]]; then log_skip "--skip-doctor"; return 0; fi @@ -605,30 +614,59 @@ run_doctor() { fi if command -v jq >/dev/null 2>&1 && printf '%s' "${out}" | jq -e . >/dev/null 2>&1; then - local blockers + local blockers readiness_status blockers="$(printf '%s' "${out}" | jq -r '.readiness.blockers | if type == "array" then length else -1 end')" + readiness_status="$(printf '%s' "${out}" | jq -r ' + .readiness as $r | + if ($r | type) != "object" + or ($r.blockers | type) != "array" + or ($r.can_register_mcp_tools | type) != "boolean" + or ($r.can_build_accessibility_tree | type) != "boolean" + or ($r.can_send_development_input | type) != "boolean" + then "invalid" + elif $r.can_register_mcp_tools != true + or $r.can_build_accessibility_tree != true + or $r.can_send_development_input != true + then "blocked" + elif ($r.blockers | length) == 0 then "ready" + else "degraded" + end + ')" printf '%s\n' "${out}" | jq -r ' .readiness as $r | - "ready: \($r.blockers | type == "array" and length == 0)\n" + + "fully ready: \($r.blockers | type == "array" and length == 0)\n" + ((($r.blockers // []) | map(" - \(.)") | join("\n"))) ' - if [[ "${blockers}" -eq 0 ]]; then - log_ok "doctor reports ready" - elif [[ "${blockers}" -eq -1 ]]; then - log_fail "doctor output missing blockers field — unexpected JSON structure" - return 1 - else - log_fail "doctor reports NOT ready" - while IFS= read -r line; do - FAILED_CHECKS+=("${line}") - done < <(printf '%s' "${out}" | jq -r '.readiness.blockers[]?') - return 1 - fi + case "${readiness_status}" in + ready) + log_ok "doctor reports ready" + ;; + degraded) + local capability_verb="capabilities are" + if [[ "${blockers}" -eq 1 ]]; then capability_verb="capability is"; fi + log_ok "installation prerequisites are ready" + log_warn "installation succeeded; ${blockers} platform ${capability_verb} unavailable on this desktop/compositor" + ;; + blocked) + log_fail "doctor reports NOT ready" + while IFS= read -r line; do + FAILED_CHECKS+=("${line}") + done < <(printf '%s' "${out}" | jq -r '.readiness.blockers[]?') + return 1 + ;; + *) + log_fail "doctor output is missing required readiness fields — unexpected JSON structure" + return 1 + ;; + esac else # Raw fallback. printf '%s\n' "${out}" if printf '%s' "${out}" | grep -qiE '"blockers"[[:space:]]*:[[:space:]]*\[\]'; then log_ok "doctor reports ready (raw)" + elif doctor_install_prerequisites_ready_raw "${out}"; then + log_ok "installation prerequisites are ready (raw)" + log_warn "installation succeeded; platform capabilities are unavailable on this desktop/compositor" else log_fail "doctor did not report ready (install jq for a structured summary)" return 1 diff --git a/scripts/install_sh_test.sh b/scripts/install_sh_test.sh index 5b72ae0..ed91a63 100755 --- a/scripts/install_sh_test.sh +++ b/scripts/install_sh_test.sh @@ -30,6 +30,33 @@ assert_not_contains() { fi } +create_fake_doctor() { + local path="$1" + printf '%s\n' \ + '#!/usr/bin/env bash' \ + 'printf '\''%s\n'\'' "${COMPUTER_USE_LINUX_TEST_DOCTOR_OUTPUT}"' \ + >"${path}" + chmod +x "${path}" +} + +run_fake_doctor() { + local doctor_output="$1" hide_jq="${2:-0}" fake_dir status + fake_dir="$(mktemp -d)" + INSTALL_PATH="${fake_dir}/computer-use-linux" + create_fake_doctor "${INSTALL_PATH}" + export COMPUTER_USE_LINUX_TEST_DOCTOR_OUTPUT="${doctor_output}" + if [[ "${hide_jq}" -eq 1 ]]; then + command() { + if [[ "$1" == "-v" && "$2" == "jq" ]]; then return 1; fi + builtin command "$@" + } + fi + status=0 + run_doctor || status=$? + rm -rf -- "${fake_dir}" + return "${status}" +} + test_artix_selects_pacman() ( export COMPUTER_USE_LINUX_OS_RELEASE_FILE="${FIXTURE_DIR}/os-release.artix" export XDG_SESSION_TYPE=x11 @@ -171,6 +198,62 @@ test_non_systemd_host_requires_uinput_access() ( assert_not_contains "${output}" "configure your per-user supervisor to run" || return 1 ) +test_doctor_accepts_platform_capability_blockers() ( + # shellcheck source=../install.sh + source "${INSTALLER}" + local status output doctor_output + doctor_output='{"readiness":{"can_register_mcp_tools":true,"can_build_accessibility_tree":true,"can_query_windows":false,"can_focus_apps":false,"can_focus_windows":false,"can_send_development_input":true,"recommended_next_step":"Use global input without targeted window focus.","blockers":["Window introspection is unavailable; targeted window focus and verification will be disabled."]}}' + + status=0 + output="$(run_fake_doctor "${doctor_output}")" || status=$? + + assert_eq "${status}" "0" || return 1 + assert_contains "${output}" "installation succeeded; 1 platform capability is unavailable" || return 1 + assert_contains "${output}" "Window introspection is unavailable" || return 1 + assert_not_contains "${output}" "doctor reports NOT ready" || return 1 +) + +test_doctor_rejects_missing_install_prerequisite() ( + # shellcheck source=../install.sh + source "${INSTALLER}" + local status output doctor_output + doctor_output='{"readiness":{"can_register_mcp_tools":true,"can_build_accessibility_tree":true,"can_query_windows":false,"can_focus_apps":false,"can_focus_windows":false,"can_send_development_input":false,"recommended_next_step":"Start a keyboard-capable input backend.","blockers":["Window introspection is unavailable.","Development keyboard input is unavailable."]}}' + + status=0 + output="$(run_fake_doctor "${doctor_output}")" || status=$? + + assert_eq "${status}" "1" || return 1 + assert_contains "${output}" "doctor reports NOT ready" || return 1 + assert_contains "${output}" "Development keyboard input is unavailable" || return 1 +) + +test_doctor_raw_fallback_accepts_platform_capability_blockers() ( + # shellcheck source=../install.sh + source "${INSTALLER}" + local status output doctor_output + doctor_output='{"readiness":{"can_register_mcp_tools":true,"can_build_accessibility_tree":true,"can_query_windows":false,"can_focus_apps":false,"can_focus_windows":false,"can_send_development_input":true,"recommended_next_step":"Use global input without targeted window focus.","blockers":["Window introspection is unavailable."]}}' + + status=0 + output="$(run_fake_doctor "${doctor_output}" 1)" || status=$? + + assert_eq "${status}" "0" || return 1 + assert_contains "${output}" "installation succeeded; platform capabilities are unavailable" || return 1 + assert_not_contains "${output}" "doctor did not report ready" || return 1 +) + +test_doctor_raw_fallback_rejects_missing_install_prerequisite() ( + # shellcheck source=../install.sh + source "${INSTALLER}" + local status output doctor_output + doctor_output='{"readiness":{"can_register_mcp_tools":true,"can_build_accessibility_tree":true,"can_query_windows":false,"can_focus_apps":false,"can_focus_windows":false,"can_send_development_input":false,"recommended_next_step":"Start a keyboard-capable input backend.","blockers":["Development keyboard input is unavailable."]}}' + + status=0 + output="$(run_fake_doctor "${doctor_output}" 1)" || status=$? + + assert_eq "${status}" "1" || return 1 + assert_contains "${output}" "doctor did not report ready" || return 1 +) + run_test() { local name="$1" test_fn="$2" if "${test_fn}"; then @@ -189,3 +272,7 @@ run_test "startx system dependencies include xdotool" test_startx_system_deps_in run_test "Wayland system dependencies exclude xdotool" test_wayland_system_deps_exclude_xdotool run_test "non-systemd host gets manual ydotoold guidance" test_non_systemd_host_gets_manual_guidance run_test "non-systemd host requires uinput access" test_non_systemd_host_requires_uinput_access +run_test "doctor accepts platform capability blockers" test_doctor_accepts_platform_capability_blockers +run_test "doctor rejects missing install prerequisites" test_doctor_rejects_missing_install_prerequisite +run_test "doctor raw fallback accepts platform capability blockers" test_doctor_raw_fallback_accepts_platform_capability_blockers +run_test "doctor raw fallback rejects missing install prerequisites" test_doctor_raw_fallback_rejects_missing_install_prerequisite