108 lines
3.2 KiB
Python
108 lines
3.2 KiB
Python
"""Smoke test for the GW-BASIC Jupyter kernel.
|
|||
|
|
|
||
|
|
Spawns the kernel class directly and exercises the do_execute path
|
||
|
|
via the subprocess protocol. No running Jupyter instance required.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
|
||
|
|
# Ensure gwbasic binary is found
|
||
|
|
os.environ['GWBASIC'] = os.path.join(os.path.dirname(__file__), '..', 'build', 'gwbasic')
|
||
|
|
|
||
|
|
|
||
|
|
def test_kernel():
|
||
|
|
from .kernel import GWBasicKernel
|
||
|
|
|
||
|
|
# Minimal mock — we only need the subprocess communication, not ZMQ
|
||
|
|
class MockKernel(GWBasicKernel):
|
||
|
|
execution_count = 1
|
||
|
|
iopub_socket = None
|
||
|
|
_captured = []
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
# Skip Kernel.__init__ (needs ZMQ); just init our state
|
||
|
|
self._proc = None
|
||
|
|
self._timeout = 10
|
||
|
|
|
||
|
|
def send_response(self, socket, msg_type, content):
|
||
|
|
self._captured.append((msg_type, content))
|
||
|
|
|
||
|
|
k = MockKernel()
|
||
|
|
|
||
|
|
def run(code):
|
||
|
|
k._captured = []
|
||
|
|
result = k.do_execute(code, silent=False)
|
||
|
|
output = ''
|
||
|
|
for msg_type, content in k._captured:
|
||
|
|
if msg_type == 'stream':
|
||
|
|
output += content.get('text', '')
|
||
|
|
return result, output.strip()
|
||
|
|
|
||
|
|
# Test 1: Simple PRINT
|
||
|
|
r, out = run('PRINT "Hello Jupyter"')
|
||
|
|
assert r['status'] == 'ok', f'Expected ok, got {r}'
|
||
|
|
assert 'Hello Jupyter' in out, f'Expected Hello Jupyter, got: {out}'
|
||
|
|
print(f' PASS PRINT: {out}')
|
||
|
|
|
||
|
|
# Test 2: Arithmetic
|
||
|
|
r, out = run('PRINT 6*7')
|
||
|
|
assert r['status'] == 'ok'
|
||
|
|
assert '42' in out, f'Expected 42, got: {out}'
|
||
|
|
print(f' PASS Arithmetic: {out}')
|
||
|
|
|
||
|
|
# Test 3: State persistence across cells
|
||
|
|
r, _ = run('X = 42')
|
||
|
|
assert r['status'] == 'ok'
|
||
|
|
r, out = run('PRINT X')
|
||
|
|
assert '42' in out, f'Expected 42, got: {out}'
|
||
|
|
print(f' PASS State persistence: {out}')
|
||
|
|
|
||
|
|
# Test 4: Error handling
|
||
|
|
r, out = run('PRINT 1/0')
|
||
|
|
assert r['status'] == 'error', f'Expected error, got {r}'
|
||
|
|
assert 'Division by zero' in out, f'Expected Division by zero, got: {out}'
|
||
|
|
print(f' PASS Error: {out}')
|
||
|
|
|
||
|
|
# Test 5: Recovery after error
|
||
|
|
r, out = run('PRINT "OK after error"')
|
||
|
|
assert r['status'] == 'ok'
|
||
|
|
assert 'OK after error' in out
|
||
|
|
print(f' PASS Recovery: {out}')
|
||
|
|
|
||
|
|
# Test 6: Program RUN
|
||
|
|
r, _ = run('10 PRINT "Alpha"\n20 PRINT "Beta"')
|
||
|
|
assert r['status'] == 'ok'
|
||
|
|
r, out = run('RUN')
|
||
|
|
assert 'Alpha' in out and 'Beta' in out, f'Expected Alpha+Beta, got: {out}'
|
||
|
|
print(f' PASS RUN: {out}')
|
||
|
|
|
||
|
|
# Test 7: Magic %reset
|
||
|
|
r, out = run('%reset')
|
||
|
|
assert r['status'] == 'ok'
|
||
|
|
assert 'reset' in out.lower()
|
||
|
|
print(f' PASS %reset: {out}')
|
||
|
|
|
||
|
|
# Test 8: String functions
|
||
|
|
r, out = run('PRINT LEFT$("HELLO", 3)')
|
||
|
|
assert 'HEL' in out, f'Expected HEL, got: {out}'
|
||
|
|
print(f' PASS String functions: {out}')
|
||
|
|
|
||
|
|
# Test 9: Multi-statement line
|
||
|
|
r, out = run('A=10:B=20:PRINT A+B')
|
||
|
|
assert '30' in out, f'Expected 30, got: {out}'
|
||
|
|
print(f' PASS Multi-statement: {out}')
|
||
|
|
|
||
|
|
# Test 10: FRE returns real value
|
||
|
|
r, out = run('PRINT FRE(0)')
|
||
|
|
assert r['status'] == 'ok'
|
||
|
|
# Should be a number > 0
|
||
|
|
print(f' PASS FRE: {out}')
|
||
|
|
|
||
|
|
k.do_shutdown(False)
|
||
|
|
print(f'\n10 tests passed.')
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
test_kernel()
|