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

193 lines
7.0 KiB
EmacsLisp

;;; etaf-resource-tests.el --- ETAF Resource tests -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Code:
(require 'ert)
(require 'etaf-observer)
(require 'etaf-resource)
(ert-deftest etaf-resource-loads-synchronous-value ()
"Expose successful loader output through the public Resource readers."
(let ((resource (etaf-resource (lambda () "ready") :name 'success-test)))
(unwind-protect
(progn
(should (eq 'success (etaf-resource-status resource)))
(should (equal "ready" (etaf-resource-value resource)))
(should-not (etaf-resource-error resource)))
(etaf-resource-dispose resource))))
(ert-deftest etaf-resource-captures-loader-error-state ()
"Capture synchronous loader failures without signaling them."
(let ((resource (etaf-resource (lambda () (error "boom"))
:name 'error-test)))
(unwind-protect
(progn
(should (eq 'error (etaf-resource-status resource)))
(should-not (etaf-resource-value resource))
(should (eq 'error (car (etaf-resource-error resource))))
(should (string-match-p "boom"
(cadr (etaf-resource-error resource)))))
(etaf-resource-dispose resource))))
(ert-deftest etaf-resource-reload-runs-cleanup-and-replaces-value ()
"Run loader cleanup on reload and disposal."
(let ((load-count 0)
(cleanup-count 0)
resource)
(setq resource
(etaf-resource
(lambda ()
(cl-incf load-count)
(etaf-resource-result
load-count
:cleanup (lambda () (cl-incf cleanup-count))))
:name 'reload-test))
(unwind-protect
(progn
(should (= 1 (etaf-resource-value resource)))
(should (= 0 cleanup-count))
(etaf-resource-load resource)
(should (= 2 (etaf-resource-value resource)))
(should (= 1 cleanup-count))
(should-not (etaf-resource-dispose resource))
(should (= 2 cleanup-count))
(should-error (etaf-resource-load resource)
:type 'etaf-resource-error))
(when resource
(etaf-resource-dispose resource)))))
(ert-deftest etaf-resource-parent-scope-disposes-resource-cleanup ()
"Attach Resource cleanup to the active parent Scope."
(let ((scope (etaf-effect-scope :detached t :name 'parent-test))
resource
cleanup-count)
(setq cleanup-count 0)
(setq resource
(etaf-scope-run
scope
(lambda ()
(etaf-resource
(lambda ()
(etaf-resource-result
"scoped"
:cleanup (lambda () (cl-incf cleanup-count))))
:name 'scoped-test))))
(should (equal "scoped" (etaf-resource-value resource)))
(should-not (etaf-scope-stop scope))
(should (= 1 cleanup-count))
(should-error (etaf-resource-load resource)
:type 'etaf-resource-error)))
(ert-deftest etaf-error-boundary-run-handles-body-error ()
"Convert an explicitly wrapped body error into a handler result."
(let ((seen nil))
(should
(equal "handled"
(etaf-error-boundary-run
(lambda () (error "render failed"))
(lambda (condition)
(setq seen condition)
"handled")
:name 'boundary-test)))
(should (eq 'error (car seen)))
(should (string-match-p "render failed" (cadr seen)))))
(ert-deftest etaf-error-boundary-run-preserves-unhandled-errors ()
"Do not swallow errors outside the wrapped body or inside the handler."
(should-error
(progn
(etaf-error-boundary-run
(lambda () "ok")
(lambda (_condition) "unused"))
(error "outside"))
:type 'error)
(should-error
(etaf-error-boundary-run
(lambda () (error "body"))
(lambda (_condition) (error "handler")))
:type 'error))
(ert-deftest etaf-resource-observes-only-loader-execution ()
"Report Resource loads while preserving replacement cleanup semantics."
(let* ((loads 0)
(cleanups 0)
reports
resource
(context
(etaf--observer-context-create
:sink (lambda (report) (push report reports))
:operation-id 51
:runtime-id 8
:buffer-name nil
:diagnostic #'ignore)))
(unwind-protect
(progn
(etaf--observer-call-with-context
context
(lambda ()
(setq resource
(etaf-resource
(lambda ()
(etaf-resource-result
(cl-incf loads)
:cleanup (lambda () (cl-incf cleanups))))))
(etaf-resource-load resource)))
(setq reports (nreverse reports))
(should (equal '(resource resource)
(mapcar (lambda (report)
(plist-get report :provider))
reports)))
(should (equal '(load load)
(mapcar (lambda (report)
(plist-get report :stage))
reports)))
(should (equal '(success success)
(mapcar (lambda (report)
(plist-get report :status))
reports)))
(should (= 2 (etaf-resource-value resource)))
(should (= 1 cleanups))
(etaf-resource-dispose resource)
(should (= 2 cleanups)))
(when resource
(etaf-resource-dispose resource)))))
(ert-deftest etaf-resource-observed-error-keeps-captured-error-contract ()
"Report loader failure without changing Resource error containment."
(let* (reports resource
(context
(etaf--observer-context-create
:sink (lambda (report) (push report reports))
:operation-id 52
:runtime-id 8
:buffer-name nil
:diagnostic #'ignore)))
(unwind-protect
(progn
(etaf--observer-call-with-context
context
(lambda ()
(setq resource (etaf-resource (lambda () (error "boom"))))))
(should (= 1 (length reports)))
(should (eq 'error (plist-get (car reports) :status)))
(should (eq 'error (etaf-resource-status resource)))
(should (string-match-p "boom" (cadr (etaf-resource-error resource)))))
(when resource
(etaf-resource-dispose resource)))))
(ert-deftest etaf-resource-unobserved-load-bypasses-stage-runtime ()
"Keep standalone Resource loading free of observation work."
(let (resource)
(unwind-protect
(cl-letf (((symbol-function 'etaf--observer-call-stage)
(lambda (&rest _args)
(ert-fail "unobserved Resource entered stage runtime"))))
(setq resource (etaf-resource (lambda () "ready")))
(should (equal "ready" (etaf-resource-value resource))))
(when resource
(etaf-resource-dispose resource)))))
;;; etaf-resource-tests.el ends here