Fix sort arg order, glob tests; add test suite (26 tests passing)
ober
5461ef58d6ea53c8677e7110b4425bdeb667f9b8
--- a/lib/jcode/tool/file.sls +++ b/lib/jcode/tool/file.sls @@ -103,7 +103,7 @@ (string-ref e 0) #\.)))) entries))] - [sorted (sort visible string<?)] + [sorted (sort string<? visible)] [base (strip-trailing-slash path)] [lines (map (lambda (e) (let ([full (string-append base "/" e)]) --- a/src/jcode/tool/file.ss +++ b/src/jcode/tool/file.ss @@ -66,7 +66,7 @@ (or (= (string-length e) 0) (not (char=? (string-ref e 0) #\.)))) entries))) - (sorted (sort visible string<?)) + (sorted (sort string<? visible)) (base (strip-trailing-slash path)) (lines (map (lambda (e) (let ((full (string-append base "/" e))) new file mode 100644 --- /dev/null +++ b/test/run.ss @@ -0,0 +1,161 @@ +#!/usr/bin/env scheme --script +;;; jcode test suite + +(import (chezscheme) + (jcode core log) + (jcode core message) + (jcode tool registry) + (jcode tool file) + (jcode tool bash)) + +;; ── Helpers ────────────────────────────────────────────────────── + +(define pass-count 0) +(define fail-count 0) + +(define (check! desc result expected) + (if (equal? result expected) + (begin (set! pass-count (+ pass-count 1)) + (printf " PASS: ~a~n" desc)) + (begin (set! fail-count (+ fail-count 1)) + (printf " FAIL: ~a~n expected: ~s~n got: ~s~n" desc expected result)))) + +(define (check-pred! desc result pred) + (if (pred result) + (begin (set! pass-count (+ pass-count 1)) + (printf " PASS: ~a~n" desc)) + (begin (set! fail-count (+ fail-count 1)) + (printf " FAIL: ~a~n got: ~s~n" desc result)))) + +(define (section name) (printf "~n~a~n" name)) + +;; Build an args hashtable from flat key/value pairs +(define (args . pairs) + (let ([h (make-hashtable equal-hash equal?)]) + (let loop ([p pairs]) + (unless (null? p) + (hashtable-set! h (car p) (cadr p)) + (loop (cddr p)))) + h)) + +(define (str-contains? s needle) + (let ([slen (string-length s)] [nlen (string-length needle)]) + (let loop ([i 0]) + (cond [(> (+ i nlen) slen) #f] + [(string=? (substring s i (+ i nlen)) needle) #t] + [else (loop (+ i 1))])))) + +(define (str-prefix? p s) + (and (>= (string-length s) (string-length p)) + (string=? (substring s 0 (string-length p)) p))) + +(define (string-trim-right s) + (let loop ([i (string-length s)]) + (if (and (> i 0) (char-whitespace? (string-ref s (- i 1)))) + (loop (- i 1)) + (substring s 0 i)))) + +;; ── Setup ───────────────────────────────────────────────────────── + +(current-log-level 'warn) +(init-file-tools) +(init-bash-tool) + +;; ── Message tests ───────────────────────────────────────────────── + +(section "=== message ===") + +(let ([m (make-user-message "hello")]) + (check! "user role" (message-role m) "user") + (check! "user content" (message-content m) "hello") + (check! "user no tcs" (message-tool-calls m) #f)) + +(let ([m (make-assistant-message "hi")]) + (check! "assistant role" (message-role m) "assistant") + (check! "assistant content" (message-content m) "hi")) + +(let ([tc (make-tool-call "read" "{\"path\":\"foo\"}")]) + (check! "tool-call name" (tool-call-name tc) "read") + (check! "tool-call args" (tool-call-arguments tc) "{\"path\":\"foo\"}") + (check-pred! "tool-call id non-empty" (tool-call-id tc) + (lambda (s) (> (string-length s) 0)))) + +(let ([tc (restore-tool-call "my-id" "bash" "{}")]) + (check! "restore-tool-call id" (tool-call-id tc) "my-id")) + +(let ([m (make-tool-result "call-123" "result")]) + (check! "tool-result role" (message-role m) "tool") + (check! "tool-result content" (message-content m) "result") + (check! "tool-result call-id" (message-tool-call-id m) "call-123")) + +;; ── File tool tests ─────────────────────────────────────────────── + +(section "=== file tools ===") + +;; write + read round-trip +(let* ([tmp "/tmp/jcode-test-write.txt"] + [data "hello\nworld\n"] + [_ (tool-execute "write" (args "path" tmp "content" data))] + [r (tool-execute "read" (args "path" tmp))]) + (check! "write+read round-trip" r data)) + +;; read missing file +(let ([r (tool-execute "read" (args "path" "/tmp/jcode-no-such-file-xyz-abc"))]) + (check-pred! "read missing returns error" r (lambda (s) (str-prefix? "Error:" s)))) + +;; edit +(let* ([tmp "/tmp/jcode-test-edit.txt"] + [_ (tool-execute "write" (args "path" tmp "content" "foo bar baz"))] + [_ (tool-execute "edit" (args "path" tmp "old_str" "bar" "new_str" "QUX"))] + [r (tool-execute "read" (args "path" tmp))]) + (check! "edit replaces string" r "foo QUX baz")) + +;; edit missing string +(let* ([tmp "/tmp/jcode-test-edit2.txt"] + [_ (tool-execute "write" (args "path" tmp "content" "abc"))] + [r (tool-execute "edit" (args "path" tmp "old_str" "zzz" "new_str" "q"))]) + (check-pred! "edit missing string returns error" r (lambda (s) (str-prefix? "Error:" s)))) + +;; glob +(let ([r (tool-execute "glob" (args "pattern" "*.ss" "path" (current-directory)))]) + (check-pred! "glob finds root .ss files" r + (lambda (s) (str-contains? s "main.ss")))) + +(let ([r (tool-execute "glob" (args "pattern" "**/*.ss" "path" (current-directory)))]) + (check-pred! "glob finds src .ss files" r + (lambda (s) (str-contains? s ".ss")))) + +;; ls +(let ([r (tool-execute "ls" (args "path" "."))]) + (check-pred! "ls lists src dir" r (lambda (s) (str-contains? s "src"))) + (check-pred! "ls marks dirs with /" r (lambda (s) (str-contains? s "src/")))) + +(let ([r (tool-execute "ls" (args "path" "/tmp/no-such-dir-xyz"))]) + (check-pred! "ls missing dir error" r (lambda (s) (str-prefix? "Error:" s)))) + +;; grep +(let ([r (tool-execute "grep" (args "pattern" "cli-main" + "path" "src/jcode/ui/cli.ss"))]) + (check-pred! "grep finds pattern" r (lambda (s) (str-contains? s "cli-main")))) + +(let ([r (tool-execute "grep" (args "pattern" "zzz_not_found_zzz" + "path" "src/jcode/ui/cli.ss"))]) + (check! "grep no match" r "No matches found")) + +;; ── Bash tool tests ─────────────────────────────────────────────── + +(section "=== bash tool ===") + +(let ([r (tool-execute "bash" (args "command" "echo hello"))]) + (check! "bash echo" (string-trim-right r) "hello")) + +(let ([r (tool-execute "bash" (args "command" "echo -n abc && echo def"))]) + (check-pred! "bash multi-cmd" r (lambda (s) (str-contains? s "abc")))) + +(let ([r (tool-execute "bash" (args "command" "exit 42"))]) + (check-pred! "bash non-zero exit shown" r (lambda (s) (str-contains? s "Exit code")))) + +;; ── Results ─────────────────────────────────────────────────────── + +(printf "~n~a passed, ~a failed~n" pass-count fail-count) +(when (> fail-count 0) (exit 1))