test: verify Playground scenarios through shared GUI runner
This commit is contained in:
parent
7c6eb45f4f
commit
b8579e57cc
30
Makefile
30
Makefile
@ -1,5 +1,5 @@
|
||||
EMACS ?= emacs
|
||||
LOAD_PATH = -L . -L ../etaf -L ../etaf-ui -L ../ebox -L ../ecss -L ../tp -L ../etaf-sqlite
|
||||
LOAD_PATH = -L . -L scripts -L ../etaf -L ../etaf-ui -L ../ebox -L ../ecss -L ../tp -L ../etaf-sqlite
|
||||
EBOX_NATIVE_RELEASE_DIR = $(shell $(EMACS) -Q --batch $(LOAD_PATH) \
|
||||
--eval '(setq load-prefer-newer t)' -l ../ebox/ebox-native-reflow.el \
|
||||
--eval '(princ (expand-file-name (format "../ebox/native/target/%s/release/" (ebox-native-reflow--rust-target))))')
|
||||
@ -9,7 +9,8 @@ EXAMPLE_ELC := $(EXAMPLES:.el=.elc)
|
||||
TEST_FILES := $(wildcard tests/*-tests.el)
|
||||
|
||||
.PHONY: all compile examples-read test perf perf-prepare perf-evaluator perf-regressions \
|
||||
perf-check check checkdoc load clean
|
||||
perf-check gui-doctor gui-research gui-flex gui-grid gui-all \
|
||||
check checkdoc load clean
|
||||
|
||||
all: check
|
||||
|
||||
@ -23,12 +24,14 @@ compile:
|
||||
$(MAKE) -C ../etaf compile EMACS="$(EMACS)"
|
||||
$(MAKE) -C ../etaf-ui compile EMACS="$(EMACS)"
|
||||
$(MAKE) -C ../etaf-sqlite compile EMACS="$(EMACS)"
|
||||
rm -f *.elc tests/*.elc $(EXAMPLE_ELC)
|
||||
$(EMACS) -Q --batch $(LOAD_PATH) --eval '(setq load-prefer-newer t)' \
|
||||
rm -f *.elc scripts/*.elc tests/*.elc $(EXAMPLE_ELC)
|
||||
$(EMACS) -Q --batch $(LOAD_PATH) -L ../etaf/scripts -L ../ebox-playground \
|
||||
--eval "(setq load-prefer-newer t byte-compile-error-on-warn t byte-compile-warnings '(not obsolete))" \
|
||||
--eval '(load-file "etaf-playground.el")' \
|
||||
--eval '(dolist (file (quote ($(foreach file,$(ENTRY_EXAMPLES),"$(file)")))) (load-file file))' \
|
||||
--eval '(dolist (file (quote ($(foreach file,$(EXAMPLES),"$(file)")))) (byte-compile-file file))' \
|
||||
--eval '(byte-compile-file "etaf-playground.el")' \
|
||||
--eval '(byte-compile-file "scripts/playground-gui-scenarios.el")' \
|
||||
--eval '(dolist (file (quote ($(foreach file,$(TEST_FILES),"$(file)")))) (byte-compile-file file))'
|
||||
|
||||
examples-read: compile
|
||||
@ -76,14 +79,29 @@ perf-check:
|
||||
$(MAKE) perf EMACS="$(EMACS)"
|
||||
$(MAKE) perf-regressions EMACS="$(EMACS)"
|
||||
|
||||
gui-doctor:
|
||||
scripts/run-gui-verification.sh doctor
|
||||
|
||||
gui-research:
|
||||
scripts/run-gui-verification.sh research-shelf
|
||||
|
||||
gui-flex:
|
||||
scripts/run-gui-verification.sh flex-reference
|
||||
|
||||
gui-grid:
|
||||
scripts/run-gui-verification.sh grid-reference
|
||||
|
||||
gui-all:
|
||||
scripts/run-gui-verification.sh all
|
||||
|
||||
load: compile
|
||||
$(EMACS) -Q --batch $(LOAD_PATH) --eval '(require (quote etaf-playground))' \
|
||||
--eval '(princ "etaf-playground load OK\\n")'
|
||||
|
||||
checkdoc:
|
||||
$(EMACS) -Q --batch --eval '(progn (require (quote checkdoc)) (dolist (file (append (list "etaf-playground.el" "scripts/benchmark-research-shelf.el") (directory-files-recursively "tests" "\\.el$$") (directory-files-recursively "examples" "\\.el$$"))) (checkdoc-file file)))'
|
||||
$(EMACS) -Q --batch --eval '(progn (require (quote checkdoc)) (dolist (file (append (list "etaf-playground.el" "scripts/benchmark-research-shelf.el" "scripts/playground-gui-scenarios.el") (directory-files-recursively "tests" "\\.el$$") (directory-files-recursively "examples" "\\.el$$"))) (checkdoc-file file)))'
|
||||
|
||||
check: checkdoc test
|
||||
|
||||
clean:
|
||||
rm -f *.elc tests/*.elc $(EXAMPLE_ELC)
|
||||
rm -f *.elc scripts/*.elc tests/*.elc $(EXAMPLE_ELC)
|
||||
|
||||
41
README.md
41
README.md
@ -60,6 +60,47 @@ The companion registers `:reload-on-refresh t`, so saving the `.el`, `.etaf`, or
|
||||
`.ecss` source and refreshing reloads the complete consumer before the next
|
||||
mount.
|
||||
|
||||
## Repeatable Emacs 31.1 GUI verification
|
||||
|
||||
The repository owns one stable real-GUI runner instead of relying on ad-hoc
|
||||
`emacsclient`, focus, and recording commands:
|
||||
|
||||
```sh
|
||||
make gui-doctor
|
||||
make gui-research
|
||||
make gui-flex
|
||||
make gui-grid
|
||||
# Or capture all three sequentially:
|
||||
make gui-all
|
||||
```
|
||||
|
||||
The reusable engine and process runner live in `../etaf/scripts/`. They know
|
||||
only Scenario, Action, Context, checkpoint, recording, and evidence contracts.
|
||||
`scripts/playground-gui-scenarios.el` is a thin adapter layer: Research Shelf
|
||||
defines its application actions, while Flex and Grid are two inputs to the same
|
||||
Ebox reference scenario factory. Adding another application does not create a
|
||||
second daemon/recording/checkpoint implementation.
|
||||
|
||||
Each scenario creates a unique named daemon, disables native-comp JIT, loads
|
||||
the sibling repositories explicitly, creates one GUI frame, activates Emacs
|
||||
from the controlling shell, records through a PTY-backed macOS screen recorder,
|
||||
runs mount/resize/scroll/interaction checkpoints, writes screenshots and a
|
||||
manifest, then shuts down every process it created. Flex is always extracted
|
||||
from the committed `ebox-playground` HEAD, so a user's dirty fixture is never
|
||||
read, overwritten, staged, or restored.
|
||||
|
||||
A fresh capture intentionally reports `INCOMPLETE` until a human or agent has
|
||||
inspected `report.md`, `contact-sheet.png`, and its selected endpoint images.
|
||||
After that temporal review, finalize the exact run directory:
|
||||
|
||||
```sh
|
||||
scripts/run-gui-verification.sh review /private/tmp/etaf-playground-gui.XXXXXX
|
||||
```
|
||||
|
||||
Only `VERDICT=PASS` is valid GUI evidence. Assertion failure, a black segment,
|
||||
missing recording, wrong buffer, split window, stale frame, or missing temporal
|
||||
review remains fail-closed.
|
||||
|
||||
Run `make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs`.
|
||||
Run `make perf` for the fixed 1413×62 latency gate. Every scenario performs
|
||||
five unmeasured warmups followed by 30 measured samples; both p95 and max must
|
||||
|
||||
@ -58,6 +58,43 @@ examples/research-shelf.el # DATA / THEME / STATE / VIEW / ROOT 分区
|
||||
该 companion 注册了 `:reload-on-refresh t`;保存 `.el`、`.etaf` 或 `.ecss` 后刷新
|
||||
source,会在下一次 mount 前重新加载完整 consumer。
|
||||
|
||||
## 可重复执行的 Emacs 31.1 GUI 实测
|
||||
|
||||
仓库提供一个稳定的真实 GUI runner,不再临时拼接 `emacsclient`、前台激活和录屏
|
||||
命令:
|
||||
|
||||
```sh
|
||||
make gui-doctor
|
||||
make gui-research
|
||||
make gui-flex
|
||||
make gui-grid
|
||||
# 或按顺序采集三个场景:
|
||||
make gui-all
|
||||
```
|
||||
|
||||
可复用的执行引擎与进程 runner 位于 `../etaf/scripts/`,只理解 Scenario、Action、
|
||||
Context、checkpoint、录屏和 evidence 合同。`scripts/playground-gui-scenarios.el` 只是
|
||||
薄适配层:Research Shelf 提供应用动作,Flex 与 Grid 只是同一个 Ebox reference
|
||||
scenario factory 的两个输入。增加新应用时不会复制第二套 daemon/录屏/checkpoint
|
||||
实现。
|
||||
|
||||
每个场景都会创建唯一命名 daemon,关闭 native-comp JIT,显式加载 sibling 仓库,
|
||||
创建单一 GUI frame,由外层 shell 激活 Emacs,通过保留 PTY 的 macOS recorder 录屏,
|
||||
依次执行 mount/resize/scroll/interaction checkpoint,生成 screenshot 与 manifest,最后
|
||||
清理自己创建的全部进程。Flex 始终从已提交的 `ebox-playground` HEAD 解包,绝不读取、
|
||||
覆盖、暂存或 restore 用户的脏 fixture。
|
||||
|
||||
新采集有意保持 `INCOMPLETE`,直到人或 agent 检查 `report.md`、
|
||||
`contact-sheet.png` 和报告选出的首尾图。完成时序审查后,对同一个 run directory
|
||||
执行:
|
||||
|
||||
```sh
|
||||
scripts/run-gui-verification.sh review /private/tmp/etaf-playground-gui.XXXXXX
|
||||
```
|
||||
|
||||
只有 `VERDICT=PASS` 才是有效 GUI 证据。assertion 失败、黑帧、录屏缺失、错误 buffer、
|
||||
split window、陈旧 frame 或没有完成时序审查都会保持 fail-closed。
|
||||
|
||||
验证命令:`make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs`。
|
||||
性能门禁:`make perf`(1413×62 viewport 的 warm 选行/主题延迟)。
|
||||
|
||||
|
||||
446
scripts/playground-gui-scenarios.el
Normal file
446
scripts/playground-gui-scenarios.el
Normal file
@ -0,0 +1,446 @@
|
||||
;;; playground-gui-scenarios.el --- Playground GUI verifier adapters -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Concrete Research Shelf and Ebox reference adapters for the generic
|
||||
;; `emacs-gui-verifier' engine. Process, recording, checkpoint ordering, and
|
||||
;; evidence lifecycle remain outside this file.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'emacs-gui-verifier)
|
||||
(require 'ebox-playground)
|
||||
(require 'etaf-playground)
|
||||
(require 'benchmark-research-shelf)
|
||||
(require 'ebox-native-reflow)
|
||||
|
||||
(defun etaf-playground-gui-scenarios--report (context)
|
||||
"Return CONTEXT target's public Ebox report, or nil before mount."
|
||||
(let ((buffer (etaf-gui-verifier-context-target-buffer context)))
|
||||
(when (and (buffer-live-p buffer)
|
||||
(ebox-surface-buffer-mounted-p buffer))
|
||||
(ebox-buffer-update-report buffer))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--visible-window-content-p (context)
|
||||
"Return non-nil when CONTEXT's selected window shows nonblank text."
|
||||
(let* ((buffer (etaf-gui-verifier-context-target-buffer context))
|
||||
(window (selected-window)))
|
||||
(and (buffer-live-p buffer)
|
||||
(window-live-p window)
|
||||
(eq (window-buffer window) buffer)
|
||||
(let ((start (window-start window))
|
||||
(end (window-end window t)))
|
||||
(and (integer-or-marker-p start)
|
||||
(integer-or-marker-p end)
|
||||
(< start end)
|
||||
(with-current-buffer buffer
|
||||
(string-match-p
|
||||
"[^[:space:]]"
|
||||
(buffer-substring-no-properties start end))))))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--buffer-text (context)
|
||||
"Return CONTEXT target's complete plain buffer text."
|
||||
(let ((buffer (etaf-gui-verifier-context-target-buffer context)))
|
||||
(and (buffer-live-p buffer)
|
||||
(with-current-buffer buffer
|
||||
(buffer-substring-no-properties (point-min) (point-max))))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--mounted-settled-p (context)
|
||||
"Return non-nil when CONTEXT has a mounted, visibly rendered surface."
|
||||
(let ((buffer (etaf-gui-verifier-context-target-buffer context)))
|
||||
(and (buffer-live-p buffer)
|
||||
(ebox-surface-buffer-mounted-p buffer)
|
||||
(eq (frame-parameter nil 'fullscreen)
|
||||
(etaf-gui-verifier-context-get context 'target-fullscreen))
|
||||
(etaf-playground-gui-scenarios--visible-window-content-p context))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--begin-viewport-change
|
||||
(context fullscreen &optional width height)
|
||||
"Record CONTEXT state before targeting FULLSCREEN, WIDTH, and HEIGHT."
|
||||
(let ((report (etaf-playground-gui-scenarios--report context)))
|
||||
(etaf-gui-verifier-context-put
|
||||
context 'viewport-revision-before
|
||||
(plist-get report :runtime-revision))
|
||||
(etaf-gui-verifier-context-put context 'target-fullscreen fullscreen)
|
||||
(etaf-gui-verifier-context-put context 'target-frame-width width)
|
||||
(etaf-gui-verifier-context-put context 'target-frame-height height)))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--frame-target-settled-p (context)
|
||||
"Return non-nil when the selected frame reached CONTEXT's target geometry."
|
||||
(let ((fullscreen
|
||||
(etaf-gui-verifier-context-get context 'target-fullscreen))
|
||||
(width (etaf-gui-verifier-context-get context 'target-frame-width))
|
||||
(height (etaf-gui-verifier-context-get context 'target-frame-height)))
|
||||
(and (eq (frame-parameter nil 'fullscreen) fullscreen)
|
||||
(or (not (numberp width)) (= (frame-text-width) width))
|
||||
(or (not (numberp height)) (= (frame-text-height) height)))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--viewport-settled-p (context)
|
||||
"Return non-nil when CONTEXT published the selected window's viewport."
|
||||
(let* ((report (etaf-playground-gui-scenarios--report context))
|
||||
(actual (ebox-viewport-window-width (selected-window)))
|
||||
(published (or (plist-get report :target-viewport-width)
|
||||
(plist-get report :viewport-width)))
|
||||
(revision (plist-get report :runtime-revision))
|
||||
(previous
|
||||
(etaf-gui-verifier-context-get
|
||||
context 'viewport-revision-before)))
|
||||
(and (numberp actual)
|
||||
(numberp published)
|
||||
(= actual published)
|
||||
(numberp revision)
|
||||
(or (not (numberp previous)) (> revision previous))
|
||||
(plist-get report :runtime-published)
|
||||
(not (plist-get report :tp-scope-fallback))
|
||||
(etaf-playground-gui-scenarios--frame-target-settled-p context)
|
||||
(etaf-playground-gui-scenarios--visible-window-content-p context))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--invariants (context)
|
||||
"Return shared Ebox/ETAF publication invariants for CONTEXT."
|
||||
(let* ((buffer (etaf-gui-verifier-context-target-buffer context))
|
||||
(mounted (and (buffer-live-p buffer)
|
||||
(ebox-surface-buffer-mounted-p buffer)))
|
||||
(text (and mounted
|
||||
(with-current-buffer buffer
|
||||
(buffer-substring-no-properties
|
||||
(point-min) (point-max))))))
|
||||
(when (etaf-gui-verifier-context-get context 'expects-mounted)
|
||||
(list
|
||||
(etaf-gui-verifier-assert "surface-mounted" mounted)
|
||||
(etaf-gui-verifier-assert
|
||||
"rendered-output-nonempty" (and text (> (length text) 0)))
|
||||
(etaf-gui-verifier-assert
|
||||
"visible-output-nonempty"
|
||||
(etaf-playground-gui-scenarios--visible-window-content-p context))
|
||||
(etaf-gui-verifier-assert
|
||||
"no-render-error"
|
||||
(and text
|
||||
(not (string-match-p
|
||||
"could not render\\|runtime error\\|Wrong type argument"
|
||||
text))))))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--adapter (context)
|
||||
"Return Playground-specific JSON data for CONTEXT."
|
||||
(let* ((report (etaf-playground-gui-scenarios--report context))
|
||||
(buffer (etaf-gui-verifier-context-target-buffer context))
|
||||
(runtime (and (buffer-live-p buffer)
|
||||
(etaf-runtime-for-buffer buffer)))
|
||||
(stage (plist-get report :stage)))
|
||||
(list
|
||||
(cons 'generation (if runtime (etaf-runtime-generation runtime) 0))
|
||||
(cons 'viewport_width
|
||||
(or (plist-get report :target-viewport-width)
|
||||
(plist-get report :viewport-width)
|
||||
0))
|
||||
(cons 'frame_text_width (frame-text-width))
|
||||
(cons 'frame_text_height (frame-text-height))
|
||||
(cons 'fullscreen
|
||||
(format "%s" (frame-parameter nil 'fullscreen)))
|
||||
(cons 'target_frame_width
|
||||
(or (etaf-gui-verifier-context-get
|
||||
context 'target-frame-width)
|
||||
0))
|
||||
(cons 'target_frame_height
|
||||
(or (etaf-gui-verifier-context-get
|
||||
context 'target-frame-height)
|
||||
0))
|
||||
(cons 'stage (if stage (symbol-name stage) "none")))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--resize (context width height)
|
||||
"Resize CONTEXT's selected frame to pixel WIDTH and HEIGHT."
|
||||
(etaf-playground-gui-scenarios--begin-viewport-change
|
||||
context nil width height)
|
||||
(let ((frame-resize-pixelwise t))
|
||||
(set-frame-size nil width height t)))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--windowed-action ()
|
||||
"Return an action that leaves fullscreen and publishes its viewport."
|
||||
(etaf-gui-verifier-action-create
|
||||
:id "leave-fullscreen"
|
||||
:execute
|
||||
(lambda (context)
|
||||
(etaf-playground-gui-scenarios--begin-viewport-change context nil)
|
||||
(set-frame-parameter nil 'fullscreen nil))
|
||||
:settled-p #'etaf-playground-gui-scenarios--viewport-settled-p
|
||||
:assertions #'etaf-playground-gui-scenarios--resize-assertions))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--resize-assertions (context)
|
||||
"Return exact accepted-viewport assertions for CONTEXT."
|
||||
(let* ((report (etaf-playground-gui-scenarios--report context))
|
||||
(actual (ebox-viewport-window-width (selected-window)))
|
||||
(published (or (plist-get report :target-viewport-width)
|
||||
(plist-get report :viewport-width))))
|
||||
(list
|
||||
(etaf-gui-verifier-assert
|
||||
"viewport-published"
|
||||
(and (numberp actual) (numberp published) (= actual published)
|
||||
(plist-get report :runtime-published)))
|
||||
(etaf-gui-verifier-assert
|
||||
"resize-not-scope-fallback"
|
||||
(not (plist-get report :tp-scope-fallback))))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--resize-action (id width height)
|
||||
"Return resize action ID for pixel WIDTH and HEIGHT."
|
||||
(etaf-gui-verifier-action-create
|
||||
:id id
|
||||
:execute (lambda (context)
|
||||
(etaf-playground-gui-scenarios--resize
|
||||
context width height))
|
||||
:settled-p #'etaf-playground-gui-scenarios--viewport-settled-p
|
||||
:assertions #'etaf-playground-gui-scenarios--resize-assertions
|
||||
:screenshot t))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--maximize-action ()
|
||||
"Return a stable full-workarea viewport publication action."
|
||||
(etaf-gui-verifier-action-create
|
||||
:id "maximize-frame"
|
||||
:execute
|
||||
(lambda (context)
|
||||
(etaf-playground-gui-scenarios--begin-viewport-change
|
||||
context 'maximized)
|
||||
(set-frame-parameter nil 'fullscreen 'maximized))
|
||||
:settled-p #'etaf-playground-gui-scenarios--viewport-settled-p
|
||||
:assertions #'etaf-playground-gui-scenarios--resize-assertions
|
||||
:screenshot t))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--scroll-progressed-p (context)
|
||||
"Return non-nil when CONTEXT's pending scroll changed visible state."
|
||||
(or
|
||||
(/= (etaf-gui-verifier-context-get
|
||||
context 'scroll-window-start (window-start))
|
||||
(window-start))
|
||||
(not
|
||||
(equal
|
||||
(etaf-gui-verifier-context-get context 'scroll-report)
|
||||
(etaf-playground-gui-scenarios--report context)))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--scroll-settled-p (context)
|
||||
"Return non-nil when CONTEXT's scroll progressed to visible content."
|
||||
(and (etaf-playground-gui-scenarios--scroll-progressed-p context)
|
||||
(etaf-playground-gui-scenarios--visible-window-content-p context)))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--scroll-action (id command)
|
||||
"Return Ebox page-scroll action ID using public COMMAND."
|
||||
(etaf-gui-verifier-action-create
|
||||
:id id
|
||||
:execute
|
||||
(lambda (context)
|
||||
(etaf-gui-verifier-context-put context 'scroll-window-start
|
||||
(window-start))
|
||||
(etaf-gui-verifier-context-put
|
||||
context 'scroll-report
|
||||
(etaf-playground-gui-scenarios--report context))
|
||||
(with-current-buffer (etaf-gui-verifier-context-target-buffer context)
|
||||
(goto-char (window-start)))
|
||||
(funcall command))
|
||||
:settled-p #'etaf-playground-gui-scenarios--scroll-settled-p
|
||||
:assertions
|
||||
(lambda (context)
|
||||
(list
|
||||
(etaf-gui-verifier-assert
|
||||
"scroll-progressed"
|
||||
(etaf-playground-gui-scenarios--scroll-progressed-p context))))
|
||||
:screenshot t))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--reset-scroll-action ()
|
||||
"Return an action that restores the outer Emacs window to its top."
|
||||
(etaf-gui-verifier-action-create
|
||||
:id "reset-scroll"
|
||||
:execute
|
||||
(lambda (_context)
|
||||
(set-window-start (selected-window) (point-min))
|
||||
(goto-char (point-min)))
|
||||
:settled-p
|
||||
(lambda (context)
|
||||
(and (= (window-start) (point-min))
|
||||
(etaf-playground-gui-scenarios--visible-window-content-p context)))
|
||||
:assertions
|
||||
(lambda (_context)
|
||||
(list
|
||||
(etaf-gui-verifier-assert
|
||||
"scroll-reset" (= (window-start) (point-min)))))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios--research-event-action
|
||||
(id reference postcondition &optional screenshot)
|
||||
"Return Research action ID for REFERENCE, POSTCONDITION, and SCREENSHOT."
|
||||
(etaf-gui-verifier-action-create
|
||||
:id id
|
||||
:execute
|
||||
(lambda (context)
|
||||
(let ((runtime
|
||||
(etaf-runtime-for-buffer
|
||||
(etaf-gui-verifier-context-target-buffer context))))
|
||||
(etaf-gui-verifier-context-put
|
||||
context (intern (concat id "-generation"))
|
||||
(etaf-runtime-generation runtime))
|
||||
(with-current-buffer
|
||||
(etaf-gui-verifier-context-target-buffer context)
|
||||
(etaf-focus runtime reference)
|
||||
(execute-kbd-macro (kbd "RET")))))
|
||||
:settled-p
|
||||
(lambda (context)
|
||||
(let ((runtime
|
||||
(etaf-runtime-for-buffer
|
||||
(etaf-gui-verifier-context-target-buffer context))))
|
||||
(and runtime
|
||||
(> (etaf-runtime-generation runtime)
|
||||
(etaf-gui-verifier-context-get
|
||||
context (intern (concat id "-generation")) -1))
|
||||
(funcall postcondition context)
|
||||
(etaf-playground-gui-scenarios--visible-window-content-p
|
||||
context))))
|
||||
:assertions
|
||||
(lambda (context)
|
||||
(let ((runtime
|
||||
(etaf-runtime-for-buffer
|
||||
(etaf-gui-verifier-context-target-buffer context))))
|
||||
(list
|
||||
(etaf-gui-verifier-assert
|
||||
"generation-advanced"
|
||||
(> (etaf-runtime-generation runtime)
|
||||
(etaf-gui-verifier-context-get
|
||||
context (intern (concat id "-generation")) -1)))
|
||||
(etaf-gui-verifier-assert
|
||||
"activated-host-focused"
|
||||
(equal reference (etaf-focused-host-ref runtime)))
|
||||
(etaf-gui-verifier-assert
|
||||
"product-postcondition"
|
||||
(funcall postcondition context)))))
|
||||
:screenshot screenshot))
|
||||
|
||||
(defun etaf-playground-gui-scenarios-research ()
|
||||
"Return the Research Shelf GUI scenario adapter."
|
||||
(etaf-gui-verifier-scenario-create
|
||||
:name "research-shelf"
|
||||
:claim "Research Shelf mounts, resizes, scrolls, and handles interactions"
|
||||
:initialize
|
||||
(lambda (context)
|
||||
(etaf-gui-verifier-context-select-buffer
|
||||
context (get-buffer-create "*ETAF GUI Research Shelf*")))
|
||||
:invariants #'etaf-playground-gui-scenarios--invariants
|
||||
:adapter #'etaf-playground-gui-scenarios--adapter
|
||||
:actions
|
||||
(list
|
||||
(etaf-gui-verifier-action-create
|
||||
:id "mount"
|
||||
:execute
|
||||
(lambda (context)
|
||||
(etaf-gui-verifier-context-put context 'target-fullscreen 'maximized)
|
||||
(set-frame-parameter nil 'fullscreen 'maximized)
|
||||
(etaf-playground-refresh-examples)
|
||||
(etaf-gui-verifier-context-select-buffer
|
||||
context
|
||||
(etaf-playground-open-example
|
||||
"research-shelf" "*ETAF GUI Research Shelf*"))
|
||||
(etaf-gui-verifier-context-put context 'expects-mounted t))
|
||||
:settled-p #'etaf-playground-gui-scenarios--mounted-settled-p
|
||||
:assertions
|
||||
(lambda (context)
|
||||
(list
|
||||
(etaf-gui-verifier-assert
|
||||
"runtime-mounted"
|
||||
(etaf-runtime-for-buffer
|
||||
(etaf-gui-verifier-context-target-buffer context)))))
|
||||
:screenshot t)
|
||||
(etaf-playground-gui-scenarios--windowed-action)
|
||||
(etaf-playground-gui-scenarios--resize-action
|
||||
"resize-narrow" 900 500)
|
||||
(etaf-playground-gui-scenarios--scroll-action
|
||||
"scroll-down" #'ebox-scroll-page-down)
|
||||
(etaf-playground-gui-scenarios--reset-scroll-action)
|
||||
(etaf-playground-gui-scenarios--resize-action
|
||||
"resize-wide" 1300 750)
|
||||
(etaf-playground-gui-scenarios--research-event-action
|
||||
"row-select" 'research-shelf-row-2
|
||||
(lambda (context)
|
||||
(string-match-p
|
||||
"John Berger · Book"
|
||||
(etaf-playground-gui-scenarios--buffer-text context))))
|
||||
(etaf-playground-gui-scenarios--research-event-action
|
||||
"theme-toggle" 'research-shelf-theme-toggle
|
||||
(lambda (context)
|
||||
(string-match-p
|
||||
"Dark theme"
|
||||
(etaf-playground-gui-scenarios--buffer-text context)))
|
||||
t)
|
||||
(etaf-playground-gui-scenarios--research-event-action
|
||||
"pagination" 'research-shelf-page-next
|
||||
(lambda (context)
|
||||
(let ((text
|
||||
(etaf-playground-gui-scenarios--buffer-text context)))
|
||||
(and (string-match-p "Page 2 /" text)
|
||||
(string-match-p "13–24 of" text))))
|
||||
t)
|
||||
(etaf-playground-gui-scenarios--maximize-action))
|
||||
:completion
|
||||
(lambda (context)
|
||||
(= (etaf-gui-verifier-context-action-count context) 10))))
|
||||
|
||||
(defun etaf-playground-gui-scenarios-ebox (scenario fixture)
|
||||
"Return one Ebox SCENARIO adapter for FIXTURE."
|
||||
(etaf-gui-verifier-scenario-create
|
||||
:name scenario
|
||||
:claim "Ebox reference mounts, follows frame resize, and scrolls"
|
||||
:initialize
|
||||
(lambda (context)
|
||||
(let ((source (find-file fixture)))
|
||||
(ebox-dsl-mode)
|
||||
(etaf-gui-verifier-context-select-buffer context source)))
|
||||
:invariants #'etaf-playground-gui-scenarios--invariants
|
||||
:adapter #'etaf-playground-gui-scenarios--adapter
|
||||
:actions
|
||||
(list
|
||||
(etaf-gui-verifier-action-create
|
||||
:id "mount"
|
||||
:execute
|
||||
(lambda (context)
|
||||
(etaf-gui-verifier-context-put context 'target-fullscreen 'maximized)
|
||||
(set-frame-parameter nil 'fullscreen 'maximized)
|
||||
(etaf-gui-verifier-context-select-buffer context (ebox-dsl-render))
|
||||
(etaf-gui-verifier-context-put context 'expects-mounted t))
|
||||
:settled-p #'etaf-playground-gui-scenarios--mounted-settled-p
|
||||
:assertions
|
||||
(lambda (context)
|
||||
(let ((preview (etaf-gui-verifier-context-target-buffer context)))
|
||||
(list
|
||||
(etaf-gui-verifier-assert
|
||||
"preview-mounted"
|
||||
(and (buffer-live-p preview)
|
||||
(ebox-surface-buffer-mounted-p preview))))))
|
||||
:screenshot t)
|
||||
(etaf-playground-gui-scenarios--windowed-action)
|
||||
(etaf-playground-gui-scenarios--resize-action
|
||||
"resize-narrow" 900 500)
|
||||
(etaf-playground-gui-scenarios--scroll-action
|
||||
"scroll-down" #'ebox-scroll-page-down)
|
||||
(etaf-playground-gui-scenarios--reset-scroll-action)
|
||||
(etaf-playground-gui-scenarios--resize-action
|
||||
"resize-wide" 1300 750)
|
||||
(etaf-playground-gui-scenarios--maximize-action))
|
||||
:completion
|
||||
(lambda (context)
|
||||
(= (etaf-gui-verifier-context-action-count context) 7))))
|
||||
|
||||
;;;###autoload
|
||||
(defun etaf-playground-gui-scenarios-run-from-environment ()
|
||||
"Build one concrete adapter from environment and run the generic engine."
|
||||
(unless (plist-get (ebox-native-reflow-runtime-report) :layout-ready-p)
|
||||
(error "Prepared Ebox native module is unavailable"))
|
||||
(let* ((scenario (or (getenv "ETAF_GUI_SCENARIO")
|
||||
(error "ETAF_GUI_SCENARIO is not configured")))
|
||||
(run-directory (or (getenv "ETAF_GUI_RUN_DIR")
|
||||
(error "ETAF_GUI_RUN_DIR is not configured")))
|
||||
(fixture (getenv "ETAF_GUI_FIXTURE"))
|
||||
(adapter
|
||||
(pcase scenario
|
||||
("research-shelf"
|
||||
(etaf-playground-gui-scenarios-research))
|
||||
((or "flex-reference" "grid-reference")
|
||||
(unless (and fixture (file-readable-p fixture))
|
||||
(error "ETAF_GUI_FIXTURE is not readable"))
|
||||
(etaf-playground-gui-scenarios-ebox scenario fixture))
|
||||
(_ (error "Unknown Playground GUI scenario: %s" scenario)))))
|
||||
(etaf-gui-verifier-run adapter run-directory)))
|
||||
|
||||
(provide 'playground-gui-scenarios)
|
||||
;;; playground-gui-scenarios.el ends here
|
||||
122
scripts/run-gui-verification.sh
Executable file
122
scripts/run-gui-verification.sh
Executable file
@ -0,0 +1,122 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
PLAYGROUND_SCRIPT_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd)
|
||||
PLAYGROUND_DIR=$(dirname "$PLAYGROUND_SCRIPT_DIR")
|
||||
REPOS_DIR=$(dirname "$PLAYGROUND_DIR")
|
||||
GENERIC_RUNNER="$REPOS_DIR/etaf/scripts/run-emacs-gui-verification.sh"
|
||||
SCENARIO_ADAPTER="$PLAYGROUND_SCRIPT_DIR/playground-gui-scenarios.el"
|
||||
SCENARIO_ENTRY=etaf-playground-gui-scenarios-run-from-environment
|
||||
|
||||
usage() {
|
||||
echo "usage:" >&2
|
||||
echo " $0 doctor" >&2
|
||||
echo " $0 review RUN_DIR" >&2
|
||||
echo " $0 all [--run-dir DIRECTORY]" >&2
|
||||
echo " $0 research-shelf|flex-reference|grid-reference [--run-dir DIRECTORY]" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
doctor() {
|
||||
[ -x "$GENERIC_RUNNER" ] || {
|
||||
echo "generic GUI runner is unavailable: $GENERIC_RUNNER" >&2
|
||||
exit 1
|
||||
}
|
||||
[ -f "$SCENARIO_ADAPTER" ] || {
|
||||
echo "Playground scenario adapter is unavailable: $SCENARIO_ADAPTER" >&2
|
||||
exit 1
|
||||
}
|
||||
"$GENERIC_RUNNER" doctor
|
||||
echo "PLAYGROUND-GUI-ADAPTER DOCTOR PASS"
|
||||
}
|
||||
|
||||
parse_run_dir() {
|
||||
SCENARIO_RUN_DIR=""
|
||||
while [ "$#" -gt 0 ]; do
|
||||
case $1 in
|
||||
--run-dir)
|
||||
[ "$#" -ge 2 ] || usage
|
||||
SCENARIO_RUN_DIR=$2
|
||||
shift 2
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
done
|
||||
if [ -z "$SCENARIO_RUN_DIR" ]; then
|
||||
SCENARIO_RUN_DIR=$(mktemp -d /private/tmp/etaf-playground-gui.XXXXXX)
|
||||
else
|
||||
mkdir -p "$SCENARIO_RUN_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_fixture() {
|
||||
case $SCENARIO_NAME in
|
||||
research-shelf)
|
||||
SCENARIO_FIXTURE=""
|
||||
;;
|
||||
grid-reference)
|
||||
SCENARIO_FIXTURE="$REPOS_DIR/ebox-playground/examples/grid-reference.ebox"
|
||||
;;
|
||||
flex-reference)
|
||||
SCENARIO_FIXTURE_ROOT="$SCENARIO_RUN_DIR/fixture"
|
||||
mkdir -p "$SCENARIO_FIXTURE_ROOT"
|
||||
git -C "$REPOS_DIR/ebox-playground" archive \
|
||||
--format=tar --output="$SCENARIO_RUN_DIR/flex-fixture.tar" \
|
||||
HEAD examples/flex-reference.ebox
|
||||
tar -xf "$SCENARIO_RUN_DIR/flex-fixture.tar" \
|
||||
-C "$SCENARIO_FIXTURE_ROOT"
|
||||
SCENARIO_FIXTURE="$SCENARIO_FIXTURE_ROOT/examples/flex-reference.ebox"
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_scenario() {
|
||||
doctor >/dev/null
|
||||
prepare_fixture
|
||||
export ETAF_GUI_SCENARIO="$SCENARIO_NAME"
|
||||
export ETAF_GUI_FIXTURE="$SCENARIO_FIXTURE"
|
||||
"$GENERIC_RUNNER" run "$SCENARIO_ADAPTER" "$SCENARIO_ENTRY" \
|
||||
--run-dir "$SCENARIO_RUN_DIR" \
|
||||
--load-path "$REPOS_DIR/tp" \
|
||||
--load-path "$REPOS_DIR/ecss" \
|
||||
--load-path "$REPOS_DIR/ebox" \
|
||||
--load-path "$REPOS_DIR/etaf" \
|
||||
--load-path "$REPOS_DIR/etaf-ui" \
|
||||
--load-path "$REPOS_DIR/etaf-sqlite" \
|
||||
--load-path "$REPOS_DIR/ebox-playground" \
|
||||
--load-path "$REPOS_DIR/ebox-playground/scripts" \
|
||||
--load-path "$PLAYGROUND_DIR" \
|
||||
--load-path "$PLAYGROUND_SCRIPT_DIR" \
|
||||
--load-path "$PLAYGROUND_DIR/examples"
|
||||
}
|
||||
|
||||
[ "$#" -ge 1 ] || usage
|
||||
COMMAND=$1
|
||||
shift
|
||||
|
||||
case $COMMAND in
|
||||
doctor)
|
||||
[ "$#" -eq 0 ] || usage
|
||||
doctor
|
||||
;;
|
||||
review)
|
||||
[ "$#" -eq 1 ] || usage
|
||||
"$GENERIC_RUNNER" review "$1"
|
||||
;;
|
||||
all)
|
||||
parse_run_dir "$@"
|
||||
ALL_RUN_ROOT=$SCENARIO_RUN_DIR
|
||||
for SCENARIO_ITEM in research-shelf flex-reference grid-reference; do
|
||||
"$0" "$SCENARIO_ITEM" --run-dir "$ALL_RUN_ROOT/$SCENARIO_ITEM"
|
||||
done
|
||||
echo "PLAYGROUND-GUI ALL CAPTURED root=$ALL_RUN_ROOT"
|
||||
;;
|
||||
research-shelf|flex-reference|grid-reference)
|
||||
SCENARIO_NAME=$COMMAND
|
||||
parse_run_dir "$@"
|
||||
run_scenario
|
||||
;;
|
||||
*) usage ;;
|
||||
esac
|
||||
Loading…
Reference in New Issue
Block a user