github.com/mattn/anko@v0.1.10/anko_test.go (about) 1 // +build !appengine 2 3 package main 4 5 import ( 6 "bufio" 7 "io" 8 "log" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "runtime" 13 "strings" 14 "testing" 15 "time" 16 ) 17 18 var logger *log.Logger 19 20 func TestMain(m *testing.M) { 21 parseFlags() 22 code := m.Run() 23 os.Exit(code) 24 } 25 26 func TestRunNonInteractiveFile(t *testing.T) { 27 _, filename, _, _ := runtime.Caller(0) 28 testDir := filepath.Join(filepath.Dir(filename), "core", "testdata") 29 setupEnv() 30 31 file = filepath.Join(testDir, "not-found.ank") 32 exitCode := runNonInteractive() 33 if exitCode != 2 { 34 t.Fatalf("exitCode - received: %v - expected: %v", exitCode, 2) 35 } 36 37 file = filepath.Join(testDir, "broken.ank") 38 exitCode = runNonInteractive() 39 os.Args = []string{os.Args[0]} 40 if exitCode != 4 { 41 t.Fatalf("exitCode - received: %v - expected: %v", exitCode, 4) 42 } 43 44 file = filepath.Join(testDir, "test.ank") 45 exitCode = runNonInteractive() 46 os.Args = []string{os.Args[0]} 47 if exitCode != 0 { 48 t.Fatalf("exitCode - received: %v - expected: %v", exitCode, 0) 49 } 50 51 file = "" 52 } 53 54 func TestRunNonInteractiveExecute(t *testing.T) { 55 setupEnv() 56 57 flagExecute = "1 + 1" 58 exitCode := runNonInteractive() 59 if exitCode != 0 { 60 t.Fatalf("exitCode - received: %v - expected: %v", exitCode, 0) 61 } 62 63 flagExecute = "1++" 64 exitCode = runNonInteractive() 65 if exitCode != 4 { 66 t.Fatalf("exitCode - received: %v - expected: %v", exitCode, 4) 67 } 68 69 flagExecute = "" 70 } 71 72 type testInteractive struct { 73 runLines []string 74 runOutputs []string 75 runError string 76 } 77 78 func TestRunInteractive(t *testing.T) { 79 // empty strings in runOutputs will ignore read timeouts 80 tests := []testInteractive{ 81 {runLines: []string{".."}, runError: "1:1 syntax error on '.' at 1:1"}, 82 {runLines: []string{"1++"}, runError: "1:1 invalid operation"}, 83 {runLines: []string{"var , b = 1, 2"}, runError: "1:7 syntax error: unexpected ','"}, 84 85 {runLines: []string{"", "1"}, runOutputs: []string{"", "1"}}, 86 {runLines: []string{"1 + 1"}, runOutputs: []string{"2"}}, 87 {runLines: []string{"a = 1", "b = 2", "a + b"}, runOutputs: []string{"1", "2", "3"}}, 88 {runLines: []string{"a = 1", "if a == 1 {", "b = 1", "b = 2", "}", "a"}, runOutputs: []string{"1", "", "", "", "2", "1"}}, 89 {runLines: []string{"a = 1", "for i = 0; i < 2; i++ {", "a++", "}", "a"}, runOutputs: []string{"1", "", "", "<nil>", "3"}}, 90 {runLines: []string{"1 + 1", "// comment 1", "2 + 2 // comment 2", "// 3 + 3"}, runOutputs: []string{"2", "<nil>", "4", "<nil>"}}, 91 } 92 runInteractiveTests(t, tests) 93 } 94 95 func runInteractiveTests(t *testing.T, tests []testInteractive) { 96 // create logger 97 // Note: logger is used for debugging since real stdout cannot be used 98 logger = log.New(os.Stderr, "", log.Ldate|log.Ltime|log.LUTC|log.Llongfile) 99 gopath := os.Getenv("GOPATH") 100 if gopath == "" { 101 b, err := exec.Command("go", "env", "GOPATH").CombinedOutput() 102 if err != nil { 103 t.Fatal(err) 104 } 105 gopath = strings.TrimSpace(string(b)) 106 } 107 filehandle, err := os.OpenFile(filepath.Join(gopath, "bin", "anko_test.log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) 108 if err != nil { 109 t.Fatal("OpenFile error:", err) 110 } 111 defer filehandle.Close() 112 logger.SetOutput(filehandle) 113 logger.Print("logger file created") 114 115 // defer sending std back to real 116 realStdin := os.Stdin 117 realStderr := os.Stderr 118 realStdout := os.Stdout 119 defer setStd(realStdin, realStderr, realStdout) 120 121 // create pipes 122 readFromIn, writeToIn, err := os.Pipe() 123 if err != nil { 124 t.Fatal("Pipe error:", err) 125 } 126 os.Stdin = readFromIn 127 logger.Print("pipe in created") 128 readFromErr, writeToErr, err := os.Pipe() 129 if err != nil { 130 t.Fatal("Pipe error:", err) 131 } 132 os.Stderr = writeToErr 133 logger.Print("pipe err created") 134 readFromOut, writeToOut, err := os.Pipe() 135 if err != nil { 136 t.Fatal("Pipe error:", err) 137 } 138 os.Stdout = writeToOut 139 logger.Print("pipe out created") 140 141 // setup reader 142 readerErr := bufio.NewReaderSize(readFromErr, 256) 143 readerOut := bufio.NewReaderSize(readFromOut, 256) 144 chanQuit := make(chan struct{}, 1) 145 chanErr := make(chan string, 3) 146 chanOut := make(chan string, 3) 147 readTimeout := 10 * time.Millisecond 148 var dataErr string 149 var dataOut string 150 151 go readerToChan(t, chanQuit, readerErr, chanErr) 152 go readerToChan(t, chanQuit, readerOut, chanOut) 153 154 go runInteractive() 155 156 time.Sleep(readTimeout) 157 158 // basic read and write to make sure things are working 159 _, err = writeToIn.WriteString("1\n") 160 if err != nil { 161 t.Fatal("Stdin WriteString error:", err) 162 } 163 select { 164 case dataOut = <-chanOut: 165 if len(dataOut) > 0 && dataOut[0] == '>' { 166 dataOut = dataOut[1:] 167 dataOut = strings.TrimSpace(dataOut) 168 } 169 if dataOut != "1" { 170 t.Fatalf("Stdout - received: %v - expected: %v - basic test", dataOut, "1") 171 } 172 case dataErr = <-chanErr: 173 dataErr = strings.TrimSpace(dataErr) 174 if dataErr != "" { 175 t.Fatalf("Stderr - received: %v - expected: %v - basic test", dataErr, "") 176 } 177 case <-time.After(readTimeout): 178 t.Fatal("read timeout for basic test") 179 } 180 181 // run tests 182 logger.Print("running tests start") 183 for _, test := range tests { 184 185 for i, runLine := range test.runLines { 186 187 _, err = writeToIn.WriteString(runLine + "\n") 188 if err != nil { 189 t.Fatal("Stdin WriteString error:", err) 190 } 191 192 select { 193 case <-time.After(readTimeout): 194 if test.runOutputs[i] != "" { 195 t.Fatalf("read timeout for i: %v - runLines: %v", i, test.runLines) 196 } 197 case dataOut = <-chanOut: 198 for len(dataOut) > 0 && dataOut[0] == '>' { 199 dataOut = dataOut[1:] 200 dataOut = strings.TrimSpace(dataOut) 201 } 202 if dataOut != test.runOutputs[i] { 203 t.Fatalf("Stdout - received: %v - expected: %v - i: %v - runLines: %v", dataOut, test.runOutputs[i], i, test.runLines) 204 } 205 case dataErr = <-chanErr: 206 dataErr = strings.TrimSpace(dataErr) 207 if dataErr != test.runError { 208 t.Fatalf("Stderr - received: %v - expected: %v - i: %v - runLines: %v", dataErr, test.runError, i, test.runLines) 209 } 210 211 // to clean output from error 212 _, err = writeToIn.WriteString("1\n") 213 if err != nil { 214 t.Fatal("Stdin WriteString error:", err) 215 } 216 217 select { 218 case dataOut = <-chanOut: 219 for len(dataOut) > 0 && dataOut[0] == '>' { 220 dataOut = dataOut[1:] 221 dataOut = strings.TrimSpace(dataOut) 222 } 223 if dataOut != "1" { 224 t.Fatalf("Stdout - received: %v - expected: %v - i: %v - runLines: %v", dataOut, test.runOutputs[i], i, test.runLines) 225 } 226 case <-time.After(readTimeout): 227 t.Fatalf("read timeout for i: %v - runLines: %v", i, test.runLines) 228 } 229 230 } 231 232 } 233 234 } 235 logger.Print("running tests end") 236 237 // quit 238 _, err = writeToIn.WriteString("quit()\n") 239 if err != nil { 240 t.Fatal("Stdin WriteString error:", err) 241 } 242 logger.Print("quit() sent") 243 244 close(chanQuit) 245 logger.Print("chanQuit closed") 246 247 writeToErr.Close() 248 writeToOut.Close() 249 logger.Print("pipes closed") 250 } 251 252 func readerToChan(t *testing.T, chanQuit chan struct{}, reader *bufio.Reader, chanOut chan string) { 253 logger.Print("readerToChan start") 254 for { 255 data, err := reader.ReadString('\n') 256 if err != nil && err != io.EOF { 257 t.Fatal("readerToChan ReadString error:", err) 258 } 259 select { 260 case <-chanQuit: 261 logger.Print("readerToChan end") 262 return 263 default: 264 } 265 chanOut <- data 266 } 267 } 268 269 func setStd(stdin *os.File, stderr *os.File, stdout *os.File) { 270 os.Stdin = stdin 271 os.Stderr = stderr 272 os.Stdout = stdout 273 }