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
107 changes: 107 additions & 0 deletions .github/workflows/acceptance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: Product Acceptance

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

env:
RIVET_PIN: v0.12.0

jobs:
windows:
name: Windows packaged WinUI acceptance
runs-on: windows-latest
timeout-minutes: 35
steps:
- uses: actions/checkout@v4
- uses: Bogdanp/setup-racket@v1.15
with:
version: '9.3'
architecture: x64
distribution: full
variant: CS
- name: Install pinned dependencies and link core
shell: pwsh
run: |
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/rivet.git#$env:RIVET_PIN" crypto
raco pkg install --auto --no-docs --name autarx-core --link racket
- name: Run executable product acceptance
run: raco rivet acceptance --json
- uses: actions/upload-artifact@v4
if: always()
with:
name: autarx-acceptance-windows
path: .rivet/acceptance/
if-no-files-found: warn

macos:
name: macOS packaged SwiftUI acceptance (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 35
strategy:
fail-fast: false
matrix:
include:
- runner: macos-15
arch: arm64
- runner: macos-15-intel
arch: x64
steps:
- uses: actions/checkout@v4
- uses: Bogdanp/setup-racket@v1.15
with:
version: '9.3'
architecture: ${{ matrix.arch }}
distribution: full
variant: CS
- name: Install pinned dependencies and link core
run: |
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/rivet.git#${RIVET_PIN}" crypto
raco pkg install --auto --no-docs --name autarx-core --link racket
- name: Run executable product acceptance
run: raco rivet acceptance --json
- uses: actions/upload-artifact@v4
if: always()
with:
name: autarx-acceptance-macos-${{ matrix.arch }}
path: .rivet/acceptance/
if-no-files-found: warn

linux:
name: Linux packaged GTK4 acceptance
runs-on: ubuntu-24.04
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
- uses: Bogdanp/setup-racket@v1.15
with:
version: '9.3'
architecture: x64
distribution: full
variant: CS
- name: Install native and accessibility dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake libgtk-4-dev libglib2.0-dev libsecret-1-dev \
liblz4-dev zlib1g-dev libncurses-dev weston at-spi2-core python3-pyatspi
- name: Install pinned dependencies and link core
run: |
raco pkg install --auto --no-docs "https://github.com/turinglambdaai/rivet.git#${RIVET_PIN}" crypto
raco pkg install --auto --no-docs --name autarx-core --link racket
- name: Remember full Racket command path
run: echo "FULL_RACKET_BIN=$(dirname "$(command -v raco)")" >> "$GITHUB_ENV"
- name: Set up embeddable Racket CS
uses: turinglambdaai/rivet/.github/actions/setup-embed-racket@v0.12.0
- name: Run executable product acceptance
run: PATH="$FULL_RACKET_BIN:$PATH" dbus-run-session -- raco rivet acceptance --json
- uses: actions/upload-artifact@v4
if: always()
with:
name: autarx-acceptance-linux
path: .rivet/acceptance/
if-no-files-found: warn
8 changes: 7 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ racket racket/autarx/backend.rkt # RVT1 transport (GUI/dev)

# 5) Large-file benchmark (R0 stop-rule reproduction)
racket scripts/gen-bench-ecuc.rkt -o "$TMPDIR/autarx-bench" -n 50000

# 6) Current-platform product acceptance (contract → RVT1 → native → package → UI)
raco rivet acceptance --json
```

## Contracts (read before changing behaviour; change both sides together)
Expand Down Expand Up @@ -80,5 +83,8 @@ autarx/
- Unknown AUTOSAR element types degrade to `unknown` — never a parse
failure. Ambiguity is a first-class outcome, never silently resolved.
- Rivet framework gaps go upstream (issue/PR to turinglambdaai/rivet);
this repo pins a commit.
this repo pins the reviewed `v0.12.0` release across CI, acceptance, and release.
- Agent 不得以单元测试、编译或截图单独宣称产品完成;涉及 GUI 的改动必须让
`raco rivet acceptance --json` 在声明支持的平台通过,并保留原生控件交互与
后端结果证据。
- `raco test racket/` must stay green on every change.
19 changes: 19 additions & 0 deletions linux/GeneratedBackend.hpp

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions linux/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1721,11 +1721,24 @@ ListPage make_list_page(GtkStack* stack, char const* list_css_class) {

GtkWidget* make_nav_row(char const* title, Section section) {
auto* row = gtk_list_box_row_new();
gtk_accessible_update_property(GTK_ACCESSIBLE(row),
GTK_ACCESSIBLE_PROPERTY_LABEL, title, -1);
auto* label = gtk_label_new(title);
gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
gtk_list_box_row_set_child(GTK_LIST_BOX_ROW(row), label);
g_object_set_data(G_OBJECT(row), "section",
GINT_TO_POINTER(static_cast<gint>(section)));
// Expose the same navigation behavior through GTK's activate action so
// screen readers and AT-SPI automation can operate the sidebar, not only
// pointer-driven ListBox selection.
g_signal_connect(
row, "activate",
G_CALLBACK(+[](GtkListBoxRow* activated, gpointer) {
auto const selected = static_cast<Section>(GPOINTER_TO_INT(
g_object_get_data(G_OBJECT(activated), "section")));
select_section(selected);
}),
nullptr);
return row;
}

Expand Down
3 changes: 3 additions & 0 deletions macos-host/Sources/RivetHost/MainWindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ struct MainWindowView: View {
Label(section.rawValue, systemImage: icon(for: section))
}
.buttonStyle(.plain)
.accessibilityIdentifier("Nav\(section.rawValue)")
}
.navigationTitle("Autarx")
.frame(minWidth: 190)
Expand Down Expand Up @@ -61,6 +62,7 @@ struct WorkspaceSummaryView: View {
Text(row.1).textSelection(.enabled)
}
}
.accessibilityIdentifier("WorkspaceSummary")
} else {
ContentUnavailableView(
"No workspace",
Expand Down Expand Up @@ -116,6 +118,7 @@ struct ObjectListView: View {
object.absolute_path == selectedPath
? Color.accentColor.opacity(0.15) : Color.clear)
}
.accessibilityIdentifier("ObjectList")
}
.inspector(isPresented: .constant(selectedObject != nil)) {
RefInspector(object: selectedObject, refs: detail)
Expand Down
2 changes: 1 addition & 1 deletion racket/autarx/update.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@

(define (strip-trailing-slash s)
(if (and (> (string-length s) 1)
(char=? (string-ref s (- (string-length s) 1)) #\/))
(memq (string-ref s (- (string-length s) 1)) '(#\/ #\\)))
(substring s 0 (- (string-length s) 1))
s))

Expand Down
2 changes: 1 addition & 1 deletion racket/autarx/vendor.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
(let-values ([(base _dir _) (split-path (string->path full-project-abs))])
(define dir (if (path? base) (path->string base) "."))
(if (and (> (string-length dir) 1)
(char=? (string-ref dir (- (string-length dir) 1)) #\/))
(memq (string-ref dir (- (string-length dir) 1)) '(#\/ #\\)))
(substring dir 0 (- (string-length dir) 1))
dir)))
(vendor-invocation
Expand Down
10 changes: 6 additions & 4 deletions racket/tests/app-update-test.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,21 @@
;; per-directory read so a packaged regression trips here, not in rivet's
;; launch smoke.
(test-case "app-info candidates cover every packaged layout"
(define exe-dir (string->path "/tmp/Autarx.app/Contents/MacOS/"))
(define base (make-temporary-file "autarx-layout-~a" 'directory))
(define exe-dir (build-path base "Autarx.app" "Contents" "MacOS"))
(define dirs (a:app-info-candidate-dirs exe-dir))
(check-equal? (length dirs) 4)
;; macOS bundle: Contents/MacOS → Contents/Resources/app
(check-not-false
(member (simplify-path (string->path "/tmp/Autarx.app/Contents/Resources/app"))
(member (simplify-path (build-path base "Autarx.app" "Contents" "Resources" "app"))
dirs)
"macOS bundle layout missing")
;; stage root: exe beside app/
(check-not-false
(member (build-path (string->path "/tmp/Autarx.app/Contents/MacOS/") "app")
(member (build-path exe-dir "app")
dirs)
"stage-root layout missing"))
"stage-root layout missing")
(delete-directory/files base))

(test-case "app-info version reads the packaged bundle layout"
(define base (make-temporary-file "autarx-bundle-~a" 'directory))
Expand Down
63 changes: 63 additions & 0 deletions rivet-acceptance.rktd
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#hasheq(
(version . 1)
(requirements
. (#hasheq((kind . contract) (platforms . (windows macos linux)))
#hasheq((kind . bridge) (platforms . (windows macos linux)))
#hasheq((kind . native-build) (platforms . (windows macos linux)))
#hasheq((kind . package) (platforms . (windows macos linux)))
#hasheq((kind . native-ui) (platforms . (windows macos linux)))))
(suites
. (#hasheq((id . core-contract)
(kind . contract)
(platforms . (windows macos linux))
(command . ("raco" "test" "racket/"))
(timeout-seconds . 900))
#hasheq((id . embedded-wire)
(kind . bridge)
(platforms . (windows macos linux))
(command . ("raco" "test" "racket/tests/rvt1-smoke.rkt"))
(timeout-seconds . 300))
#hasheq((id . native-host)
(kind . native-build)
(platforms . (windows macos linux))
(command . ("raco" "rivet" "build"))
(timeout-seconds . 1800))
#hasheq((id . windows-package)
(kind . package)
(platforms . (windows))
(command . ("racket" "scripts/acceptance/package.rkt"))
(timeout-seconds . 1800)
(evidence . (".rivet/acceptance/windows/package.json")))
#hasheq((id . macos-package)
(kind . package)
(platforms . (macos))
(command . ("racket" "scripts/acceptance/package.rkt"))
(timeout-seconds . 1800)
(evidence . (".rivet/acceptance/macos/package.json")))
#hasheq((id . linux-package)
(kind . package)
(platforms . (linux))
(command . ("racket" "scripts/acceptance/package.rkt"))
(timeout-seconds . 1800)
(evidence . (".rivet/acceptance/linux/package.json")))
#hasheq((id . windows-native-ui)
(kind . native-ui)
(platforms . (windows))
(command . ("pwsh" "-NoProfile" "-File" "scripts/e2e/e2e-windows.ps1"))
(timeout-seconds . 600)
(evidence . (".rivet/acceptance/windows/interaction-trace.json"
".rivet/acceptance/windows/accessibility.json")))
#hasheq((id . macos-native-ui)
(kind . native-ui)
(platforms . (macos))
(command . ("bash" "scripts/e2e/e2e-macos.sh"))
(timeout-seconds . 600)
(evidence . (".rivet/acceptance/macos/interaction-trace.json"
".rivet/acceptance/macos/accessibility.json")))
#hasheq((id . linux-native-ui)
(kind . native-ui)
(platforms . (linux))
(command . ("bash" "scripts/e2e/e2e-linux.sh"))
(timeout-seconds . 600)
(evidence . (".rivet/acceptance/linux/interaction-trace.json"
".rivet/acceptance/linux/accessibility.json"))))))
26 changes: 26 additions & 0 deletions scripts/acceptance/package.rkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#lang racket/base

(require json racket/file racket/path racket/system)

(define platform
(case (system-type 'os)
[(windows) "windows"]
[(macosx) "macos"]
[(unix) "linux"]
[else (error 'autarx-acceptance "unsupported platform")]))
(define raco
(or (find-executable-path "raco")
(error 'autarx-acceptance "raco was not found")))
(unless (system* raco "rivet" "package" "--skip-launch-smoke")
(error 'autarx-acceptance "raco rivet package failed"))
(define output-directory
(build-path (current-directory) ".rivet" "acceptance" platform))
(make-directory* output-directory)
(call-with-output-file (build-path output-directory "package.json")
#:exists 'truncate/replace
(lambda (output)
(write-json (hash 'platform platform
'package-verified #t
'source "raco rivet package --skip-launch-smoke")
output)
(newline output)))
31 changes: 31 additions & 0 deletions scripts/e2e/e2e-linux.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -euo pipefail

repo="$(cd "$(dirname "$0")/../.." && pwd)"
output="$repo/.rivet/acceptance/linux"
executable="$(find "$repo/dist" -type f -name RivetHost -perm -u+x -print -quit)"
fixture="$repo/shared/fixtures/OemDelivery"
mkdir -p "$output"
[[ -n "$executable" && -x "$executable" ]]
export HOME="$output/home-$$"
export AUTARX_OPEN="$fixture"
export XDG_RUNTIME_DIR="${RUNNER_TEMP:-${TMPDIR:-/tmp}}/autarx-acceptance-runtime-$$"
export WAYLAND_DISPLAY=wayland-autarx-acceptance
export GDK_BACKEND=wayland GTK_A11Y=atspi NO_AT_BRIDGE=0 LANG=C.UTF-8
unset DISPLAY
mkdir -p "$HOME" "$XDG_RUNTIME_DIR"; chmod 700 "$XDG_RUNTIME_DIR"
weston --backend=headless-backend.so --socket="$WAYLAND_DISPLAY" --idle-time=0 --debug --log="$output/weston.log" &
weston_pid=$!; app_pid=""; atspi_pid=""
stop_bounded() { local pid="$1"; kill -TERM "$pid" 2>/dev/null || true; for _ in $(seq 1 200); do kill -0 "$pid" 2>/dev/null || return 0; sleep 0.05; done; kill -KILL "$pid" 2>/dev/null || true; return 1; }
cleanup() { local status=$?; trap - EXIT; [[ -n "$app_pid" ]] && kill -0 "$app_pid" 2>/dev/null && stop_bounded "$app_pid" || true; [[ -n "$atspi_pid" ]] && kill -0 "$atspi_pid" 2>/dev/null && kill -TERM "$atspi_pid" || true; kill -TERM "$weston_pid" 2>/dev/null || true; wait "$app_pid" "$atspi_pid" "$weston_pid" 2>/dev/null || true; exit "$status"; }
trap cleanup EXIT
for _ in $(seq 1 100); do [[ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]] && break; kill -0 "$weston_pid" 2>/dev/null; sleep 0.05; done
[[ -S "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" ]]
launcher="$(command -v at-spi-bus-launcher || true)"; [[ -n "$launcher" ]] || launcher=/usr/libexec/at-spi-bus-launcher
"$launcher" --launch-immediately >"$output/at-spi-bus.log" 2>&1 & atspi_pid=$!
for _ in $(seq 1 100); do gdbus call --session --dest org.a11y.Bus --object-path /org/a11y/bus --method org.a11y.Bus.GetAddress >/dev/null 2>&1 && break; kill -0 "$atspi_pid" 2>/dev/null; sleep 0.05; done
export AT_SPI_BUS_ADDRESS="$(gdbus call --session --dest org.a11y.Bus --object-path /org/a11y/bus --method org.a11y.Bus.GetAddress | sed -E "s/^\('([^']+)',\)$/\1/")"
cd "$repo"; "$executable" >"$output/application.log" 2>&1 & app_pid=$!
/usr/bin/python3 "$repo/scripts/e2e/linux-atspi.py" --output "$output"
stop_bounded "$app_pid" || true; wait "$app_pid" 2>/dev/null || true; app_pid=""; trap - EXIT
kill -TERM "$atspi_pid" "$weston_pid" 2>/dev/null || true; wait "$atspi_pid" "$weston_pid" 2>/dev/null || true
22 changes: 22 additions & 0 deletions scripts/e2e/e2e-macos.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail

repo="$(cd "$(dirname "$0")/../.." && pwd)"
output="$repo/.rivet/acceptance/macos"
app="$repo/dist/autarx.app/Contents/MacOS/autarx"
driver="$output/macos-ax"
home="$output/home-$$"
fixture="$repo/shared/fixtures/OemDelivery"
mkdir -p "$output" "$home"
[[ -x "$app" ]]
swiftc "$repo/scripts/e2e/macos-ax.swift" -o "$driver" -framework AppKit -framework ApplicationServices
HOME="$home" AUTARX_OPEN="$fixture" "$app" >"$output/application.log" 2>&1 &
app_pid=$!
stop_bounded() { local pid="$1"; kill -TERM "$pid" 2>/dev/null || true; for _ in $(seq 1 200); do kill -0 "$pid" 2>/dev/null || return 0; sleep 0.05; done; kill -KILL "$pid" 2>/dev/null || true; return 1; }
cleanup() { local status=$?; trap - EXIT; if kill -0 "$app_pid" 2>/dev/null; then stop_bounded "$app_pid" || true; wait "$app_pid" 2>/dev/null || true; fi; exit "$status"; }
trap cleanup EXIT
"$driver" --pid "$app_pid" --output "$output"
for _ in $(seq 1 200); do kill -0 "$app_pid" 2>/dev/null || break; sleep 0.05; done
if kill -0 "$app_pid" 2>/dev/null; then echo "Autarx did not exit after Quit" >&2; exit 1; fi
wait "$app_pid"
trap - EXIT
Loading
Loading