feat: publish Ebox SPI v2 provider for M2a E5
Some checks are pending
CI / test (push) Waiting to run
CI / native-build (macos-latest) (push) Waiting to run
CI / native-build (ubuntu-latest) (push) Waiting to run
CI / native-build (windows-latest) (push) Waiting to run
CI / native-msrv (macos-latest) (push) Waiting to run
CI / native-msrv (ubuntu-latest) (push) Waiting to run
CI / native-msrv (windows-latest) (push) Waiting to run
Some checks are pending
CI / test (push) Waiting to run
CI / native-build (macos-latest) (push) Waiting to run
CI / native-build (ubuntu-latest) (push) Waiting to run
CI / native-build (windows-latest) (push) Waiting to run
CI / native-msrv (macos-latest) (push) Waiting to run
CI / native-msrv (ubuntu-latest) (push) Waiting to run
CI / native-msrv (windows-latest) (push) Waiting to run
This commit is contained in:
parent
918a88d95d
commit
b5e8b6d863
@ -11,6 +11,7 @@ Ebox is the low-level spatial rendering engine. Its job is to turn a declarative
|
||||
- The native module is an optional accelerator with an exact Elisp fallback.
|
||||
- New abstractions must remove duplication or make an existing public workflow simpler.
|
||||
- Public property schema, ECSS metadata, and used engine projection are derived from one property definition record.
|
||||
- Cross-package framework integration is an additive versioned provider; consumer selection and fallback policy remain outside Ebox.
|
||||
|
||||
## Pipeline
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ Ebox 是底层空间渲染引擎。它负责把声明式节点树转换为经过
|
||||
- native 模块只是可选加速器,必须有完全等价的 Elisp fallback。
|
||||
- 新抽象必须消除重复,或让已有的公共工作流更简单。
|
||||
- 公共 property schema、ECSS metadata 与 used engine projection 都从同一 property definition record 派生。
|
||||
- 跨包 framework integration 只发布 additive versioned provider;consumer selection 与 fallback policy 不属于 Ebox。
|
||||
|
||||
## 流水线
|
||||
|
||||
|
||||
10
Makefile
10
Makefile
@ -10,7 +10,7 @@ NATIVE_MANIFEST = native/Cargo.toml
|
||||
NATIVE_TARGET ?= $(shell $(EMACS_BATCH) -l ebox-native-reflow.el --eval '(princ (ebox-native-reflow--rust-target))')
|
||||
NATIVE_RELEASE_DIR = native/target/$(NATIVE_TARGET)/release
|
||||
|
||||
.PHONY: all check ci load compile test checkdoc source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests state-contract-tests layout-boundary-tests layout-boundary-performance patch-plan-tests patch-plan-performance style-schema-tests style-schema-performance docs-contract-tests ci-contract-tests performance-evaluator visual-check native-rust-tests native-build diff-check clean package-lint package-lint-install
|
||||
.PHONY: all check ci load compile test checkdoc source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests state-contract-tests layout-boundary-tests layout-boundary-performance patch-plan-tests patch-plan-performance style-schema-tests style-schema-performance spi-tests spi-performance docs-contract-tests ci-contract-tests performance-evaluator visual-check native-rust-tests native-build diff-check clean package-lint package-lint-install
|
||||
|
||||
all: check
|
||||
|
||||
@ -25,7 +25,7 @@ compile:
|
||||
rm -f *.elc tests/*.elc scripts/*.elc
|
||||
$(EMACS_BATCH) --eval '(setq byte-compile-error-on-warn t byte-compile-warnings (quote (not obsolete)))' -l ebox.el --eval '(ebox-byte-compile)'
|
||||
|
||||
test: source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests state-contract-tests layout-boundary-tests patch-plan-tests style-schema-tests docs-contract-tests ci-contract-tests
|
||||
test: source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests state-contract-tests layout-boundary-tests patch-plan-tests style-schema-tests spi-tests docs-contract-tests ci-contract-tests
|
||||
|
||||
source-tests:
|
||||
$(EMACS_BATCH) -l tests/ebox-source-tests.el -f ert-run-tests-batch-and-exit
|
||||
@ -84,6 +84,12 @@ style-schema-tests:
|
||||
style-schema-performance:
|
||||
$(EMACS_TEST) -l tests/ebox-style-schema-tests.el --eval "(ert-run-tests-batch-and-exit 'ebox-style-schema-performance-uses-one-lookup-per-property)"
|
||||
|
||||
spi-tests:
|
||||
$(EMACS_TEST) -l tests/ebox-spi-tests.el -f ert-run-tests-batch-and-exit
|
||||
|
||||
spi-performance:
|
||||
$(EMACS_TEST) -l tests/ebox-spi-tests.el --eval "(ert-run-tests-batch-and-exit 'ebox-spi-performance-builds-bounded-provider-snapshots)"
|
||||
|
||||
performance-evaluator: surface-tests ebox-commit-tests
|
||||
$(EMACS_BATCH) -l scripts/ebox-performance-evaluator.el -f ebox-performance-evaluator-batch
|
||||
|
||||
|
||||
@ -35,11 +35,12 @@ This is the maintainer entry point for the standalone Ebox repository. It descri
|
||||
| `ebox-incremental.el` | Runtime state, snapshots, dirty planning, owner escalation, pure commit preparation, and reports. |
|
||||
| `ebox-dsl.el` | Data-oriented `.ebox` forms and lowering to public nodes. |
|
||||
| `ebox-selector.el` | CSS-like string parsing to ECSS structured selector ASTs, indexed candidate lookup, and tree/runtime query handles. |
|
||||
| `ebox-spi.el` | Additive immutable framework SPI v2 provider and initial/update operation descriptors. |
|
||||
| `ebox-native-reflow.el` | Optional native module loading/build commands, ABI checks, bounded sessions, and Elisp fallback. |
|
||||
|
||||
The package intentionally does not include application Components, UI controls, reactive data, or a playground implementation. Those are sibling-package responsibilities.
|
||||
|
||||
The active contract also covers `Makefile`, `.github/workflows/ci.yml`, `tests/ebox-core-render-tests.el`, `tests/ebox-state-contract-tests.el`, `tests/ebox-layout-boundary-tests.el`, `tests/ebox-patch-plan-tests.el`, `tests/ebox-style-schema-tests.el`, `tests/ebox-child-range-tests.el`, `tests/ebox-grid-tests.el`, `tests/ebox-commit-tests.el`, `tests/ebox-surface-tests.el`, `tests/ebox-dsl-tests.el`, `tests/ebox-flex-tests.el`, `tests/ebox-selector-tests.el`, `tests/ebox-package-tests.el`, `tests/ebox-visual-check-tests.el`, `tests/ebox-docs-contract-tests.el`, `tests/ebox-ci-contract-tests.el`, `native/Cargo.toml`, `native/Cargo.lock`, `native/build.rs`, `native/vendor/emacs-30/emacs-module.h`, `native/src/lib.rs`, `native/src/layout.rs`, `native/c/ebox_module.c`, `scripts/ebox-package-lint.el`, `scripts/ebox-visual-check.el`, and `scripts/ebox-performance-evaluator.el`.
|
||||
The active contract also covers `Makefile`, `.github/workflows/ci.yml`, `tests/ebox-core-render-tests.el`, `tests/ebox-state-contract-tests.el`, `tests/ebox-layout-boundary-tests.el`, `tests/ebox-patch-plan-tests.el`, `tests/ebox-style-schema-tests.el`, `tests/ebox-spi-tests.el`, `tests/ebox-child-range-tests.el`, `tests/ebox-grid-tests.el`, `tests/ebox-commit-tests.el`, `tests/ebox-surface-tests.el`, `tests/ebox-dsl-tests.el`, `tests/ebox-flex-tests.el`, `tests/ebox-selector-tests.el`, `tests/ebox-package-tests.el`, `tests/ebox-visual-check-tests.el`, `tests/ebox-docs-contract-tests.el`, `tests/ebox-ci-contract-tests.el`, `native/Cargo.toml`, `native/Cargo.lock`, `native/build.rs`, `native/vendor/emacs-30/emacs-module.h`, `native/src/lib.rs`, `native/src/layout.rs`, `native/c/ebox_module.c`, `scripts/ebox-package-lint.el`, `scripts/ebox-visual-check.el`, and `scripts/ebox-performance-evaluator.el`.
|
||||
|
||||
## Runtime model
|
||||
|
||||
@ -89,6 +90,7 @@ Caller-owned Source Tree
|
||||
- `ebox-layout.el` has no load or call edge to `ebox-surface.el` or TP. Candidate isolation, inherited-cascade detection, and one-shot materialization enter layout only through the validated port owned by `ebox-render-context.el`; `ebox.el` wires the surface implementation after both sides load.
|
||||
- `ebox-patch-plan.el` consumes only tentative operation plists plus the immutable generation parent table and returns a deterministic plan artifact. The incremental adapter owns all live-fact preparation. E3 defaults to the pure route while retaining explicit `legacy` and equality-checking `shadow` routes; none of the three routes publishes.
|
||||
- `ebox-style--property-definitions` is also the single used-value projection truth. Non-default engine lowering lives in each property's `:engine-projection`; ECSS metadata exposes the derived `:engine-targets`, and only non-public engine fields may use the small internal exception table.
|
||||
- `ebox-framework-spi-capabilities` returns a fresh immutable v2 provider record declaring paired initial/update stage+rollback, combined participant ordering, same-object legacy reports, and `tp-transaction-protocol-v1+v2`. Ebox provides no selected port or consumer bootstrap; the existing v1 callback APIs remain available.
|
||||
- `owner-rerender` is broader than `span-patch`, which is broader than `paint-patch`.
|
||||
- Buffer coordinates belong to the generation that produced them and must be refreshed after mutation.
|
||||
- Grid uses the normal measurement and rendering pipeline. Native reflow may reject an ineligible tree and must fall back to Elisp without changing correctness.
|
||||
@ -127,6 +129,8 @@ make patch-plan-tests
|
||||
make patch-plan-performance
|
||||
make style-schema-tests
|
||||
make style-schema-performance
|
||||
make spi-tests
|
||||
make spi-performance
|
||||
make docs-contract-tests
|
||||
make ci-contract-tests
|
||||
make performance-evaluator
|
||||
|
||||
@ -35,11 +35,12 @@
|
||||
| `ebox-incremental.el` | runtime、snapshot、dirty 规划、owner 提升、纯 commit 准备和报告。 |
|
||||
| `ebox-dsl.el` | 数据型 `.ebox` form,以及向公共节点的 lowering。 |
|
||||
| `ebox-selector.el` | 把 CSS-like 字符串解析为 ECSS structured selector AST,利用索引缩小候选,并返回 tree/runtime query handle。 |
|
||||
| `ebox-spi.el` | Additive immutable framework SPI v2 provider 及 initial/update operation descriptor。 |
|
||||
| `ebox-native-reflow.el` | 可选 native 模块加载/构建、ABI 校验、受限 session 和 Elisp fallback。 |
|
||||
|
||||
本包有意不包含应用 Component、UI control、响应式 data 或 playground 实现;它们属于同级包。历史应用性能记录器和 native reflow 评估器也不属于独立 Ebox 的发布边界;Ebox 只保留 native 模块本身、Rust 构建输入和可重复的构建检查。
|
||||
|
||||
active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-core-render-tests.el`、`tests/ebox-state-contract-tests.el`、`tests/ebox-layout-boundary-tests.el`、`tests/ebox-patch-plan-tests.el`、`tests/ebox-style-schema-tests.el`、`tests/ebox-child-range-tests.el`、`tests/ebox-grid-tests.el`、`tests/ebox-commit-tests.el`、`tests/ebox-surface-tests.el`、`tests/ebox-dsl-tests.el`、`tests/ebox-flex-tests.el`、`tests/ebox-selector-tests.el`、`tests/ebox-package-tests.el`、`tests/ebox-visual-check-tests.el`、`tests/ebox-docs-contract-tests.el`、`tests/ebox-ci-contract-tests.el`、`native/Cargo.toml`、`native/Cargo.lock`、`native/build.rs`、`native/vendor/emacs-30/emacs-module.h`、`native/src/lib.rs`、`native/src/layout.rs`、`native/c/ebox_module.c`、`scripts/ebox-package-lint.el`、`scripts/ebox-visual-check.el` 和 `scripts/ebox-performance-evaluator.el`。
|
||||
active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-core-render-tests.el`、`tests/ebox-state-contract-tests.el`、`tests/ebox-layout-boundary-tests.el`、`tests/ebox-patch-plan-tests.el`、`tests/ebox-style-schema-tests.el`、`tests/ebox-spi-tests.el`、`tests/ebox-child-range-tests.el`、`tests/ebox-grid-tests.el`、`tests/ebox-commit-tests.el`、`tests/ebox-surface-tests.el`、`tests/ebox-dsl-tests.el`、`tests/ebox-flex-tests.el`、`tests/ebox-selector-tests.el`、`tests/ebox-package-tests.el`、`tests/ebox-visual-check-tests.el`、`tests/ebox-docs-contract-tests.el`、`tests/ebox-ci-contract-tests.el`、`native/Cargo.toml`、`native/Cargo.lock`、`native/build.rs`、`native/vendor/emacs-30/emacs-module.h`、`native/src/lib.rs`、`native/src/layout.rs`、`native/c/ebox_module.c`、`scripts/ebox-package-lint.el`、`scripts/ebox-visual-check.el` 和 `scripts/ebox-performance-evaluator.el`。
|
||||
|
||||
## 运行时模型
|
||||
|
||||
@ -89,6 +90,7 @@ active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-cor
|
||||
- `ebox-layout.el` 不再 load 或调用 `ebox-surface.el`/TP。candidate isolation、继承 cascade 判定和一次性 materialization 只能通过 `ebox-render-context.el` 拥有的已校验 port 进入 layout;`ebox.el` 在两侧加载后接线 surface 实现。
|
||||
- `ebox-patch-plan.el` 只消费 tentative operation plist 与不可变 generation parent table,并返回 deterministic plan artifact;所有 live fact 准备仍由 incremental adapter 拥有。E3 默认使用 pure route,同时保留显式 `legacy` 与执行等价检查的 `shadow` route;三条 route 都不能发布。
|
||||
- `ebox-style--property-definitions` 同时也是 used-value projection 的唯一真相源。非默认 engine lowering 写在每个 property 的 `:engine-projection` 中;ECSS metadata 暴露派生的 `:engine-targets`,只有非公共 engine 字段可以进入小型 internal exception table。
|
||||
- `ebox-framework-spi-capabilities` 返回 fresh immutable v2 provider record,声明 paired initial/update stage+rollback、combined participant order、same-object legacy report 以及 `tp-transaction-protocol-v1+v2`。Ebox 不创建 selected port 或 consumer bootstrap;现有 v1 callback API 保留。
|
||||
- `owner-rerender` 范围大于 `span-patch`,`span-patch` 大于 `paint-patch`。
|
||||
- Buffer 坐标属于生成它的 generation,变更后必须重新获取。
|
||||
- Grid 使用普通测量与渲染流水线;native reflow 可以拒绝不适合的树并回退到 Elisp,正确性不变。
|
||||
@ -125,6 +127,8 @@ make patch-plan-tests
|
||||
make patch-plan-performance
|
||||
make style-schema-tests
|
||||
make style-schema-performance
|
||||
make spi-tests
|
||||
make spi-performance
|
||||
make docs-contract-tests
|
||||
make ci-contract-tests
|
||||
make performance-evaluator
|
||||
|
||||
259
ebox-spi.el
Normal file
259
ebox-spi.el
Normal file
@ -0,0 +1,259 @@
|
||||
;;; ebox-spi.el --- Versioned Ebox framework provider -*- lexical-binding: t; -*-
|
||||
|
||||
;; SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;; Additive provider-side Ebox framework SPI v2. This module publishes an
|
||||
;; immutable capability snapshot and delegates initial/update execution to the
|
||||
;; existing combined Ebox participant. It does not select a consumer port,
|
||||
;; probe a framework package, or remove the v1 callback surface.
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
(require 'ebox-canonical)
|
||||
(require 'ebox-surface)
|
||||
(require 'tp-transaction)
|
||||
|
||||
(declare-function ebox--render-to-buffer-internal
|
||||
"ebox" (buffer-or-name input options &optional participant))
|
||||
(declare-function ebox-commit
|
||||
"ebox"
|
||||
(buffer-or-name next-root &optional framework-stage
|
||||
framework-rollback))
|
||||
(declare-function tp-transaction-active-p "tp-reactive" ())
|
||||
|
||||
(define-error 'ebox-framework-spi-provider-error
|
||||
"Invalid Ebox framework SPI provider")
|
||||
|
||||
(defconst ebox-framework-spi-version 2
|
||||
"Version of the additive Ebox framework provider SPI.")
|
||||
|
||||
(defconst ebox-framework-spi-schema-version 'ebox-framework-spi-schema/v2
|
||||
"Schema identity for Ebox framework provider records.")
|
||||
|
||||
(defconst ebox-framework-spi--required-capabilities
|
||||
'(initial-paired-stage-rollback
|
||||
update-paired-stage-rollback
|
||||
combined-participant-ordering
|
||||
same-object-legacy-report)
|
||||
"Capabilities guaranteed by every Ebox framework SPI v2 provider record.")
|
||||
|
||||
(defconst ebox-framework-spi--stage-order
|
||||
'(ebox-mirror/native framework-stage)
|
||||
"Owner order inside the combined Ebox participant stage.")
|
||||
|
||||
(defconst ebox-framework-spi--rollback-order
|
||||
'(framework-rollback ebox-mirror/native tp)
|
||||
"Rollback order exposed by the combined Ebox participant contract.")
|
||||
|
||||
(cl-defstruct
|
||||
(ebox-framework-spi-operation
|
||||
(:constructor ebox-framework-spi--make-operation)
|
||||
(:conc-name ebox-framework-spi--operation-))
|
||||
"One immutable operation descriptor in the framework SPI."
|
||||
(kind nil :read-only t)
|
||||
(function nil :read-only t)
|
||||
(argument-schema nil :read-only t)
|
||||
(result-schema nil :read-only t)
|
||||
(paired-stage-rollback-p nil :read-only t))
|
||||
|
||||
(cl-defstruct
|
||||
(ebox-framework-spi-provider
|
||||
(:constructor ebox-framework-spi--make-provider)
|
||||
(:conc-name ebox-framework-spi--provider-))
|
||||
"One immutable Ebox framework SPI capability record."
|
||||
(spi-version nil :read-only t)
|
||||
(schema-version nil :read-only t)
|
||||
(capabilities nil :read-only t)
|
||||
(tp-protocol nil :read-only t)
|
||||
(stage-order nil :read-only t)
|
||||
(rollback-order nil :read-only t)
|
||||
(report-semantics nil :read-only t)
|
||||
(initial-operation nil :read-only t)
|
||||
(update-operation nil :read-only t))
|
||||
|
||||
(defun ebox-framework-spi-operation-kind (operation)
|
||||
"Return OPERATION's immutable kind."
|
||||
(ebox-framework-spi--operation-kind operation))
|
||||
|
||||
(defun ebox-framework-spi-operation-function (operation)
|
||||
"Return OPERATION's immutable function symbol."
|
||||
(ebox-framework-spi--operation-function operation))
|
||||
|
||||
(defun ebox-framework-spi-operation-argument-schema (operation)
|
||||
"Return a defensive copy of OPERATION's argument schema."
|
||||
(copy-sequence (ebox-framework-spi--operation-argument-schema operation)))
|
||||
|
||||
(defun ebox-framework-spi-operation-result-schema (operation)
|
||||
"Return OPERATION's immutable result schema."
|
||||
(ebox-framework-spi--operation-result-schema operation))
|
||||
|
||||
(defun ebox-framework-spi-operation-paired-stage-rollback-p (operation)
|
||||
"Return non-nil when OPERATION requires paired stage and rollback."
|
||||
(ebox-framework-spi--operation-paired-stage-rollback-p operation))
|
||||
|
||||
(defun ebox-framework-spi-provider-spi-version (provider)
|
||||
"Return PROVIDER's immutable SPI version."
|
||||
(ebox-framework-spi--provider-spi-version provider))
|
||||
|
||||
(defun ebox-framework-spi-provider-schema-version (provider)
|
||||
"Return PROVIDER's immutable schema version."
|
||||
(ebox-framework-spi--provider-schema-version provider))
|
||||
|
||||
(defun ebox-framework-spi-provider-capabilities (provider)
|
||||
"Return a defensive copy of PROVIDER's capabilities."
|
||||
(copy-sequence (ebox-framework-spi--provider-capabilities provider)))
|
||||
|
||||
(defun ebox-framework-spi-provider-tp-protocol (provider)
|
||||
"Return PROVIDER's immutable TP protocol."
|
||||
(ebox-framework-spi--provider-tp-protocol provider))
|
||||
|
||||
(defun ebox-framework-spi-provider-stage-order (provider)
|
||||
"Return a defensive copy of PROVIDER's participant stage order."
|
||||
(copy-sequence (ebox-framework-spi--provider-stage-order provider)))
|
||||
|
||||
(defun ebox-framework-spi-provider-rollback-order (provider)
|
||||
"Return a defensive copy of PROVIDER's participant rollback order."
|
||||
(copy-sequence (ebox-framework-spi--provider-rollback-order provider)))
|
||||
|
||||
(defun ebox-framework-spi-provider-report-semantics (provider)
|
||||
"Return PROVIDER's immutable report semantics."
|
||||
(ebox-framework-spi--provider-report-semantics provider))
|
||||
|
||||
(defun ebox-framework-spi-provider-initial-operation (provider)
|
||||
"Return PROVIDER's immutable initial operation descriptor."
|
||||
(ebox-framework-spi--provider-initial-operation provider))
|
||||
|
||||
(defun ebox-framework-spi-provider-update-operation (provider)
|
||||
"Return PROVIDER's immutable update operation descriptor."
|
||||
(ebox-framework-spi--provider-update-operation provider))
|
||||
|
||||
(defun ebox-framework-spi--validate-callback-pair
|
||||
(framework-stage framework-rollback)
|
||||
"Validate FRAMEWORK-STAGE and FRAMEWORK-ROLLBACK as a required pair."
|
||||
(unless (functionp framework-stage)
|
||||
(signal 'wrong-type-argument (list 'functionp framework-stage)))
|
||||
(unless (functionp framework-rollback)
|
||||
(signal 'wrong-type-argument (list 'functionp framework-rollback)))
|
||||
t)
|
||||
|
||||
(defun ebox-framework-spi-initial
|
||||
(buffer-or-name input framework-stage framework-rollback)
|
||||
"Initially mount INPUT and run the paired framework callbacks.
|
||||
|
||||
FRAMEWORK-STAGE runs after provisional TP state and Ebox mirrors/native state
|
||||
agree. FRAMEWORK-ROLLBACK receives the same legacy report on any later
|
||||
transaction failure. Return that same report object after completion."
|
||||
(ebox-framework-spi--validate-callback-pair
|
||||
framework-stage framework-rollback)
|
||||
(unless (ebox-canonical-input-p input)
|
||||
(signal 'wrong-type-argument (list 'ebox-canonical-input-p input)))
|
||||
(when (tp-transaction-active-p)
|
||||
(error "Ebox SPI initial operation cannot join an outer TP transaction"))
|
||||
(let ((buffer (get-buffer-create buffer-or-name)))
|
||||
(when (ebox-surface-buffer-mounted-p buffer)
|
||||
(error "Ebox SPI initial operation requires an unmounted buffer"))
|
||||
(let ((participant
|
||||
(ebox-surface--make-framework-participant
|
||||
:publish framework-stage
|
||||
:rollback framework-rollback
|
||||
:state 'unpublished
|
||||
:diagnostics nil)))
|
||||
(ebox--render-to-buffer-internal buffer input nil participant))))
|
||||
|
||||
(defun ebox-framework-spi-update
|
||||
(buffer-or-name input framework-stage framework-rollback)
|
||||
"Update BUFFER-OR-NAME from INPUT through paired framework callbacks.
|
||||
Return the same completed legacy report object observed by FRAMEWORK-STAGE."
|
||||
(ebox-framework-spi--validate-callback-pair
|
||||
framework-stage framework-rollback)
|
||||
(ebox-commit buffer-or-name input framework-stage framework-rollback))
|
||||
|
||||
(defun ebox-framework-spi--operation
|
||||
(kind function argument-schema)
|
||||
"Return a fresh KIND operation descriptor for FUNCTION and ARGUMENT-SCHEMA."
|
||||
(ebox-framework-spi--make-operation
|
||||
:kind kind
|
||||
:function function
|
||||
:argument-schema (copy-sequence argument-schema)
|
||||
:result-schema 'ebox-legacy-report/same-object
|
||||
:paired-stage-rollback-p t))
|
||||
|
||||
(defun ebox-framework-spi--validate-operation
|
||||
(operation kind function argument-schema)
|
||||
"Validate OPERATION against KIND, FUNCTION, and ARGUMENT-SCHEMA."
|
||||
(unless (and (ebox-framework-spi-operation-p operation)
|
||||
(eq (ebox-framework-spi--operation-kind operation) kind)
|
||||
(eq (ebox-framework-spi--operation-function operation) function)
|
||||
(equal (ebox-framework-spi--operation-argument-schema operation)
|
||||
argument-schema)
|
||||
(eq (ebox-framework-spi--operation-result-schema operation)
|
||||
'ebox-legacy-report/same-object)
|
||||
(eq (ebox-framework-spi--operation-paired-stage-rollback-p
|
||||
operation)
|
||||
t))
|
||||
(signal 'ebox-framework-spi-provider-error
|
||||
(list :malformed-operation kind operation)))
|
||||
t)
|
||||
|
||||
(defun ebox-framework-spi-validate-provider (provider)
|
||||
"Validate PROVIDER as one complete Ebox framework SPI v2 record."
|
||||
(unless (ebox-framework-spi-provider-p provider)
|
||||
(signal 'ebox-framework-spi-provider-error
|
||||
(list :malformed-provider provider)))
|
||||
(unless (and (eql (ebox-framework-spi--provider-spi-version provider)
|
||||
ebox-framework-spi-version)
|
||||
(eq (ebox-framework-spi--provider-schema-version provider)
|
||||
ebox-framework-spi-schema-version)
|
||||
(equal (ebox-framework-spi--provider-capabilities provider)
|
||||
ebox-framework-spi--required-capabilities)
|
||||
(eq (ebox-framework-spi--provider-tp-protocol provider)
|
||||
tp-transaction-protocol)
|
||||
(equal (ebox-framework-spi--provider-stage-order provider)
|
||||
ebox-framework-spi--stage-order)
|
||||
(equal (ebox-framework-spi--provider-rollback-order provider)
|
||||
ebox-framework-spi--rollback-order)
|
||||
(eq (ebox-framework-spi--provider-report-semantics provider)
|
||||
'same-object-legacy-report))
|
||||
(signal 'ebox-framework-spi-provider-error
|
||||
(list :malformed-provider-schema provider)))
|
||||
(ebox-framework-spi--validate-operation
|
||||
(ebox-framework-spi--provider-initial-operation provider)
|
||||
'initial 'ebox-framework-spi-initial
|
||||
'(buffer canonical-input framework-stage framework-rollback))
|
||||
(ebox-framework-spi--validate-operation
|
||||
(ebox-framework-spi--provider-update-operation provider)
|
||||
'update 'ebox-framework-spi-update
|
||||
'(buffer canonical-input-or-candidate framework-stage framework-rollback))
|
||||
t)
|
||||
|
||||
(defun ebox-framework-spi-capabilities ()
|
||||
"Return a fresh immutable Ebox framework SPI v2 provider record."
|
||||
(let ((provider
|
||||
(ebox-framework-spi--make-provider
|
||||
:spi-version ebox-framework-spi-version
|
||||
:schema-version ebox-framework-spi-schema-version
|
||||
:capabilities
|
||||
(copy-sequence ebox-framework-spi--required-capabilities)
|
||||
:tp-protocol tp-transaction-protocol
|
||||
:stage-order (copy-sequence ebox-framework-spi--stage-order)
|
||||
:rollback-order (copy-sequence ebox-framework-spi--rollback-order)
|
||||
:report-semantics 'same-object-legacy-report
|
||||
:initial-operation
|
||||
(ebox-framework-spi--operation
|
||||
'initial 'ebox-framework-spi-initial
|
||||
'(buffer canonical-input framework-stage framework-rollback))
|
||||
:update-operation
|
||||
(ebox-framework-spi--operation
|
||||
'update 'ebox-framework-spi-update
|
||||
'(buffer canonical-input-or-candidate
|
||||
framework-stage framework-rollback)))))
|
||||
(ebox-framework-spi-validate-provider provider)
|
||||
provider))
|
||||
|
||||
(provide 'ebox-framework-spi-v2)
|
||||
(provide 'ebox-spi)
|
||||
|
||||
;;; ebox-spi.el ends here
|
||||
40
ebox.el
40
ebox.el
@ -32,7 +32,7 @@
|
||||
"ebox-flex.el" "ebox-grid.el" "ebox-canonical.el"
|
||||
"ebox-buffer-backend.el" "ebox-patch-plan.el" "ebox-incremental.el"
|
||||
"ebox-native-commit.el" "ebox-surface.el" "ebox-viewport.el"
|
||||
"ebox-dsl.el" "ebox-selector.el" "ebox.el"
|
||||
"ebox-dsl.el" "ebox-selector.el" "ebox-spi.el" "ebox.el"
|
||||
"ebox-native-reflow.el")
|
||||
"Active Ebox Lisp sources compiled by `ebox-byte-compile'.")
|
||||
|
||||
@ -4384,13 +4384,9 @@ Examples:
|
||||
(signal 'wrong-type-argument (list 'functionp observer)))
|
||||
(cons present observer)))
|
||||
|
||||
;;;###autoload
|
||||
(defun ebox-render-to-buffer (buffer-or-name input &optional options)
|
||||
"Render canonical INPUT into BUFFER-OR-NAME and return the buffer.
|
||||
INPUT atomically carries one typed Text/Box root and its source generation.
|
||||
OPTIONS accepts only `:observer'. Its function receives the buffer and flat
|
||||
TP/Ebox reports after accepted initial publication and later commits."
|
||||
(declare (indent 1))
|
||||
(defun ebox--render-to-buffer-internal
|
||||
(buffer-or-name input options &optional framework-participant)
|
||||
"Render INPUT into BUFFER-OR-NAME with OPTIONS and FRAMEWORK-PARTICIPANT."
|
||||
(unless (ebox-canonical-input-p input)
|
||||
(signal 'wrong-type-argument (list 'ebox-canonical-input-p input)))
|
||||
(let* ((node (ebox-canonical-input--single-root
|
||||
@ -4398,6 +4394,13 @@ TP/Ebox reports after accepted initial publication and later commits."
|
||||
(source-base-index (ebox-canonical-input--source-index input))
|
||||
(observer-option (ebox--render-observer-option options))
|
||||
(buffer (get-buffer-create buffer-or-name))
|
||||
(report-base
|
||||
(and framework-participant
|
||||
'(:strategy initial-mount
|
||||
:constraint-source declarative
|
||||
:publication-scope full-root
|
||||
:dirty-count 1 :patch-count 1
|
||||
:patch-ops (initial-mount))))
|
||||
(old-observer (ebox-surface-buffer-observer buffer))
|
||||
(old-bridge (with-current-buffer buffer ebox-surface--tp-observer))
|
||||
success result)
|
||||
@ -4411,9 +4414,10 @@ TP/Ebox reports after accepted initial publication and later commits."
|
||||
(lambda ()
|
||||
(ebox--with-render-gc
|
||||
(ebox-surface-mount-buffer
|
||||
buffer node nil nil nil
|
||||
buffer node report-base nil nil
|
||||
(and source-base-index
|
||||
(list :source-base-index source-base-index)))
|
||||
(list :source-base-index source-base-index))
|
||||
framework-participant)
|
||||
(with-current-buffer buffer
|
||||
(goto-char (point-min))
|
||||
(add-hook 'kill-buffer-hook
|
||||
@ -4423,12 +4427,24 @@ TP/Ebox reports after accepted initial publication and later commits."
|
||||
(ebox--schedule-buffer-runtime-prewarm buffer nil nil)
|
||||
buffer))))
|
||||
success t)
|
||||
result)
|
||||
(if framework-participant
|
||||
(ebox-surface--framework-participant-report
|
||||
framework-participant)
|
||||
result))
|
||||
(when (and (not success) (car observer-option))
|
||||
(ebox-surface-set-buffer-observer buffer old-observer)
|
||||
(unless old-bridge
|
||||
(ebox-surface-cleanup-buffer-observer buffer))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun ebox-render-to-buffer (buffer-or-name input &optional options)
|
||||
"Render canonical INPUT into BUFFER-OR-NAME and return the buffer.
|
||||
INPUT atomically carries one typed Text/Box root and its source generation.
|
||||
OPTIONS accepts only `:observer'. Its function receives the buffer and flat
|
||||
TP/Ebox reports after accepted initial publication and later commits."
|
||||
(declare (indent 1))
|
||||
(ebox--render-to-buffer-internal buffer-or-name input options))
|
||||
|
||||
(defun ebox--host-ref-buffer (buffer-or-name)
|
||||
"Return the live buffer named by BUFFER-OR-NAME, or nil."
|
||||
(let ((buffer (and buffer-or-name (get-buffer buffer-or-name))))
|
||||
@ -4845,6 +4861,8 @@ and are documented separately in the public API reference.")
|
||||
viewport-reflow)
|
||||
"Stable high-level Ebox capability families used by conformance tooling.")
|
||||
|
||||
(require 'ebox-spi)
|
||||
|
||||
(provide 'ebox)
|
||||
;;; ============================================================
|
||||
;;; Public API Summary
|
||||
|
||||
@ -25,6 +25,8 @@
|
||||
"patch-plan-performance:"
|
||||
"style-schema-tests:"
|
||||
"style-schema-performance:"
|
||||
"spi-tests:"
|
||||
"spi-performance:"
|
||||
"docs-contract-tests:"
|
||||
"ci-contract-tests:" "native-rust-tests:"
|
||||
"native-build:" "performance-evaluator:" "diff-check:"))
|
||||
@ -40,6 +42,7 @@
|
||||
(should (string-match-p "ebox-byte-compile" source))
|
||||
(should (string-match-p "ebox-native-reflow.el" ebox))
|
||||
(should (string-match-p "ebox-patch-plan.el" ebox))
|
||||
(should (string-match-p "ebox-spi.el" ebox))
|
||||
(should (string-match-p "ebox-child-range.el" ebox))
|
||||
(should-not (string-match-p "ebox-playground.el" ebox))))
|
||||
|
||||
|
||||
@ -29,12 +29,13 @@
|
||||
"ebox-measure.el" "ebox-fragment.el" "ebox-render-context.el"
|
||||
"ebox-layout.el" "ebox-flex.el" "ebox-grid.el"
|
||||
"ebox-buffer-backend.el" "ebox-patch-plan.el" "ebox-incremental.el"
|
||||
"ebox-surface.el" "ebox-dsl.el"
|
||||
"ebox-surface.el" "ebox-dsl.el" "ebox-spi.el"
|
||||
"ebox-selector.el" "ebox-native-reflow.el"
|
||||
"tests/ebox-core-render-tests.el" "tests/ebox-state-contract-tests.el"
|
||||
"tests/ebox-layout-boundary-tests.el"
|
||||
"tests/ebox-patch-plan-tests.el"
|
||||
"tests/ebox-style-schema-tests.el"
|
||||
"tests/ebox-spi-tests.el"
|
||||
"tests/ebox-child-range-tests.el" "tests/ebox-grid-tests.el"
|
||||
"tests/ebox-commit-tests.el" "tests/ebox-surface-tests.el"
|
||||
"tests/ebox-dsl-tests.el" "tests/ebox-flex-tests.el"
|
||||
@ -55,6 +56,7 @@
|
||||
"layout-boundary-tests" "layout-boundary-performance"
|
||||
"patch-plan-tests" "patch-plan-performance"
|
||||
"style-schema-tests" "style-schema-performance"
|
||||
"spi-tests" "spi-performance"
|
||||
"docs-contract-tests" "ci-contract-tests" "performance-evaluator"
|
||||
"visual-check"
|
||||
"native-rust-tests" "native-build" "package-lint" "diff-check")
|
||||
|
||||
@ -59,7 +59,8 @@
|
||||
ebox-buffer-backend ebox-patch-plan
|
||||
ebox-incremental
|
||||
ebox-surface ebox-viewport ebox-dsl
|
||||
ebox-selector))
|
||||
ebox-selector ebox-spi
|
||||
ebox-framework-spi-v2))
|
||||
(should (featurep feature))))
|
||||
|
||||
(ert-deftest ebox-canonical-module-loads-typed-port-without-facade ()
|
||||
@ -142,7 +143,7 @@
|
||||
"ebox-buffer-backend.el" "ebox-patch-plan.el"
|
||||
"ebox-incremental.el"
|
||||
"ebox-native-commit.el" "ebox-surface.el" "ebox-viewport.el"
|
||||
"ebox-dsl.el" "ebox-selector.el" "ebox.el"
|
||||
"ebox-dsl.el" "ebox-selector.el" "ebox-spi.el" "ebox.el"
|
||||
"ebox-native-reflow.el"))))
|
||||
|
||||
(ert-deftest ebox-surface-depends-only-on-public-tp-api ()
|
||||
|
||||
381
tests/ebox-spi-tests.el
Normal file
381
tests/ebox-spi-tests.el
Normal file
@ -0,0 +1,381 @@
|
||||
;;; ebox-spi-tests.el --- M2a framework provider SPI gates -*- lexical-binding: t; -*-
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'ert)
|
||||
(require 'cl-lib)
|
||||
(require 'benchmark)
|
||||
(require 'ebox)
|
||||
(require 'ebox-spi)
|
||||
(require 'ebox-fixtures)
|
||||
|
||||
(defconst ebox-spi-test--root
|
||||
(expand-file-name ".." (file-name-directory (or load-file-name buffer-file-name)))
|
||||
"Repository root used by provider SPI tests.")
|
||||
|
||||
(defun ebox-spi-test--source (file)
|
||||
"Return repository FILE as text."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents (expand-file-name file ebox-spi-test--root))
|
||||
(buffer-string)))
|
||||
|
||||
(defun ebox-spi-test--reset-runtime-state ()
|
||||
"Reset identities and compatibility mirrors used by SPI tests."
|
||||
(setq ebox--region-id-counter 0
|
||||
ebox--runtime-node-id-counter 0)
|
||||
(dolist (table (list ebox--region-box-table
|
||||
ebox--buffer-render-state-table
|
||||
ebox--scroll-global-state))
|
||||
(when (hash-table-p table) (clrhash table))))
|
||||
|
||||
(ert-deftest ebox-spi-provider-schema-is-complete-and-defensive ()
|
||||
"The additive v2 provider exposes one complete immutable snapshot."
|
||||
(let* ((first (ebox-framework-spi-capabilities))
|
||||
(second (ebox-framework-spi-capabilities))
|
||||
(initial (ebox-framework-spi-provider-initial-operation first))
|
||||
(update (ebox-framework-spi-provider-update-operation first)))
|
||||
(should (featurep 'ebox-framework-spi-v2))
|
||||
(should (ebox-framework-spi-validate-provider first))
|
||||
(should-not (eq first second))
|
||||
(should (= (ebox-framework-spi-provider-spi-version first) 2))
|
||||
(should (eq (ebox-framework-spi-provider-schema-version first)
|
||||
'ebox-framework-spi-schema/v2))
|
||||
(should (eq (ebox-framework-spi-provider-tp-protocol first)
|
||||
'tp-transaction-protocol-v1+v2))
|
||||
(should
|
||||
(equal (ebox-framework-spi-provider-capabilities first)
|
||||
'(initial-paired-stage-rollback
|
||||
update-paired-stage-rollback
|
||||
combined-participant-ordering
|
||||
same-object-legacy-report)))
|
||||
(should
|
||||
(equal (ebox-framework-spi-provider-stage-order first)
|
||||
'(ebox-mirror/native framework-stage)))
|
||||
(should
|
||||
(equal (ebox-framework-spi-provider-rollback-order first)
|
||||
'(framework-rollback ebox-mirror/native tp)))
|
||||
(should (eq (ebox-framework-spi-provider-report-semantics first)
|
||||
'same-object-legacy-report))
|
||||
(should (eq (ebox-framework-spi-operation-kind initial) 'initial))
|
||||
(should (eq (ebox-framework-spi-operation-function initial)
|
||||
'ebox-framework-spi-initial))
|
||||
(should
|
||||
(equal (ebox-framework-spi-operation-argument-schema initial)
|
||||
'(buffer canonical-input framework-stage framework-rollback)))
|
||||
(should (eq (ebox-framework-spi-operation-result-schema initial)
|
||||
'ebox-legacy-report/same-object))
|
||||
(should (eq (ebox-framework-spi-operation-paired-stage-rollback-p initial)
|
||||
t))
|
||||
(should (eq (ebox-framework-spi-operation-kind update) 'update))
|
||||
(should (eq (ebox-framework-spi-operation-function update)
|
||||
'ebox-framework-spi-update))
|
||||
(should
|
||||
(equal (ebox-framework-spi-operation-argument-schema update)
|
||||
'(buffer canonical-input-or-candidate
|
||||
framework-stage framework-rollback)))
|
||||
(should (eq (ebox-framework-spi-operation-result-schema update)
|
||||
'ebox-legacy-report/same-object))
|
||||
(should (eq (ebox-framework-spi-operation-paired-stage-rollback-p update)
|
||||
t))
|
||||
(should-error
|
||||
(eval
|
||||
`(setf (ebox-framework-spi-provider-spi-version ',first) 3)))
|
||||
(should-error
|
||||
(eval
|
||||
`(setf (ebox-framework-spi-operation-kind ',initial) 'broken)))
|
||||
(let ((capabilities (ebox-framework-spi-provider-capabilities first))
|
||||
(stage-order (ebox-framework-spi-provider-stage-order first))
|
||||
(rollback-order (ebox-framework-spi-provider-rollback-order first))
|
||||
(arguments (ebox-framework-spi-operation-argument-schema initial)))
|
||||
(setcar capabilities 'mutated)
|
||||
(setcar stage-order 'mutated)
|
||||
(setcar rollback-order 'mutated)
|
||||
(setcar arguments 'mutated))
|
||||
(should (ebox-framework-spi-validate-provider first))
|
||||
(should (eq (car (ebox-framework-spi-provider-capabilities first))
|
||||
'initial-paired-stage-rollback))
|
||||
(should (eq (car (ebox-framework-spi-provider-stage-order first))
|
||||
'ebox-mirror/native))
|
||||
(should (eq (car (ebox-framework-spi-provider-rollback-order first))
|
||||
'framework-rollback))
|
||||
(should (eq (car (ebox-framework-spi-operation-argument-schema initial))
|
||||
'buffer))
|
||||
(let ((fresh (ebox-framework-spi-capabilities)))
|
||||
(should (eq (car (ebox-framework-spi-provider-capabilities fresh))
|
||||
'initial-paired-stage-rollback))
|
||||
(should (eq (car (ebox-framework-spi-operation-argument-schema
|
||||
(ebox-framework-spi-provider-initial-operation fresh)))
|
||||
'buffer)))))
|
||||
|
||||
(ert-deftest ebox-spi-provider-is-additive-and-does-not-bootstrap-consumers ()
|
||||
"E5 publishes no selected port, consumer probe, or v1 removal."
|
||||
(let ((source (ebox-spi-test--source "ebox-spi.el"))
|
||||
(facade (ebox-spi-test--source "ebox.el")))
|
||||
(dolist (pattern '("featurep" "fboundp" "selected-port"
|
||||
"etaf-renderer-port" "etaf-spi-bootstrap"))
|
||||
(should-not (string-match-p (regexp-quote pattern) source)))
|
||||
(should (string-match-p "(tp \"1.0.0\")" facade))
|
||||
(should (functionp #'ebox-render-to-buffer))
|
||||
(should (functionp #'ebox-commit))
|
||||
(should (functionp #'ebox-framework-spi-capabilities))))
|
||||
|
||||
(ert-deftest ebox-spi-initial-and-update-share-their-legacy-report ()
|
||||
"Both operation descriptors preserve paired callbacks and report identity."
|
||||
(ebox-spi-test--reset-runtime-state)
|
||||
(let* ((provider (ebox-framework-spi-capabilities))
|
||||
(initial-function
|
||||
(ebox-framework-spi-operation-function
|
||||
(ebox-framework-spi-provider-initial-operation provider)))
|
||||
(update-function
|
||||
(ebox-framework-spi-operation-function
|
||||
(ebox-framework-spi-provider-update-operation provider)))
|
||||
(buffer (generate-new-buffer " *ebox-m2a-e5-success*"))
|
||||
initial-seen update-seen trace)
|
||||
(unwind-protect
|
||||
(let ((initial-report
|
||||
(funcall
|
||||
initial-function buffer
|
||||
(ebox-test-box :key 'root (ebox-test-text "initial")
|
||||
:width '(96))
|
||||
(lambda (report)
|
||||
(setq initial-seen report)
|
||||
(push 'initial-stage trace)
|
||||
(should (eq (plist-get report :framework-participant-state)
|
||||
'published))
|
||||
(let ((surface (ebox-surface--live-buffer-surface buffer)))
|
||||
(should surface)
|
||||
(should
|
||||
(eq (gethash buffer ebox--buffer-render-state-table)
|
||||
(tp-surface-client-state surface))))
|
||||
(plist-put report :framework-token 'initial-token))
|
||||
(lambda (_report) (push 'initial-rollback trace)))))
|
||||
(should (eq initial-report initial-seen))
|
||||
(should (eq (plist-get initial-report :framework-participant-state)
|
||||
'completed))
|
||||
(should (eq (plist-get initial-report :framework-token)
|
||||
'initial-token))
|
||||
(should (eq (plist-get initial-report :strategy) 'initial-mount))
|
||||
(should (equal trace '(initial-stage)))
|
||||
(setq trace nil)
|
||||
(let ((update-report
|
||||
(funcall
|
||||
update-function buffer
|
||||
(ebox-test-box :key 'root (ebox-test-text "updated")
|
||||
:width '(96))
|
||||
(lambda (report)
|
||||
(setq update-seen report)
|
||||
(push 'update-stage trace)
|
||||
(plist-put report :framework-token 'update-token))
|
||||
(lambda (_report) (push 'update-rollback trace)))))
|
||||
(should (eq update-report update-seen))
|
||||
(should (eq (plist-get update-report
|
||||
:framework-participant-state)
|
||||
'completed))
|
||||
(should (eq (plist-get update-report :framework-token)
|
||||
'update-token))
|
||||
(should (equal trace '(update-stage)))
|
||||
(should (string-match-p
|
||||
"updated"
|
||||
(with-current-buffer buffer
|
||||
(buffer-substring-no-properties
|
||||
(point-min) (point-max)))))))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||
|
||||
(ert-deftest ebox-spi-initial-rolls-the-same-report-back ()
|
||||
"A later TP failure rolls initial framework and Ebox state back in order."
|
||||
(ebox-spi-test--reset-runtime-state)
|
||||
(let ((buffer (generate-new-buffer " *ebox-m2a-e5-initial-fault*"))
|
||||
(original
|
||||
(symbol-function 'tp--run-transaction-precommit-functions))
|
||||
stage-report rollback-report trace failure)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(with-current-buffer buffer (insert "sentinel"))
|
||||
(cl-letf
|
||||
(((symbol-function 'tp--run-transaction-precommit-functions)
|
||||
(lambda ()
|
||||
(funcall original)
|
||||
(error "E5 initial later failure"))))
|
||||
(setq failure
|
||||
(condition-case condition
|
||||
(ebox-framework-spi-initial
|
||||
buffer
|
||||
(ebox-test-box
|
||||
:key 'root :width '(96) :height 1 :overflow 'scroll
|
||||
(ebox-test-text "zero\none\ntwo\nthree"))
|
||||
(lambda (report)
|
||||
(setq stage-report report)
|
||||
(push 'stage trace)
|
||||
(should (> (hash-table-count
|
||||
ebox--scroll-global-state)
|
||||
0)))
|
||||
(lambda (report)
|
||||
(setq rollback-report report)
|
||||
(push 'rollback trace)))
|
||||
(error condition))))
|
||||
(should (equal (cadr failure) "E5 initial later failure"))
|
||||
(should (equal trace '(rollback stage)))
|
||||
(should (eq stage-report rollback-report))
|
||||
(should (eq (plist-get stage-report :framework-participant-state)
|
||||
'rolled-back))
|
||||
(should-not (ebox-surface-buffer-mounted-p buffer))
|
||||
(should-not (gethash buffer ebox--buffer-render-state-table))
|
||||
(should (= (hash-table-count ebox--scroll-global-state) 0))
|
||||
(should
|
||||
(equal (with-current-buffer buffer (buffer-string)) "sentinel")))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||
|
||||
(ert-deftest ebox-spi-initial-stage-failure-runs-its-pair ()
|
||||
"An initial framework-stage error rolls back the same report and mount."
|
||||
(ebox-spi-test--reset-runtime-state)
|
||||
(let ((buffer (generate-new-buffer " *ebox-m2a-e5-stage-fault*"))
|
||||
stage-report rollback-report trace)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(should-error
|
||||
(ebox-framework-spi-initial
|
||||
buffer
|
||||
(ebox-test-box :key 'root (ebox-test-text "rejected"))
|
||||
(lambda (report)
|
||||
(setq stage-report report)
|
||||
(push 'stage trace)
|
||||
(error "E5 stage callback failure"))
|
||||
(lambda (report)
|
||||
(setq rollback-report report)
|
||||
(push 'rollback trace))))
|
||||
(should (equal trace '(rollback stage)))
|
||||
(should (eq stage-report rollback-report))
|
||||
(should (eq (plist-get stage-report :framework-participant-state)
|
||||
'rolled-back))
|
||||
(should-not (ebox-surface-buffer-mounted-p buffer))
|
||||
(should-not (gethash buffer ebox--buffer-render-state-table))
|
||||
(should (equal (with-current-buffer buffer (buffer-string)) "")))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||
|
||||
(ert-deftest ebox-spi-update-rolls-the-same-report-back ()
|
||||
"A later TP failure restores update state through the v2 operation."
|
||||
(ebox-spi-test--reset-runtime-state)
|
||||
(let ((buffer (generate-new-buffer " *ebox-m2a-e5-update-fault*"))
|
||||
(original
|
||||
(symbol-function 'tp--run-transaction-precommit-functions))
|
||||
stage-report rollback-report trace)
|
||||
(unwind-protect
|
||||
(progn
|
||||
(ebox-render-to-buffer
|
||||
buffer (ebox-test-box :key 'root (ebox-test-text "old")))
|
||||
(let* ((surface (ebox-surface--live-buffer-surface buffer))
|
||||
(state (tp-surface-client-state surface))
|
||||
(revision (tp-surface-revision surface))
|
||||
(contents
|
||||
(with-current-buffer buffer
|
||||
(buffer-substring (point-min) (point-max)))))
|
||||
(cl-letf
|
||||
(((symbol-function 'tp--run-transaction-precommit-functions)
|
||||
(lambda ()
|
||||
(funcall original)
|
||||
(error "E5 update later failure"))))
|
||||
(should-error
|
||||
(ebox-framework-spi-update
|
||||
buffer
|
||||
(ebox-test-box :key 'root (ebox-test-text "rejected"))
|
||||
(lambda (report)
|
||||
(setq stage-report report)
|
||||
(push 'stage trace))
|
||||
(lambda (report)
|
||||
(setq rollback-report report)
|
||||
(push 'rollback trace)))))
|
||||
(should (equal trace '(rollback stage)))
|
||||
(should (eq stage-report rollback-report))
|
||||
(should (eq (plist-get stage-report
|
||||
:framework-participant-state)
|
||||
'rolled-back))
|
||||
(should (eq (tp-surface-client-state surface) state))
|
||||
(should (= (tp-surface-revision surface) revision))
|
||||
(should
|
||||
(equal-including-properties
|
||||
(with-current-buffer buffer
|
||||
(buffer-substring (point-min) (point-max)))
|
||||
contents))))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||
|
||||
(ert-deftest ebox-spi-rejects-malformed-pairs-and-provider-records ()
|
||||
"Provider-side schemas reject malformed callbacks and capability records."
|
||||
(let ((buffer (generate-new-buffer " *ebox-m2a-e5-invalid*"))
|
||||
(input (ebox-test-box :key 'root (ebox-test-text "invalid")))
|
||||
(valid (ebox-framework-spi-capabilities)))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(should-error
|
||||
(ebox-framework-spi-initial buffer input nil #'ignore)
|
||||
:type 'wrong-type-argument)
|
||||
(should-error
|
||||
(ebox-framework-spi-initial buffer input #'ignore nil)
|
||||
:type 'wrong-type-argument)
|
||||
(should-error
|
||||
(ebox-framework-spi-update buffer input 7 #'ignore)
|
||||
:type 'wrong-type-argument)
|
||||
(should-error
|
||||
(ebox-framework-spi-validate-provider
|
||||
(ebox-framework-spi--make-provider
|
||||
:spi-version 99
|
||||
:schema-version 'broken
|
||||
:capabilities nil
|
||||
:tp-protocol 'broken))
|
||||
:type 'ebox-framework-spi-provider-error)
|
||||
(should-error
|
||||
(ebox-framework-spi-validate-provider
|
||||
(ebox-framework-spi--make-provider
|
||||
:spi-version 2
|
||||
:schema-version 'ebox-framework-spi-schema/v2
|
||||
:capabilities
|
||||
'(initial-paired-stage-rollback
|
||||
update-paired-stage-rollback
|
||||
combined-participant-ordering
|
||||
same-object-legacy-report)
|
||||
:tp-protocol 'tp-transaction-protocol-v1+v2
|
||||
:stage-order '(ebox-mirror/native framework-stage)
|
||||
:rollback-order '(framework-rollback ebox-mirror/native tp)
|
||||
:report-semantics 'same-object-legacy-report
|
||||
:initial-operation
|
||||
(ebox-framework-spi--make-operation
|
||||
:kind 'initial
|
||||
:function 'ebox-framework-spi-initial
|
||||
:argument-schema
|
||||
'(buffer canonical-input framework-stage framework-rollback)
|
||||
:result-schema 'broken
|
||||
:paired-stage-rollback-p nil)
|
||||
:update-operation
|
||||
(ebox-framework-spi-provider-update-operation valid)))
|
||||
:type 'ebox-framework-spi-provider-error))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||
|
||||
(ert-deftest ebox-spi-performance-builds-bounded-provider-snapshots ()
|
||||
"Capability access constructs one provider and two operations per call."
|
||||
(let ((provider-count 0)
|
||||
(operation-count 0)
|
||||
(provider-constructor
|
||||
(symbol-function 'ebox-framework-spi--make-provider))
|
||||
(operation-factory
|
||||
(symbol-function 'ebox-framework-spi--operation))
|
||||
benchmark last)
|
||||
(cl-letf (((symbol-function 'ebox-framework-spi--make-provider)
|
||||
(lambda (&rest arguments)
|
||||
(cl-incf provider-count)
|
||||
(apply provider-constructor arguments)))
|
||||
((symbol-function 'ebox-framework-spi--operation)
|
||||
(lambda (&rest arguments)
|
||||
(cl-incf operation-count)
|
||||
(apply operation-factory arguments))))
|
||||
(setq benchmark
|
||||
(benchmark-run
|
||||
1
|
||||
(dotimes (_iteration 1000)
|
||||
(setq last (ebox-framework-spi-capabilities))))))
|
||||
(should (ebox-framework-spi-provider-p last))
|
||||
(should (= provider-count 1000))
|
||||
(should (= operation-count 2000))
|
||||
(should (< (car benchmark) 2.0))))
|
||||
|
||||
(provide 'ebox-spi-tests)
|
||||
|
||||
;;; ebox-spi-tests.el ends here
|
||||
Loading…
Reference in New Issue
Block a user