markdown · 6854 bytes Raw Blame History

fortsh Interactive Test Framework

Automated testing framework for fortsh's interactive features using Python and pexpect.

Quick Start

# Activate the virtual environment
source tests/interactive/.venv/bin/activate

# Run all YAML spec tests
python tests/interactive/run_tests.py

# Run with specific fortsh binary
python tests/interactive/run_tests.py --fortsh ./bin/fortsh

# Run pytest tests
python tests/interactive/run_tests.py --pytest

# Generate markdown report
python tests/interactive/run_tests.py --report manual/results/$(date +%Y%m%d).md

Directory Structure

tests/interactive/
├── run_tests.py              # Main test runner
├── fortsh_pty.py             # PTY management class
├── conftest.py               # Pytest fixtures
├── requirements.txt          # Python dependencies
├── test_specs/               # YAML test specifications
│   ├── line_editing.yaml     # Line editing tests (49 tests)
│   ├── history.yaml          # History navigation/expansion (37 tests)
│   ├── completion.yaml       # Tab completion tests (37 tests)
│   ├── signals_jobs.yaml     # Signals and job control (40 tests)
│   ├── prompt_display.yaml   # Prompt and display tests (39 tests)
│   └── posix.yaml            # POSIX shell features (119 tests)
├── utils/
│   ├── keys.py               # Key sequence definitions
│   └── matchers.py           # Output matching utilities
├── manual/
│   └── results/              # Test result reports
└── README.md                 # This file

Writing Tests

YAML Specification Format

Tests can be defined declaratively in YAML:

metadata:
  category: "Line Editing"
  description: "Tests for cursor movement"

tests:
  - name: "Left arrow moves cursor back"
    steps:
      - send: "echo test"
      - send_key: "Left"
      - send_key: "Left"
      - send: "X"
      - send_key: "Enter"
    expect_output: "teXst"
    match_type: "contains"

Available Step Types

Step Description Example
send Send text without newline send: "echo hello"
send_line Send text with Enter send_line: "echo hello"
send_key Send special key send_key: "C-a"
send_keys Send multiple keys send_keys: ["Left", "Left"]
wait Sleep for seconds wait: 0.5
wait_for_prompt Wait for shell prompt wait_for_prompt: true
expect Wait for pattern expect: "hello"
resize Change terminal size resize: {rows: 40, cols: 120}

Match Types

Type Description
exact Exact match (after strip)
contains Substring match
regex Regular expression
startswith Prefix match
endswith Suffix match

Pytest Tests

For complex tests, use Python with pytest fixtures:

import pytest

@pytest.mark.line_editing
def test_ctrl_a_moves_to_beginning(fortsh):
    fortsh.send("hello world")
    fortsh.send_key("C-a")
    fortsh.send("echo ")
    fortsh.send_key("Enter")
    output = fortsh.wait_for_prompt()
    assert "hello world" in output

Available Fixtures

  • fortsh - Running fortsh session (rc disabled)
  • fortsh_with_rc - Session with user's .fortshrc
  • fortsh_factory - Create multiple sessions
  • fortsh_path - Path to fortsh binary

Key Sequences

Common keys are defined in utils/keys.py:

# Control keys
"C-a"  # Beginning of line
"C-e"  # End of line
"C-k"  # Kill to end
"C-u"  # Kill to beginning
"C-w"  # Kill word
"C-y"  # Yank
"C-c"  # Interrupt
"C-z"  # Suspend
"C-d"  # EOF/Delete
"C-r"  # Reverse search

# Alt/Meta keys
"M-b"  # Back word
"M-f"  # Forward word

# Arrow keys
"Up", "Down", "Left", "Right"

# Special
"Tab", "Enter", "Backspace", "Delete"
"Home", "End", "PageUp", "PageDown"

Test Categories

