etaf/tests/etaf-observer-tests.el
2026-08-28 00:50:58 +08:00

169 lines
6.5 KiB
EmacsLisp

;;; etaf-observer-tests.el --- Scoped observer port tests -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; Tests for the dynamically scoped ETAF observer port.
;;; Code:
(require 'ert)
(require 'cl-lib)
(require 'etaf-observer)
(defun etaf-test-observer-context (sink &optional diagnostic)
"Return a test observer context using SINK and DIAGNOSTIC."
(etaf--observer-context-create
:sink sink
:operation-id 41
:runtime-id 9
:buffer-name "*etaf-test*"
:diagnostic (or diagnostic #'ignore)))
(ert-deftest etaf-observer-stage-preserves-success-and-metadata ()
"Observed stages preserve results and emit required flat metadata."
(let (reports)
(should
(eq 'result
(etaf--observer-call-with-context
(etaf-test-observer-context
(lambda (report) (push report reports)))
(lambda ()
(etaf-observer-with-stage ('etaf 'render :detail "root")
'result)))))
(let ((report (car reports)))
(should (= 1 (length reports)))
(should (= etaf-observer-report-format-version
(plist-get report :format-version)))
(should (eq 'etaf (plist-get report :provider)))
(should (eq 'render (plist-get report :stage)))
(should (= 41 (plist-get report :operation-id)))
(should (= 1 (plist-get report :sequence)))
(should (= 9 (plist-get report :runtime-id)))
(should (equal "*etaf-test*" (plist-get report :buffer-name)))
(should (eq 'success (plist-get report :status)))
(should (equal "root" (plist-get report :detail)))
(should (>= (plist-get report :duration-ms) 0.0)))))
(ert-deftest etaf-observer-stage-preserves-error-and-quit ()
"Observed failures are reported and re-signaled without translation."
(let (reports error-condition quit-condition)
(let ((context
(etaf-test-observer-context
(lambda (report) (push report reports)))))
(setq error-condition
(condition-case condition
(etaf--observer-call-with-context
context
(lambda ()
(etaf-observer-with-stage ('etaf 'render)
(signal 'wrong-type-argument '(integerp bad)))))
(error condition)))
(setq quit-condition
(condition-case condition
(etaf--observer-call-with-context
context
(lambda ()
(etaf-observer-with-stage ('ebox 'commit)
(signal 'quit '(requested)))))
(quit condition))))
(should (equal '(wrong-type-argument integerp bad) error-condition))
(should (equal '(quit requested) quit-condition))
(setq reports (nreverse reports))
(should (equal '(error quit)
(mapcar (lambda (report) (plist-get report :status))
reports)))
(should (equal '(1 2)
(mapcar (lambda (report) (plist-get report :sequence))
reports)))))
(ert-deftest etaf-observer-emits-defensive-flat-copy ()
"Observer mutation cannot alter the producer's report snapshot."
(let* ((detail (copy-sequence "producer"))
(report (list :provider 'ebox :stage 'commit
:status 'success :duration-ms 1.0
:detail (list detail)))
(original (copy-tree report)))
(etaf--observer-call-with-context
(etaf-test-observer-context
(lambda (delivered)
(setcar delivered :mutated)
(setcar (cdr delivered) 99)
(aset (car (plist-get delivered :detail)) 0 ?!)))
(lambda () (etaf-observer-emit report)))
(should (equal original report))
(should (equal "producer" detail))))
(ert-deftest etaf-observer-contains-observer-failures-and-invalid-reports ()
"Observer and validation failures are diagnostics, not product failures."
(let (diagnostics failure)
(let ((context
(etaf-test-observer-context
(lambda (_report)
(if (eq failure 'error)
(error "Observer error")
(signal 'quit '(observer-quit))))
(lambda (diagnostic) (push diagnostic diagnostics)))))
(setq failure 'error)
(should
(eq 'product-result
(etaf--observer-call-with-context
context
(lambda ()
(prog1 'product-result
(etaf-observer-emit
'(:provider etaf :stage render
:status success :duration-ms 0.0)))))))
(setq failure 'quit)
(etaf--observer-call-with-context
context
(lambda ()
(etaf-observer-emit
'(:provider etaf :stage commit
:status success :duration-ms 0.0))))
(etaf--observer-call-with-context
context
(lambda ()
(etaf-observer-emit '(:provider etaf :nested (not flat))))))
(should (equal '(observer-failure observer-failure invalid-report)
(mapcar (lambda (diagnostic)
(plist-get diagnostic :kind))
(nreverse diagnostics))))))
(ert-deftest etaf-observer-clears-context-during-reentrant-delivery ()
"Observer reentry sees no active observation context."
(let (context-seen reports)
(etaf--observer-call-with-context
(etaf-test-observer-context
(lambda (report)
(push report reports)
(setq context-seen etaf--observer-context)
(etaf-observer-with-stage ('etaf 'reentrant) 'ignored)))
(lambda ()
(etaf-observer-with-stage ('etaf 'outer) 'done)))
(should-not context-seen)
(should (= 1 (length reports)))
(should (eq 'outer (plist-get (car reports) :stage)))))
(ert-deftest etaf-observer-nil-path-does-zero-instrumentation ()
"The unobserved path does not time, allocate delivery, or inspect GC state."
(let ((body-count 0)
(etaf--observer-context nil))
(cl-letf (((symbol-function 'float-time)
(lambda (&optional _time) (error "Unexpected clock read")))
((symbol-function 'etaf--observer-call-stage)
(lambda (&rest _arguments) (error "Unexpected stage wrapper")))
((symbol-function 'etaf-observer-emit)
(lambda (&rest _arguments) (error "Unexpected report"))))
(should
(eq 'plain-result
(etaf-observer-with-stage ('etaf 'render)
(setq body-count (1+ body-count))
'plain-result))))
(should (= 1 body-count))))
(provide 'etaf-observer-tests)
;;; etaf-observer-tests.el ends here