bf_test.go (6109B)
1 package gbfy 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "testing" 9 10 "github.com/google/go-cmp/cmp" 11 ) 12 13 func TestEval(t *testing.T) { 14 tests := []struct { 15 // Program to evaluate. 16 cmds string 17 // Expected value of instruction and Data pointer. 18 i, d int 19 // Expected values of *specific* cells; mapping between cell 20 // index and value expected there. Since the cell region is 21 // large (3e4) and sparse (0 by default) only specific cells 22 // are checked. 23 cells map[int]byte 24 // Similar as above, but for the internal loop jump lookup array. 25 // Internally this is a dense array (len(loops) == len(cmds)) with 26 // nonzero elements corresponding to the ['s or ]'s index in bf.cmds 27 loops map[int]int 28 // Expected output data bytes. 29 out []byte 30 }{ 31 // Check < and > move data pointer around circular cell region. 32 {"<", 1, 29999, nil, nil, nil}, 33 {">", 2, 0, nil, nil, nil}, 34 // Check that + and - modify cell values. 35 {"+", 3, 0, map[int]byte{0: 1}, nil, nil}, 36 {"-", 4, 0, map[int]byte{0: 0}, nil, nil}, 37 // Check that , and . read input and write output bytes. 38 {",", 5, 0, map[int]byte{0: 4}, nil, nil}, 39 {">", 6, 1, map[int]byte{0: 4}, nil, nil}, 40 {",", 7, 1, map[int]byte{0: 4, 1: 8}, nil, nil}, 41 {",", 8, 1, map[int]byte{0: 4, 1: 15}, nil, nil}, 42 {">", 9, 2, map[int]byte{0: 4, 1: 15}, nil, nil}, 43 {",", 10, 2, map[int]byte{0: 4, 1: 15, 2: 16}, nil, nil}, 44 {".", 11, 2, map[int]byte{0: 4, 1: 15, 2: 16}, nil, []byte{16}}, 45 {"<", 12, 1, map[int]byte{0: 4, 1: 15, 2: 16}, nil, []byte{16}}, 46 {".", 13, 1, map[int]byte{0: 4, 1: 15, 2: 16}, nil, []byte{16, 15}}, 47 {"<", 14, 0, map[int]byte{0: 4, 1: 15, 2: 16}, nil, []byte{16, 15}}, 48 {".", 15, 0, map[int]byte{0: 4, 1: 15, 2: 16}, nil, []byte{16, 15, 4}}, 49 {"[->+<]", 21, 0, map[int]byte{0: 0, 1: 19, 2: 16}, map[int]int{15: 20, 20: 15}, []byte{16, 15, 4}}, 50 {">", 22, 1, map[int]byte{0: 0, 1: 19, 2: 16}, map[int]int{15: 20, 20: 15}, []byte{16, 15, 4}}, 51 {".", 23, 1, map[int]byte{0: 0, 1: 19, 2: 16}, map[int]int{15: 20, 20: 15}, []byte{16, 15, 4, 19}}, 52 } 53 54 var out bytes.Buffer 55 bf := New(bytes.NewBuffer([]byte{4, 8, 15, 16, 23, 41}), &out) 56 if err := checkInterpreter(bf, 0, 0, nil, nil); err != nil { 57 t.Fatalf("[0] Unexpected interpreter state: %v", err) 58 } 59 60 for i, test := range tests { 61 if err := bf.Eval(test.cmds); err != nil { 62 t.Fatalf("[%d] Eval(%q) failed with error: %v", i, test.cmds, err) 63 } 64 if err := checkInterpreter(bf, test.i, test.d, test.cells, test.loops); err != nil { 65 t.Fatalf("[%d] Unexpected interpreter state: %v", i, err) 66 } 67 if diff := cmp.Diff(test.out, out.Bytes()); diff != "" { 68 t.Fatalf("[%d] output diff (-want +got): %s", i, diff) 69 } 70 } 71 } 72 73 func checkInterpreter(bf *Brainfuck, i, d int, cells map[int]byte, loops map[int]int) error { 74 if bf.i != i { 75 return fmt.Errorf("instruction pointer; got %d, want %d", bf.i, i) 76 } 77 if bf.d != d { 78 return fmt.Errorf("data pointer; got %d, want %d", bf.d, d) 79 } 80 // bf.cells \subseteq cells. 81 for idx, got := range bf.cells { 82 if want := cells[idx]; got != want { 83 return fmt.Errorf("cell value at index %d; got %d, want %d", idx, got, want) 84 } 85 } 86 // cells \subseteq bf.cells. 87 for idx, want := range cells { 88 if got := bf.cells[idx]; got != want { 89 return fmt.Errorf("cell value at index %d; got %d, want %d", idx, got, want) 90 } 91 } 92 // bf.loops \subseteq loops. 93 for idx, got := range bf.loops { 94 if want := loops[idx]; got != want { 95 return fmt.Errorf("loop value at index %d; got %d, want %d", idx, got, want) 96 } 97 } 98 // loops \subseteq bf.loops. 99 for idx, want := range loops { 100 if got := bf.loops[idx]; got != want { 101 return fmt.Errorf("loop value at index %d; got %d, want %d", idx, got, want) 102 } 103 } 104 return nil 105 } 106 107 func TestInvalidLoopHandling(t *testing.T) { 108 tests := []string{"[", "]", "[]]", "[][", "[][]]"} 109 for _, test := range tests { 110 if _, err := Run([]byte(test), nil); err == nil { 111 t.Errorf("Parsed invalid program %q; expected error", test) 112 } 113 } 114 } 115 116 func TestHelloWorld(t *testing.T) { 117 program := ` 118 >++++++++[<+++++++++>-]<. 119 >++++[<+++++++>-]<+. 120 +++++++.. 121 +++. 122 >>++++++[<+++++++>-]<++. 123 ------------. 124 >++++++[<+++++++++>-]<+. 125 <. 126 +++. 127 ------. 128 --------. 129 >>>++++[<++++++++>-]<+.` 130 131 out, err := Run([]byte(program), nil) 132 if err != nil { 133 t.Fatalf("Run failed: %v", err) 134 } 135 if diff := cmp.Diff("Hello, World!", string(out)); diff != "" { 136 t.Errorf("Mismatched output data (-want +got):\n%s", diff) 137 } 138 } 139 140 func TestCellWrapping(t *testing.T) { 141 bf := New(nil, nil) 142 // Move to the right .... 3e4 - 1 times. 143 for i := 0; i < len(bf.cells)-1; i++ { 144 if err := bf.Eval(">"); err != nil { 145 t.Fatalf("Eval(>) failed with error %v", err) 146 } 147 } 148 if err := checkInterpreter(bf, 29999, 29999, nil, nil); err != nil { 149 t.Fatalf("Unexpected interpreter state: %v", err) 150 } 151 // Move to the right once more -- should wrap! 152 if err := bf.Eval(">"); err != nil { 153 t.Fatalf("Eval(>) failed with error %v", err) 154 } else if err := checkInterpreter(bf, 30000, 0, nil, nil); err != nil { 155 t.Fatalf("Unexpected interpreter state: %v", err) 156 } 157 // Move to the left -- should wrap! 158 if err := bf.Eval("<"); err != nil { 159 t.Fatalf("Eval(<) failed with error %v", err) 160 } 161 if err := checkInterpreter(bf, 30001, 29999, nil, nil); err != nil { 162 t.Fatalf("Unexpected interpreter state: %v", err) 163 } 164 } 165 166 func Run(cmds []byte, in io.Reader) ([]byte, error) { 167 var out bytes.Buffer 168 bf := New(in, &out) 169 if err := bf.Eval(string(cmds)); err != nil { 170 return nil, err 171 } 172 return out.Bytes(), nil 173 } 174 175 // Find your own source files! 176 func BenchmarkLong(b *testing.B) { runBenchmark(b, "long.bf", nil) } 177 func BenchmarkMandelbrot(b *testing.B) { runBenchmark(b, "mandelbrot.bf", nil) } 178 func BenchmarkFactor(b *testing.B) { 179 runBenchmark(b, "factor.bf", bytes.NewReader([]byte("418151632\n"))) 180 } 181 182 func runBenchmark(b *testing.B, path string, in io.Reader) { 183 b.Helper() 184 src, err := os.ReadFile(path) 185 if err != nil { 186 b.Fatalf("Failed to read benchmark file %q: %v", path, err) 187 } 188 if _, err := Run(src, in); err != nil { 189 b.Fatalf("Failed to run benchmark program %q: %v", path, err) 190 } 191 }