mirror of
https://github.com/blackboxprogramming/universal-computer.git
synced 2026-03-20 12:51:16 -05:00
Add CI pipeline, 15-test suite, enhanced README
- GitHub Actions CI: Python 3.10/3.11/3.12, pytest, smoke tests - Tests: tape representation, incrementer, even/odd, halt detection, step limits, missing transitions, stay direction - README with badges, theory section, machine creation docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> RoadChain-SHA2048: d4d129eaf6941a81 RoadChain-Identity: alexa@sovereign RoadChain-Full: d4d129eaf6941a815dd5b84bad71115b294f7425d4f184f52ff6770ec641cad192fd361ccea269edefdb8330de6c61b50063d8c103e231bd138fd73fb956d152054336332321ec9e6d1dffa3f3d81cb6295d46d2722116bb9a8239a18d21c6d6d3145e98ff8aff3d5026645db418c5fd0a049f109f8b9e2ee29518a927aade77f315e411b2d0cdfec578f764dcec90da4a0686a39ad1ebcb0bdb9511488efb7f3e14155fe77a550d3b7d07838006a43a3847f239fb2487ca55b22c019ce4ffabc42d0e8e617180ca1aa1ace8fdfcc3ca0f0f24dc7e4be8ed510efcb8f18745217907aa3d5ee94c33b18ff21638ff0b5c0936e5a090f8ad50a1c31ec93787b9fd
This commit is contained in:
150
tests/test_utm.py
Normal file
150
tests/test_utm.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""Tests for the Universal Turing Machine simulator."""
|
||||
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from utm import TuringMachine, parse_tape, tape_to_string
|
||||
|
||||
MACHINES_DIR = os.path.join(os.path.dirname(__file__), '..', 'machines')
|
||||
|
||||
|
||||
def load_machine(name: str) -> TuringMachine:
|
||||
path = os.path.join(MACHINES_DIR, name)
|
||||
with open(path, 'r') as f:
|
||||
return TuringMachine(json.load(f))
|
||||
|
||||
|
||||
# ── Tape representation ────────────────────────────────────────────────────
|
||||
|
||||
class TestTape:
|
||||
def test_parse_tape_basic(self):
|
||||
tape = parse_tape("1101", "_")
|
||||
assert tape == {0: "1", 1: "1", 2: "0", 3: "1"}
|
||||
|
||||
def test_parse_tape_empty(self):
|
||||
tape = parse_tape("", "_")
|
||||
assert tape == {}
|
||||
|
||||
def test_parse_tape_with_blanks(self):
|
||||
tape = parse_tape("1_0", "_")
|
||||
assert tape == {0: "1", 2: "0"}
|
||||
|
||||
def test_tape_to_string(self):
|
||||
tape = {0: "1", 1: "1", 2: "1", 3: "0"}
|
||||
assert tape_to_string(tape, "_") == "1110"
|
||||
|
||||
def test_tape_to_string_empty(self):
|
||||
assert tape_to_string({}, "_") == ""
|
||||
|
||||
def test_tape_to_string_with_gaps(self):
|
||||
tape = {0: "1", 2: "1"}
|
||||
result = tape_to_string(tape, "_")
|
||||
assert result == "1_1"
|
||||
|
||||
|
||||
# ── Incrementer machine ────────────────────────────────────────────────────
|
||||
|
||||
class TestIncrementer:
|
||||
def test_increment_unary_3(self):
|
||||
"""111 (unary 3) should become 1111 (unary 4)."""
|
||||
tm = load_machine("incrementer.json")
|
||||
tape = parse_tape("111", tm.blank)
|
||||
final_tape, steps, state = tm.run(tape)
|
||||
result = tape_to_string(final_tape, tm.blank)
|
||||
assert result == "1111"
|
||||
assert state == tm.halt
|
||||
|
||||
def test_increment_unary_1(self):
|
||||
"""1 (unary 1) should become 11 (unary 2)."""
|
||||
tm = load_machine("incrementer.json")
|
||||
tape = parse_tape("1", tm.blank)
|
||||
final_tape, steps, state = tm.run(tape)
|
||||
result = tape_to_string(final_tape, tm.blank)
|
||||
assert result == "11"
|
||||
|
||||
def test_increment_empty(self):
|
||||
"""Empty tape should become 1 (unary 1)."""
|
||||
tm = load_machine("incrementer.json")
|
||||
tape = parse_tape("", tm.blank)
|
||||
final_tape, steps, state = tm.run(tape)
|
||||
result = tape_to_string(final_tape, tm.blank)
|
||||
assert result == "1"
|
||||
|
||||
|
||||
# ── Even/odd machine ──────────────────────────────────────────────────────
|
||||
|
||||
class TestEvenOdd:
|
||||
def test_even_length(self):
|
||||
tm = load_machine("even_odd.json")
|
||||
tape = parse_tape("1111", tm.blank)
|
||||
final_tape, steps, state = tm.run(tape)
|
||||
assert state == tm.halt
|
||||
|
||||
def test_odd_length(self):
|
||||
tm = load_machine("even_odd.json")
|
||||
tape = parse_tape("111", tm.blank)
|
||||
final_tape, steps, state = tm.run(tape)
|
||||
assert state == tm.halt
|
||||
|
||||
|
||||
# ── Machine behavior ──────────────────────────────────────────────────────
|
||||
|
||||
class TestMachineBehavior:
|
||||
def test_halts_within_step_limit(self):
|
||||
tm = load_machine("incrementer.json")
|
||||
tape = parse_tape("1", tm.blank)
|
||||
_, steps, _ = tm.run(tape, max_steps=100)
|
||||
assert steps < 100
|
||||
|
||||
def test_step_limit_prevents_infinite_loop(self):
|
||||
"""A machine that never halts should stop at max_steps."""
|
||||
desc = {
|
||||
"states": ["q0"],
|
||||
"alphabet": ["0", "1"],
|
||||
"blank": "_",
|
||||
"transitions": {"q0:0": ["q0", "1", "R"], "q0:1": ["q0", "0", "R"], "q0:_": ["q0", "0", "R"]},
|
||||
"start": "q0",
|
||||
"halt": "halt"
|
||||
}
|
||||
tm = TuringMachine(desc)
|
||||
tape = parse_tape("0", tm.blank)
|
||||
_, steps, state = tm.run(tape, max_steps=50)
|
||||
assert steps == 50
|
||||
assert state != tm.halt
|
||||
|
||||
def test_missing_transition_stops(self):
|
||||
"""Machine should stop when no transition exists."""
|
||||
desc = {
|
||||
"states": ["q0", "halt"],
|
||||
"alphabet": ["a"],
|
||||
"blank": "_",
|
||||
"transitions": {},
|
||||
"start": "q0",
|
||||
"halt": "halt"
|
||||
}
|
||||
tm = TuringMachine(desc)
|
||||
tape = parse_tape("a", tm.blank)
|
||||
_, steps, state = tm.run(tape)
|
||||
assert steps == 0
|
||||
assert state == "q0"
|
||||
|
||||
def test_stay_direction(self):
|
||||
"""S direction should not move the head."""
|
||||
desc = {
|
||||
"states": ["q0", "halt"],
|
||||
"alphabet": ["a"],
|
||||
"blank": "_",
|
||||
"transitions": {"q0:a": ["halt", "b", "S"]},
|
||||
"start": "q0",
|
||||
"halt": "halt"
|
||||
}
|
||||
tm = TuringMachine(desc)
|
||||
tape = parse_tape("a", "_")
|
||||
final_tape, steps, state = tm.run(tape)
|
||||
assert state == "halt"
|
||||
assert final_tape[0] == "b"
|
||||
assert steps == 1
|
||||
Reference in New Issue
Block a user