1 Shell Commands
(require recspecs/shell) | package: recspecs-lib |
The recspecs/shell module provides tools for testing interactive shell commands, inspired by the Unix expect tool. It supports both simple transcript-based testing and advanced pattern-based automation.
1.1 Basic Shell Testing
(require recspecs/shell) (expect/shell "cat" "> hi""\n" "hi""\n" "> there""\n" "there")
1.2 Pattern-Based Shell Automation
For complex interactive scenarios, expect/shell/patterns provides a declarative pattern-matching approach similar to the Unix expect tool.
#:timeout — Sets the default timeout in seconds for the entire session (default: 30)
#:strict? — Controls whether whitespace normalization is applied (default: #f)
string-expr or (exact string-expr) — Exact string matching
(regex regex-expr) — Regular expression matching with capture group support
(glob glob-string-expr) — Glob pattern matching with * and ? wildcards
(timeout seconds-expr) — Matches when the specified timeout is reached
eof — Matches when the process terminates
(send-input text-expr) — Send text as input to the process
continue — Proceed to the next pattern
retry — Retry the current pattern
(error message-expr) — Raise an error with the given message
procedure-expr — Call a custom procedure with session and variables
Examples:
(expect/shell/patterns "bash" ["$" (send-input "echo hello")] ["hello" (send-input "exit")])
(expect/shell/patterns "slow-server" #:timeout 60 [(regex #rx"Server started on port ([0-9]+)") (send-input "connect")] [(timeout 30) (error "Server startup timeout")] ["Connected" continue])
(expect/shell/patterns "deployment-script" [(glob "*$ ") (send-input "deploy app")] [(glob "*Success*") continue] [(glob "*Error*") (error "Deployment failed")] [(glob "*Warning*") continue])
(expect/shell/patterns "bash" ["$" (send-input "echo 'port: 8080'")] [(regex #rx"port: ([0-9]+)") (send-input "connect $0")] ["connected" (send-input "exit")])
1.3 Advanced Pattern Features
1.3.1 Variable Capture
When using (regex regex-expr) patterns, capture groups are automatically extracted and made available for variable substitution in subsequent actions. Variables are referenced as $0, $1, $2, etc., where $0 is the first capture group.
(expect/shell/patterns "date" [(regex #rx"([A-Z][a-z]+) ([0-9]+)") (send-input "echo Month: $0, Day: $1")])
1.3.2 Flow Control
continue — Advances to the next pattern in the sequence
retry — Repeats the current pattern (useful for polling)
(error msg) — Terminates with a controlled error
(expect/shell/patterns "build-system" ["Building..." continue] [(regex #rx"Progress: ([0-9]+)%") retry] ["Build complete" continue] [(glob "*failed*") (error "Build failed")])
1.3.3 Timeout Handling
Individual patterns can specify timeouts, and a global session timeout can be set:
(expect/shell/patterns "long-running-process" #:timeout 300 ["Starting..." continue] [(timeout 60) (send-input "status")] ["Progress: 100%" continue] [(timeout 300) (error "Process timeout")])
1.3.4 Custom Actions
For complex logic, actions can be procedures that receive the session state and captured variables:
(define (analyze-output session vars) (if (> (length vars) 0) (printf "Captured: ~a~n" (car vars)) (printf "No captures~n")) 'continue) (expect/shell/patterns "analyzer" [(regex #rx"Result: (.+)") analyze-output])
1.4 Pattern Matching Reference
procedure
(match-pattern pattern text vars) →
boolean? list? string? pattern : pattern? text : string? vars : list?
This function underlies the pattern matching in expect/shell/patterns and can be used directly for testing pattern logic.
(define-values (matched? vars text) (match-pattern (pattern-regex #rx"port: ([0-9]+)") "port: 8080" '()))
1.5 Error Handling and Debugging
When patterns fail to match or timeouts occur, expect/shell/patterns provides detailed error messages including:
The accumulated output at the time of failure
The pattern that was being matched
Suggestions for common issues
For debugging complex interactions, enable verbose mode with recspecs-verbose? or the RECSPECS_VERBOSE environment variable to see real-time output.
1.6 Migration from Basic Shell Testing
Existing expect/shell tests can be gradually migrated to the pattern-based approach for enhanced functionality:
(expect/shell "interactive-app" "> start""\n" "Ready""\n" "> process data.txt""\n" "Processing...""\n" "Done""\n" "> quit") (expect/shell/patterns "interactive-app" ["$" (send-input "start")] ["Ready" (send-input "process data.txt")] [(glob "*Processing*") continue] ["Done" (send-input "quit")])
1.7 Pattern and Action Structures
The pattern-based shell automation is built on the following structures, which can be used directly for advanced scenarios:
struct
(struct pattern-action (pattern action vars) #:extra-constructor-name make-pattern-action) pattern : pattern? action : action? vars : list?
struct
(struct pattern-exact (text) #:extra-constructor-name make-pattern-exact) text : string?
struct
(struct pattern-regex (regex) #:extra-constructor-name make-pattern-regex) regex : regexp?
struct
(struct pattern-glob (pattern) #:extra-constructor-name make-pattern-glob) pattern : string?
struct
(struct pattern-timeout (seconds) #:extra-constructor-name make-pattern-timeout) seconds : number?
struct
(struct pattern-eof () #:extra-constructor-name make-pattern-eof)
struct
(struct action-send-text (text) #:extra-constructor-name make-action-send-text) text : string?
struct
(struct action-continue () #:extra-constructor-name make-action-continue)
struct
(struct action-retry () #:extra-constructor-name make-action-retry)
struct
(struct action-error (message) #:extra-constructor-name make-action-error) message : string?
struct
(struct action-proc (proc) #:extra-constructor-name make-action-proc) proc : procedure?
procedure
(shell-run-patterns cmd patterns [ #:timeout timeout]) → void? cmd : (or/c string? (listof string?)) patterns : (listof pattern-action?) timeout : number? = 30
(shell-run-patterns "bash" (list (pattern-action (pattern-exact "$") (action-send-text "echo test") '()) (pattern-action (pattern-exact "test") (action-send-text "exit") '())))
1.8 Best Practices
Use specific patterns to avoid false matches: prefer (exact "$ ") over "$"
Include timeout patterns for long-running operations
Use continue judiciously to handle intermediate output
Capture important values with regex patterns for reuse
Test pattern logic in isolation using match-pattern
Enable verbose mode during development for better visibility
Consider the order of patterns carefully: more specific patterns should come before general ones
Use eof patterns to handle unexpected process termination gracefully