github.com/grzegorz-zur/bm@v0.0.0-20240312214136-6fc133e3e2c0/editor_test.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "github.com/gdamore/tcell" 8 "io" 9 "io/ioutil" 10 "log" 11 "os" 12 "path" 13 "strings" 14 "testing" 15 "time" 16 "unicode" 17 ) 18 19 func TestEditor(t *testing.T) { 20 prefix := time.Now().Format("bm_2006-01-02_15-04-05_") 21 temp, err := ioutil.TempDir("", prefix) 22 if err != nil { 23 t.Fatalf("error creating temporary directory %s: %v", prefix, err) 24 } 25 names, err := list("test") 26 if err != nil { 27 t.Fatalf("error reading test directory: %v", err) 28 } 29 for _, name := range names { 30 test := test(name, temp) 31 t.Run(name, test) 32 } 33 } 34 35 func test(name, temp string) func(t *testing.T) { 36 return func(t *testing.T) { 37 work, files, err := setup(name, temp, t) 38 if err != nil { 39 t.Fatalf("error on setup: %s: %v", name, err) 40 } 41 cmds, err := commands(name) 42 if err != nil { 43 t.Fatalf("error parsing script %s: %v", name, err) 44 } 45 46 dir, err := os.Getwd() 47 if err != nil { 48 t.Fatalf("error getting current directory: %v", err) 49 } 50 defer os.Chdir(dir) 51 err = os.Chdir(work) 52 if err != nil { 53 t.Fatalf("error changing dir to %s: %v", work, err) 54 } 55 56 logpath := path.Join(temp, name+".log") 57 logfile, err := os.Create(logpath) 58 if err != nil { 59 t.Fatalf("error opening logfile %s: %v", logpath, err) 60 os.Exit(-1) 61 } 62 defer logfile.Close() 63 log.SetOutput(logfile) 64 defer log.SetOutput(os.Stderr) 65 66 screen := tcell.NewSimulationScreen("") 67 screenCreate := func() (tcell.Screen, error) { 68 return screen, nil 69 } 70 editor := New(screenCreate, files) 71 err = editor.Start() 72 if err != nil { 73 t.Fatalf("error starting editor: %v", err) 74 } 75 sendResize(screen, 40, 25) 76 sendResize(screen, 80, 50) 77 78 err = interpret(screen, editor, cmds) 79 if err != nil { 80 t.Fatalf("error interpreting script: %s: %v", name, err) 81 } 82 83 editor.Wait() 84 85 logfile.Close() 86 log.SetOutput(os.Stderr) 87 88 err = os.Chdir(dir) 89 if err != nil { 90 t.Fatalf("error changing dir to %s: %v", dir, err) 91 } 92 err = verify(name, work, t) 93 if err != nil { 94 t.Fatalf("wrong results: %s: %v", name, err) 95 } 96 } 97 } 98 99 func setup(name, temp string, t *testing.T) (work string, files []string, err error) { 100 in := path.Join("test", name, "in") 101 files, err = list(in) 102 if err != nil && !os.IsNotExist(err) { 103 return "", nil, fmt.Errorf("error listing path %s: %w", in, err) 104 } 105 work = path.Join(temp, name) 106 err = os.MkdirAll(work, os.ModePerm) 107 if err != nil { 108 return "", nil, fmt.Errorf("error creating directory %s: %w", work, err) 109 } 110 for _, file := range files { 111 src := path.Join(in, file) 112 dst := path.Join(work, file) 113 err := copy(src, dst) 114 if err != nil { 115 return "", nil, fmt.Errorf("error copying from %s to %s: %w", src, dst, err) 116 } 117 } 118 return 119 } 120 121 func commands(name string) (cmds []string, err error) { 122 path := path.Join("test", name, "script") 123 file, err := os.Open(path) 124 if err != nil { 125 return nil, fmt.Errorf("error opening file %s: %w", path, err) 126 } 127 defer file.Close() 128 scanner := bufio.NewScanner(file) 129 scanner.Split(bufio.ScanWords) 130 for scanner.Scan() { 131 err = scanner.Err() 132 if err != nil { 133 return nil, fmt.Errorf("error scanning file %s: %w", path, err) 134 } 135 cmd := scanner.Text() 136 cmds = append(cmds, cmd) 137 } 138 return 139 } 140 141 func verify(name, work string, t *testing.T) (err error) { 142 out := path.Join("test", name, "out") 143 expected, err := list(out) 144 if err != nil { 145 return fmt.Errorf("error listing path %s: %w", out, err) 146 } 147 actual, err := list(work) 148 if err != nil { 149 return fmt.Errorf("error listing path %s: %w", work, err) 150 } 151 if len(expected) != len(actual) { 152 t.Logf("expected files %v", expected) 153 t.Logf("actual files %v", actual) 154 t.FailNow() 155 } 156 for i := range expected { 157 if actual[i] != expected[i] { 158 t.Logf("expected files %v", expected) 159 t.Logf("actual files %v", actual) 160 t.FailNow() 161 } 162 } 163 for _, exp := range expected { 164 actualPath := path.Join(work, exp) 165 actualContent, err := ioutil.ReadFile(actualPath) 166 if err != nil { 167 return fmt.Errorf("error reading file %s: %w", actualPath, err) 168 } 169 expectedPath := path.Join("test", name, "out", exp) 170 expectedContent, err := ioutil.ReadFile(expectedPath) 171 if err != nil { 172 return fmt.Errorf("error reading file %s: %w", expectedPath, err) 173 } 174 if bytes.Compare(actualContent, expectedContent) != 0 { 175 t.Log("comparing file " + exp) 176 t.Log("expected content") 177 t.Log(format(expectedContent)) 178 t.Log("actual content") 179 t.Log(format(actualContent)) 180 t.FailNow() 181 } 182 } 183 return 184 } 185 186 func format(content []byte) (text string) { 187 text = string(content) 188 text = strings.Replace(text, " ", "␣", -1) 189 text = strings.Replace(text, "\t", "␉", -1) 190 text = strings.Replace(text, "\r", "␍", -1) 191 text = strings.Replace(text, "\n", "␊", -1) 192 return 193 } 194 195 func interpret(screen tcell.Screen, editor *Editor, commands []string) (err error) { 196 for _, cmd := range commands { 197 runes := []rune(cmd) 198 switch { 199 case len(cmd) == 1: 200 rune := runes[0] 201 sendRune(screen, rune) 202 case len(cmd) == 2 && runes[0] == '^': 203 letter := unicode.ToUpper(runes[1]) 204 offset := int(letter - 'A') 205 key := tcell.KeyCtrlA + tcell.Key(offset) 206 sendKey(screen, key) 207 case cmd == "left": 208 sendKey(screen, tcell.KeyLeft) 209 case cmd == "right": 210 sendKey(screen, tcell.KeyRight) 211 case cmd == "up": 212 sendKey(screen, tcell.KeyUp) 213 case cmd == "down": 214 sendKey(screen, tcell.KeyDown) 215 case cmd == "home": 216 sendKey(screen, tcell.KeyHome) 217 case cmd == "end": 218 sendKey(screen, tcell.KeyEnd) 219 case cmd == "pageup": 220 sendKey(screen, tcell.KeyPgUp) 221 case cmd == "pagedown": 222 sendKey(screen, tcell.KeyPgDn) 223 case cmd == "space": 224 sendRune(screen, ' ') 225 case cmd == "tab": 226 sendKey(screen, tcell.KeyTab) 227 case cmd == "enter": 228 sendKey(screen, tcell.KeyEnter) 229 case cmd == "backspace": 230 sendKey(screen, tcell.KeyBackspace2) 231 case cmd == "delete": 232 sendKey(screen, tcell.KeyDelete) 233 case cmd == "ctrlspace": 234 sendKey(screen, tcell.KeyCtrlSpace) 235 case cmd == "TOUCH": 236 t := time.Now().Local() 237 err = os.Chtimes(editor.File.Path, t, t) 238 if err != nil { 239 return fmt.Errorf("error changing time of %s: %w", editor.File.Path, err) 240 } 241 case cmd == "CHECK": 242 time.Sleep(2 * TickInterval) 243 default: 244 return fmt.Errorf("unknown command: %s", cmd) 245 } 246 } 247 return 248 } 249 250 func sendResize(screen tcell.Screen, width, height int) { 251 event := tcell.NewEventResize(width, height) 252 screen.PostEventWait(event) 253 } 254 255 func sendKey(screen tcell.Screen, key tcell.Key) { 256 event := tcell.NewEventKey(key, 0, 0) 257 screen.PostEventWait(event) 258 } 259 260 func sendRune(screen tcell.Screen, rune rune) { 261 event := tcell.NewEventKey(tcell.KeyRune, rune, 0) 262 screen.PostEventWait(event) 263 } 264 265 func list(path string) (names []string, err error) { 266 files, err := ioutil.ReadDir(path) 267 if err != nil && !os.IsNotExist(err) { 268 return nil, fmt.Errorf("error reading directory %s: %w", path, err) 269 } 270 for _, file := range files { 271 name := file.Name() 272 names = append(names, name) 273 } 274 return 275 } 276 277 func copy(src, dst string) (err error) { 278 in, err := os.Open(src) 279 if err != nil { 280 return fmt.Errorf("error opening file %s: %w", src, err) 281 } 282 defer in.Close() 283 284 out, err := os.Create(dst) 285 if err != nil { 286 return fmt.Errorf("error creating file %s: %w", src, err) 287 } 288 defer out.Close() 289 290 _, err = io.Copy(out, in) 291 if err != nil { 292 return fmt.Errorf("error copying from %s to %s: %w", src, dst, err) 293 } 294 return out.Close() 295 }