Python · 9597 bytes Raw Blame History
1 #!/usr/bin/env python3
2 """
3 Integration test runner for FACSIMILE editor.
4 This script uses pexpect to simulate terminal interactions with the editor.
5 """
6
7 import sys
8 import os
9 import time
10 import tempfile
11 import subprocess
12 from typing import List, Tuple, Optional
13
14 try:
15 import pexpect
16 except ImportError:
17 print("Error: pexpect is required. Install with: pip install pexpect")
18 sys.exit(1)
19
20
21 class FacsimileTest:
22 """Test harness for FACSIMILE editor."""
23
24 def __init__(self, binary_path: str = "./build/gfortran_*/app/fac"):
25 """Initialize test harness."""
26 # Find the actual binary path
27 import glob
28 paths = glob.glob(binary_path)
29 if not paths:
30 raise FileNotFoundError(f"Could not find fac binary at {binary_path}")
31 self.binary_path = paths[0]
32 self.process = None
33 self.test_file = None
34
35 def start(self, initial_content: str = "") -> None:
36 """Start the editor with a temporary file."""
37 # Create temporary file with initial content
38 self.test_file = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False)
39 self.test_file.write(initial_content)
40 self.test_file.flush()
41 self.test_file.close()
42
43 # Start the editor
44 self.process = pexpect.spawn(self.binary_path, [self.test_file.name],
45 timeout=5, encoding='utf-8')
46 time.sleep(0.2) # Give editor time to initialize
47
48 def stop(self) -> None:
49 """Stop the editor and clean up."""
50 if self.process:
51 try:
52 # Save first to avoid unsaved changes prompt
53 self.send_key('ctrl-s')
54 time.sleep(0.1)
55 # Now quit
56 self.send_key('ctrl-q')
57 self.process.expect(pexpect.EOF, timeout=2)
58 except pexpect.TIMEOUT:
59 # Force terminate if it doesn't exit cleanly
60 self.process.terminate()
61 time.sleep(0.1)
62 except:
63 pass
64 finally:
65 self.process = None
66
67 # Clean up temp file
68 if self.test_file:
69 try:
70 os.unlink(self.test_file.name)
71 except:
72 pass
73 self.test_file = None
74
75 def send_key(self, key: str) -> None:
76 """Send a special key combination to the editor."""
77 key_map = {
78 'ctrl-a': '\x01', 'ctrl-b': '\x02', 'ctrl-c': '\x03', 'ctrl-d': '\x04',
79 'ctrl-e': '\x05', 'ctrl-f': '\x06', 'ctrl-g': '\x07', 'ctrl-h': '\x08',
80 'ctrl-i': '\x09', 'ctrl-j': '\x0a', 'ctrl-k': '\x0b', 'ctrl-l': '\x0c',
81 'ctrl-m': '\x0d', 'ctrl-n': '\x0e', 'ctrl-o': '\x0f', 'ctrl-p': '\x10',
82 'ctrl-q': '\x11', 'ctrl-r': '\x12', 'ctrl-s': '\x13', 'ctrl-t': '\x14',
83 'ctrl-u': '\x15', 'ctrl-v': '\x16', 'ctrl-w': '\x17', 'ctrl-x': '\x18',
84 'ctrl-y': '\x19', 'ctrl-z': '\x1a',
85 'ctrl-shift-z': '\x1a', # Redo (may need different mapping)
86 'escape': '\x1b', 'enter': '\n', 'tab': '\t',
87 'backspace': '\x7f', 'delete': '\x1b[3~',
88 'up': '\x1b[A', 'down': '\x1b[B', 'right': '\x1b[C', 'left': '\x1b[D',
89 'home': '\x1b[H', 'end': '\x1b[F',
90 'pageup': '\x1b[5~', 'pagedown': '\x1b[6~',
91 'alt-[': '\x1b[', 'alt-]': '\x1b]',
92 'alt-right': '\x1b[1;3C', 'alt-left': '\x1b[1;3D',
93 }
94
95 if key in key_map:
96 self.process.send(key_map[key])
97 time.sleep(0.05) # Small delay between key presses
98 else:
99 print(f"Warning: Unknown key '{key}'")
100
101 def type_text(self, text: str) -> None:
102 """Type regular text into the editor."""
103 for char in text:
104 self.process.send(char)
105 time.sleep(0.01) # Small delay between characters
106
107 def save_file(self) -> None:
108 """Save the current file."""
109 self.send_key('ctrl-s')
110 time.sleep(0.1)
111
112 def get_file_content(self) -> str:
113 """Get the current content of the file."""
114 self.save_file()
115 with open(self.test_file.name, 'r') as f:
116 return f.read()
117
118 def screenshot(self) -> str:
119 """Get current screen content (for debugging)."""
120 # Send a redraw command
121 self.send_key('ctrl-l')
122 time.sleep(0.1)
123 return self.process.before or ""
124
125
126 class TestRunner:
127 """Run integration tests for FACSIMILE."""
128
129 def __init__(self):
130 self.passed = 0
131 self.failed = 0
132 self.tests = []
133
134 def add_test(self, name: str, test_func):
135 """Add a test to the suite."""
136 self.tests.append((name, test_func))
137
138 def run(self) -> bool:
139 """Run all tests and return success status."""
140 print("=" * 60)
141 print("FACSIMILE Integration Tests")
142 print("=" * 60)
143
144 for name, test_func in self.tests:
145 editor = None
146 try:
147 editor = FacsimileTest()
148 test_func(editor)
149 print(f"✓ {name}")
150 self.passed += 1
151 except AssertionError as e:
152 print(f"✗ {name}: {str(e)}")
153 self.failed += 1
154 except Exception as e:
155 print(f"✗ {name}: Unexpected error: {e.__class__.__name__}: {str(e)}")
156 self.failed += 1
157 finally:
158 if editor:
159 try:
160 editor.stop()
161 except:
162 pass # Ignore cleanup errors
163
164 print("-" * 60)
165 print(f"Tests run: {self.passed + self.failed}, Passed: {self.passed}, Failed: {self.failed}")
166
167 if self.failed == 0:
168 print("All tests passed! ✓")
169 return True
170 else:
171 print(f"FAILURE: {self.failed} tests failed")
172 return False
173
174
175 # Test functions
176 def test_basic_typing(editor: FacsimileTest):
177 """Test basic text input."""
178 editor.start("")
179 editor.type_text("Hello World")
180 time.sleep(0.5) # Give more time for text to be typed
181 content = editor.get_file_content()
182 assert content == "Hello World", f"Expected 'Hello World', got '{content}'"
183
184
185 def test_navigation(editor: FacsimileTest):
186 """Test cursor navigation."""
187 editor.start("Hello\nWorld")
188 editor.send_key('down')
189 editor.send_key('end')
190 editor.type_text("!")
191 content = editor.get_file_content()
192 assert content == "Hello\nWorld!", f"Expected 'Hello\\nWorld!', got '{content}'"
193
194
195 def test_auto_close_brackets(editor: FacsimileTest):
196 """Test auto-close brackets feature."""
197 editor.start("")
198 editor.type_text("(")
199 editor.type_text("test")
200 editor.send_key('right') # Move past closing paren
201 editor.type_text(" [")
202 editor.type_text("array")
203 content = editor.get_file_content()
204 assert "(test) [array]" in content, f"Auto-close failed: '{content}'"
205
206
207 def test_search(editor: FacsimileTest):
208 """Test search functionality."""
209 editor.start("Hello World\nHello Universe\nHello Galaxy")
210 editor.type_text("/")
211 time.sleep(0.1)
212 editor.type_text("Universe")
213 editor.send_key('enter')
214 # Cursor should now be on "Universe"
215 editor.send_key('ctrl-k') # Kill line forward
216 content = editor.get_file_content()
217 assert "Hello Universe" not in content or "Universe" not in content.split('\n')[1]
218
219
220 def test_undo_redo(editor: FacsimileTest):
221 """Test undo/redo functionality."""
222 editor.start("")
223 editor.type_text("First")
224 editor.send_key('ctrl-z') # Undo
225 content = editor.get_file_content()
226 assert content == "", f"Undo failed: '{content}'"
227
228 editor.send_key('ctrl-shift-z') # Redo
229 content = editor.get_file_content()
230 assert content == "First", f"Redo failed: '{content}'"
231
232
233 def test_indent_dedent(editor: FacsimileTest):
234 """Test tab/shift-tab indent/dedent."""
235 editor.start("Line 1\nLine 2\nLine 3")
236 # Select all lines (simplified - would need proper selection in real test)
237 editor.send_key('ctrl-a') # Go to start
238 editor.type_text(" ") # Manual indent for this test
239 content = editor.get_file_content()
240 assert content.startswith(" "), f"Indent failed: '{content}'"
241
242
243 def test_bracket_jump(editor: FacsimileTest):
244 """Test jump to matching bracket."""
245 editor.start("(hello [world] test)")
246 editor.send_key('alt-]') # Jump to matching bracket
247 editor.type_text("X")
248 content = editor.get_file_content()
249 # This would need more sophisticated testing to verify cursor position
250
251
252 def test_smart_home(editor: FacsimileTest):
253 """Test smart home behavior."""
254 editor.start(" Hello World")
255 editor.send_key('end')
256 editor.send_key('ctrl-a') # Should go to 'H'
257 editor.type_text("X")
258 content = editor.get_file_content()
259 assert "XHello" in content or " XHello" in content
260
261
262 def main():
263 """Run all integration tests."""
264 runner = TestRunner()
265
266 # Add all test functions
267 runner.add_test("Basic typing", test_basic_typing)
268 runner.add_test("Navigation", test_navigation)
269 runner.add_test("Auto-close brackets", test_auto_close_brackets)
270 runner.add_test("Search", test_search)
271 runner.add_test("Undo/Redo", test_undo_redo)
272 runner.add_test("Indent/Dedent", test_indent_dedent)
273 runner.add_test("Bracket jump", test_bracket_jump)
274 runner.add_test("Smart home", test_smart_home)
275
276 # Run tests
277 success = runner.run()
278 sys.exit(0 if success else 1)
279
280
281 if __name__ == "__main__":
282 main()