Tests are organized by feature (total 321 tests):

  1. POSIX Shell Features (120+ tests)

    • Basic operations
    • Quoting and escaping
    • Variables and expansion
    • Pipelines and redirections
    • Control structures (if/for/while/case)
    • Functions and arithmetic
    • Builtins
  2. Line Editing (49 tests)

    • Cursor movement
    • Text modification
    • Kill ring operations
    • Word operations
  3. History (37 tests)

    • Arrow key navigation
    • Ctrl+R search
    • History expansion (!!, !$, etc.)
  4. Completion (37 tests)

    • Command completion
    • Path/file completion
    • Variable completion
  5. Signals & Job Control (40 tests)

    • SIGINT (Ctrl+C)
    • SIGTSTP (Ctrl+Z)
    • Background jobs
    • Job specs (%n, %%, %+)
    • fg/bg/jobs builtins
  6. Prompt & Display (39 tests)

    • PS1/PS2 escapes
    • Terminal resize
    • Colors and Unicode

Running Specific Tests

# Run single spec file
python tests/interactive/run_tests.py --spec line_editing.yaml

# Run pytest with markers
pytest tests/interactive -m line_editing
pytest tests/interactive -m "not slow"

# Run with verbose output
python tests/interactive/run_tests.py -v

Environment Variables

  • FORTSH - Path to fortsh binary
  • FORTSH_RC_FILE - Override rc file path

Adding New Tests

1. Add to existing YAML spec

# In test_specs/line_editing.yaml
tests:
  - name: "My new test"
    steps:
      - send_line: "echo test"
    expect_output: "test"

2. Create new YAML spec

# Create tests/interactive/test_specs/my_feature.yaml

3. Add pytest test

# Create tests/interactive/test_my_feature.py

def test_my_feature(fortsh):
    output = fortsh.run_command("echo hello")
    assert "hello" in output

CI Integration

For GitHub Actions with tmux:

- name: Run interactive tests
  run: |
    tmux new-session -d -s test
    tmux send-keys -t test "python tests/interactive/run_tests.py" Enter
    sleep 60
    tmux capture-pane -t test -p > test_output.txt
    grep -q "ALL TESTS PASSED" test_output.txt

Troubleshooting

"fortsh binary not found"

# Build fortsh first
make clean && make

# Or specify path
python tests/interactive/run_tests.py --fortsh /path/to/fortsh

Test timeouts

Increase timeout in test or PTY initialization:

pty = FortshPTY(timeout=10.0)  # 10 seconds

Debugging tests

# In pytest test
def test_debug(fortsh):
    fortsh.send_line("echo hello")
    import time; time.sleep(5)  # Pause to observe
    output = fortsh.wait_for_prompt()
    print(f"Output: {output}")  # Will show with -s flag

Run with:

pytest tests/interactive -s --tb=long

Dependencies

  • Python 3.8+
  • pexpect >= 4.8
  • PyYAML >= 6.0
  • pytest >= 7.0
  • colorama >= 0.4

Install with:

