feat: implement stateful Micro-Loader and centralized Boot Sequence
This commit is contained in:
@@ -1,65 +1,34 @@
|
||||
(defpackage :org-agent-boot-tests
|
||||
(:use :cl :fiveam :org-agent))
|
||||
(:use :cl :fiveam :org-agent)
|
||||
(:export #:boot-suite))
|
||||
(in-package :org-agent-boot-tests)
|
||||
|
||||
(def-suite boot-suite
|
||||
:description "Verification of the Topological Boot Sequence.")
|
||||
(def-suite boot-suite :description "Verification of the Micro-Loader.")
|
||||
(in-suite boot-suite)
|
||||
|
||||
(defun call-with-temp-dir (fn)
|
||||
(let ((tmp-dir (uiop:ensure-directory-pathname
|
||||
(string-right-trim '(#\Newline)
|
||||
(uiop:run-program "mktemp -d" :output :string)))))
|
||||
(unwind-protect
|
||||
(funcall fn tmp-dir)
|
||||
(uiop:delete-directory-tree tmp-dir :validate t))))
|
||||
(test test-skill-catalog-tracking
|
||||
"Verify that skills are added to the catalog with correct status."
|
||||
(clrhash org-agent::*skill-catalog*)
|
||||
;; We need a temporary skill file to test loading
|
||||
(let ((tmp-skill "/tmp/org-skill-test-catalog.org"))
|
||||
(with-open-file (out tmp-skill :direction :output :if-exists :supersede)
|
||||
(format out "#+TITLE: Test Skill~%#+begin_src lisp~%(defun test-catalog-fn () t)~%#+end_src"))
|
||||
|
||||
(org-agent:load-skill-from-org tmp-skill)
|
||||
(let ((entry (gethash "org-skill-test-catalog" org-agent::*skill-catalog*)))
|
||||
(is (not (null entry)))
|
||||
(is (eq :ready (org-agent::skill-entry-status entry))))
|
||||
(uiop:delete-file-if-exists tmp-skill)))
|
||||
|
||||
(test gateway-enforcement
|
||||
"Prove failure if org-skill-agent.org is missing."
|
||||
(call-with-temp-dir
|
||||
(lambda (tmp-dir)
|
||||
(let ((old-skills (uiop:getenv "SKILLS_DIR")))
|
||||
(setf (uiop:getenv "SKILLS_DIR") (namestring tmp-dir))
|
||||
(unwind-protect
|
||||
(signals error (org-agent::load-all-skills))
|
||||
(when old-skills (setf (uiop:getenv "SKILLS_DIR") old-skills)))))))
|
||||
|
||||
(test topological-sort-logic
|
||||
"Verify that skills are sorted based on #+DEPENDS_ON tags."
|
||||
(call-with-temp-dir
|
||||
(lambda (tmp-dir)
|
||||
(let ((file-a (merge-pathnames "org-skill-a.org" tmp-dir))
|
||||
(file-b (merge-pathnames "org-skill-b.org" tmp-dir))
|
||||
(file-c (merge-pathnames "org-skill-c.org" tmp-dir)))
|
||||
;; A depends on B, B depends on C. Final order should be C, B, A.
|
||||
(alexandria:write-string-into-file "#+TITLE: Skill A\n#+DEPENDS_ON: org-skill-b" file-a)
|
||||
(alexandria:write-string-into-file "#+TITLE: Skill B\n#+DEPENDS_ON: org-skill-c" file-b)
|
||||
(alexandria:write-string-into-file "#+TITLE: Skill C" file-c)
|
||||
|
||||
(let ((sorted (org-agent:topological-sort-skills tmp-dir)))
|
||||
(format t "DEBUG: Sorted skills: ~s~%" (mapcar #'pathname-name sorted))
|
||||
(is (equal "org-skill-c" (pathname-name (first sorted))))
|
||||
(is (equal "org-skill-b" (pathname-name (second sorted))))
|
||||
(is (equal "org-skill-a" (pathname-name (third sorted)))))))))
|
||||
|
||||
(test circular-dependency
|
||||
"Verify that circular dependencies signal an error."
|
||||
(call-with-temp-dir
|
||||
(lambda (tmp-dir)
|
||||
(let ((file-a (merge-pathnames "org-skill-a.org" tmp-dir))
|
||||
(file-b (merge-pathnames "org-skill-b.org" tmp-dir)))
|
||||
;; Use simple filename-based dependencies to avoid ID mapping issues in test
|
||||
(alexandria:write-string-into-file "#+DEPENDS_ON: org-skill-b" file-a)
|
||||
(alexandria:write-string-into-file "#+DEPENDS_ON: org-skill-a" file-b)
|
||||
(signals error (org-agent:topological-sort-skills tmp-dir))))))
|
||||
|
||||
(test load-skill-timeout
|
||||
"Verify that slow skills are terminated."
|
||||
(call-with-temp-dir
|
||||
(lambda (tmp-dir)
|
||||
(let ((slow-file (merge-pathnames "org-skill-slow.org" tmp-dir)))
|
||||
;; Use a busy loop that is guaranteed to take time and not be optimized easily
|
||||
(alexandria:write-string-into-file
|
||||
"#+begin_src lisp\n(cl:let ((count 0)) (cl:loop (cl:incf count) (cl:when (> count 10000000000) (cl:return))))\n#+end_src"
|
||||
slow-file)
|
||||
(is (eq :timeout (org-agent:load-skill-with-timeout slow-file 0.1)))))))
|
||||
(test test-syntax-preflight-blocking
|
||||
"Verify that malformed Lisp prevents skill from loading."
|
||||
(clrhash org-agent::*skill-catalog*)
|
||||
(let ((bad-skill "/tmp/org-skill-bad-syntax.org"))
|
||||
(with-open-file (out bad-skill :direction :output :if-exists :supersede)
|
||||
(format out "#+TITLE: Bad Skill~%#+begin_src lisp~%(defun unclosed (x~%#+end_src"))
|
||||
|
||||
(org-agent:load-skill-from-org bad-skill)
|
||||
(let ((entry (gethash "org-skill-bad-syntax" org-agent::*skill-catalog*)))
|
||||
(is (eq :failed (org-agent::skill-entry-status entry)))
|
||||
(is (search "Syntax Error" (org-agent::skill-entry-error-log entry))))
|
||||
(uiop:delete-file-if-exists bad-skill)))
|
||||
|
||||
Reference in New Issue
Block a user