pip install -r tests/interactive/requirements.txt
View source
1 # fortsh Interactive Test Framework
2
3 Automated testing framework for fortsh's interactive features using Python and pexpect.
4
5 ## Quick Start
6
7 ```bash
8 # Activate the virtual environment
9 source tests/interactive/.venv/bin/activate
10
11 # Run all YAML spec tests
12 python tests/interactive/run_tests.py
13
14 # Run with specific fortsh binary
15 python tests/interactive/run_tests.py --fortsh ./bin/fortsh
16
17 # Run pytest tests
18 python tests/interactive/run_tests.py --pytest
19
20 # Generate markdown report
21 python tests/interactive/run_tests.py --report manual/results/$(date +%Y%m%d).md
22 ```
23
24 ## Directory Structure
25
26 ```
27 tests/interactive/
28 ├── run_tests.py # Main test runner
29 ├── fortsh_pty.py # PTY management class
30 ├── conftest.py # Pytest fixtures
31 ├── requirements.txt # Python dependencies
32 ├── test_specs/ # YAML test specifications
33 │ ├── line_editing.yaml # Line editing tests (49 tests)
34 │ ├── history.yaml # History navigation/expansion (37 tests)
35 │ ├── completion.yaml # Tab completion tests (37 tests)
36 │ ├── signals_jobs.yaml # Signals and job control (40 tests)
37 │ ├── prompt_display.yaml # Prompt and display tests (39 tests)
38 │ └── posix.yaml # POSIX shell features (119 tests)
39 ├── utils/
40 │ ├── keys.py # Key sequence definitions
41 │ └── matchers.py # Output matching utilities
42 ├── manual/
43 │ └── results/ # Test result reports
44 └── README.md # This file
45 ```
46
47 ## Writing Tests
48
49 ### YAML Specification Format
50
51 Tests can be defined declaratively in YAML:
52
53 ```yaml
54 metadata:
55 category: "Line Editing"
56 description: "Tests for cursor movement"
57
58 tests:
59 - name: "Left arrow moves cursor back"
60 steps:
61 - send: "echo test"
62 - send_key: "Left"
63 - send_key: "Left"
64 - send: "X"
65 - send_key: "Enter"
66 expect_output: "teXst"
67 match_type: "contains"
68 ```
69
70 #### Available Step Types
71
72 | Step | Description | Example |
73 |------|-------------|---------|
74 | `send` | Send text without newline | `send: "echo hello"` |
75 | `send_line` | Send text with Enter | `send_line: "echo hello"` |
76 | `send_key` | Send special key | `send_key: "C-a"` |
77 | `send_keys` | Send multiple keys | `send_keys: ["Left", "Left"]` |
78 | `wait` | Sleep for seconds | `wait: 0.5` |
79 | `wait_for_prompt` | Wait for shell prompt | `wait_for_prompt: true` |
80 | `expect` | Wait for pattern | `expect: "hello"` |
81 | `resize` | Change terminal size | `resize: {rows: 40, cols: 120}` |
82
83 #### Match Types
84
85 | Type | Description |
86 |------|-------------|
87 | `exact` | Exact match (after strip) |
88 | `contains` | Substring match |
89 | `regex` | Regular expression |
90 | `startswith` | Prefix match |
91 | `endswith` | Suffix match |
92
93 ### Pytest Tests
94
95 For complex tests, use Python with pytest fixtures:
96
97 ```python
98 import pytest
99
100 @pytest.mark.line_editing
101 def test_ctrl_a_moves_to_beginning(fortsh):
102 fortsh.send("hello world")
103 fortsh.send_key("C-a")
104 fortsh.send("echo ")
105 fortsh.send_key("Enter")
106 output = fortsh.wait_for_prompt()
107 assert "hello world" in output
108 ```
109
110 #### Available Fixtures
111
112 - `fortsh` - Running fortsh session (rc disabled)
113 - `fortsh_with_rc` - Session with user's .fortshrc
114 - `fortsh_factory` - Create multiple sessions
115 - `fortsh_path` - Path to fortsh binary
116
117 ## Key Sequences
118
119 Common keys are defined in `utils/keys.py`:
120
121 ```python
122 # Control keys
123 "C-a" # Beginning of line
124 "C-e" # End of line
125 "C-k" # Kill to end
126 "C-u" # Kill to beginning
127 "C-w" # Kill word
128 "C-y" # Yank
129 "C-c" # Interrupt
130 "C-z" # Suspend
131 "C-d" # EOF/Delete
132 "C-r" # Reverse search
133
134 # Alt/Meta keys
135 "M-b" # Back word
136 "M-f" # Forward word
137
138 # Arrow keys
139 "Up", "Down", "Left", "Right"
140
141 # Special
142 "Tab", "Enter", "Backspace", "Delete"
143 "Home", "End", "PageUp", "PageDown"
144 ```
145
146 ## Test Categories
147
148 Tests are organized by feature (total 321 tests):
149
150 1. **POSIX Shell Features** (120+ tests)
151 - Basic operations
152 - Quoting and escaping
153 - Variables and expansion
154 - Pipelines and redirections
155 - Control structures (if/for/while/case)
156 - Functions and arithmetic
157 - Builtins
158
159 2. **Line Editing** (49 tests)
160 - Cursor movement
161 - Text modification
162 - Kill ring operations
163 - Word operations
164
165 3. **History** (37 tests)
166 - Arrow key navigation
167 - Ctrl+R search
168 - History expansion (!!, !$, etc.)
169
170 4. **Completion** (37 tests)
171 - Command completion
172 - Path/file completion
173 - Variable completion
174
175 5. **Signals & Job Control** (40 tests)
176 - SIGINT (Ctrl+C)
177 - SIGTSTP (Ctrl+Z)
178 - Background jobs
179 - Job specs (%n, %%, %+)
180 - fg/bg/jobs builtins
181
182 6. **Prompt & Display** (39 tests)
183 - PS1/PS2 escapes
184 - Terminal resize
185 - Colors and Unicode
186
187 ## Running Specific Tests
188
189 ```bash
190 # Run single spec file
191 python tests/interactive/run_tests.py --spec line_editing.yaml
192
193 # Run pytest with markers
194 pytest tests/interactive -m line_editing
195 pytest tests/interactive -m "not slow"
196
197 # Run with verbose output
198 python tests/interactive/run_tests.py -v
199 ```
200
201 ## Environment Variables
202
203 - `FORTSH` - Path to fortsh binary
204 - `FORTSH_RC_FILE` - Override rc file path
205
206 ## Adding New Tests
207
208 ### 1. Add to existing YAML spec
209
210 ```yaml
211 # In test_specs/line_editing.yaml
212 tests:
213 - name: "My new test"
214 steps:
215 - send_line: "echo test"
216 expect_output: "test"
217 ```
218
219 ### 2. Create new YAML spec
220
221 ```bash
222 # Create tests/interactive/test_specs/my_feature.yaml
223 ```
224
225 ### 3. Add pytest test
226
227 ```python
228 # Create tests/interactive/test_my_feature.py
229
230 def test_my_feature(fortsh):
231 output = fortsh.run_command("echo hello")
232 assert "hello" in output
233 ```
234
235 ## CI Integration
236
237 For GitHub Actions with tmux:
238
239 ```yaml
240 - name: Run interactive tests
241 run: |
242 tmux new-session -d -s test
243 tmux send-keys -t test "python tests/interactive/run_tests.py" Enter
244 sleep 60
245 tmux capture-pane -t test -p > test_output.txt
246 grep -q "ALL TESTS PASSED" test_output.txt
247 ```
248
249 ## Troubleshooting
250
251 ### "fortsh binary not found"
252
253 ```bash
254 # Build fortsh first
255 make clean && make
256
257 # Or specify path
258 python tests/interactive/run_tests.py --fortsh /path/to/fortsh
259 ```
260
261 ### Test timeouts
262
263 Increase timeout in test or PTY initialization:
264
265 ```python
266 pty = FortshPTY(timeout=10.0) # 10 seconds
267 ```
268
269 ### Debugging tests
270
271 ```python
272 # In pytest test
273 def test_debug(fortsh):
274 fortsh.send_line("echo hello")
275 import time; time.sleep(5) # Pause to observe
276 output = fortsh.wait_for_prompt()
277 print(f"Output: {output}") # Will show with -s flag
278 ```
279
280 Run with:
281 ```bash
282 pytest tests/interactive -s --tb=long
283 ```
284
285 ## Dependencies
286
287 - Python 3.8+
288 - pexpect >= 4.8
289 - PyYAML >= 6.0
290 - pytest >= 7.0
291 - colorama >= 0.4
292
293 Install with:
294 ```bash
295 pip install -r tests/interactive/requirements.txt
296 ```