github.com/google/grumpy@v0.0.0-20171122020858-3ec87959189c/runtime/file_test.go (about) 1 // Copyright 2016 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package grumpy 16 17 import ( 18 "fmt" 19 "io/ioutil" 20 "os" 21 "regexp" 22 "testing" 23 ) 24 25 var ( 26 wantFileOperationRead = 1 27 wantFileOperationWrite = 2 28 ) 29 30 func TestFileInit(t *testing.T) { 31 f := newTestFile("blah blah") 32 defer f.cleanup() 33 cases := []invokeTestCase{ 34 {args: wrapArgs(newObject(FileType), f.path), want: None}, 35 {args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(TypeErrorType, "'__init__' requires 2 arguments")}, 36 {args: wrapArgs(newObject(FileType), f.path, "abc"), wantExc: mustCreateException(ValueErrorType, `invalid mode string: "abc"`)}, 37 {args: wrapArgs(newObject(FileType), "nonexistent-file"), wantExc: mustCreateException(IOErrorType, "open nonexistent-file: no such file or directory")}, 38 } 39 for _, cas := range cases { 40 if err := runInvokeMethodTestCase(FileType, "__init__", &cas); err != "" { 41 t.Error(err) 42 } 43 } 44 } 45 46 func TestFileClosed(t *testing.T) { 47 f := newTestFile("foo\nbar") 48 defer f.cleanup() 49 closedFile := f.open("r") 50 // This puts the file into an invalid state since Grumpy thinks 51 // it's open even though the underlying file was closed. 52 closedFile.file.Close() 53 cases := []invokeTestCase{ 54 {args: wrapArgs(newObject(FileType)), want: True.ToObject()}, 55 {args: wrapArgs(f.open("r")), want: False.ToObject()}, 56 {args: wrapArgs(closedFile), want: False.ToObject()}, 57 } 58 for _, cas := range cases { 59 if err := runInvokeMethodTestCase(FileType, "closed", &cas); err != "" { 60 t.Error(err) 61 } 62 } 63 } 64 65 func TestFileCloseExit(t *testing.T) { 66 f := newTestFile("foo\nbar") 67 defer f.cleanup() 68 for _, method := range []string{"close", "__exit__"} { 69 closedFile := f.open("r") 70 // This puts the file into an invalid state since Grumpy thinks 71 // it's open even though the underlying file was closed. 72 closedFile.file.Close() 73 cases := []invokeTestCase{ 74 {args: wrapArgs(newObject(FileType)), want: None}, 75 {args: wrapArgs(f.open("r")), want: None}, 76 {args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFile.file.Close().Error())}, 77 } 78 for _, cas := range cases { 79 if err := runInvokeMethodTestCase(FileType, method, &cas); err != "" { 80 t.Error(err) 81 } 82 } 83 } 84 } 85 86 func TestFileGetName(t *testing.T) { 87 fun := wrapFuncForTest(func(f *Frame, file *File) (*Object, *BaseException) { 88 return GetAttr(f, file.ToObject(), NewStr("name"), nil) 89 }) 90 foo := newTestFile("foo") 91 defer foo.cleanup() 92 cases := []invokeTestCase{ 93 {args: wrapArgs(foo.open("r")), want: NewStr(foo.path).ToObject()}, 94 {args: wrapArgs(newObject(FileType)), want: NewStr("<uninitialized file>").ToObject()}, 95 } 96 for _, cas := range cases { 97 if err := runInvokeTestCase(fun, &cas); err != "" { 98 t.Error(err) 99 } 100 } 101 } 102 103 func TestFileIter(t *testing.T) { 104 files := makeTestFiles() 105 defer files.cleanup() 106 closedFile := files[0].open("r") 107 closedFile.file.Close() 108 _, closedFileReadError := closedFile.file.Read(make([]byte, 10)) 109 cases := []invokeTestCase{ 110 {args: wrapArgs(files[0].open("r")), want: newTestList("foo").ToObject()}, 111 {args: wrapArgs(files[0].open("rU")), want: newTestList("foo").ToObject()}, 112 {args: wrapArgs(files[1].open("r")), want: newTestList("foo\n").ToObject()}, 113 {args: wrapArgs(files[1].open("rU")), want: newTestList("foo\n").ToObject()}, 114 {args: wrapArgs(files[2].open("r")), want: newTestList("foo\n", "bar").ToObject()}, 115 {args: wrapArgs(files[2].open("rU")), want: newTestList("foo\n", "bar").ToObject()}, 116 {args: wrapArgs(files[3].open("r")), want: newTestList("foo\r\n").ToObject()}, 117 {args: wrapArgs(files[3].open("rU")), want: newTestList("foo\n").ToObject()}, 118 {args: wrapArgs(files[4].open("r")), want: newTestList("foo\rbar").ToObject()}, 119 {args: wrapArgs(files[4].open("rU")), want: newTestList("foo\n", "bar").ToObject()}, 120 {args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFileReadError.Error())}, 121 {args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(ValueErrorType, "I/O operation on closed file")}, 122 } 123 for _, cas := range cases { 124 if err := runInvokeTestCase(ListType.ToObject(), &cas); err != "" { 125 t.Error(err) 126 } 127 } 128 } 129 130 func TestFileNext(t *testing.T) { 131 files := makeTestFiles() 132 defer files.cleanup() 133 closedFile := files[0].open("r") 134 closedFile.file.Close() 135 _, closedFileReadError := closedFile.file.Read(make([]byte, 10)) 136 cases := []invokeTestCase{ 137 {args: wrapArgs(files[0].open("r")), want: NewStr("foo").ToObject()}, 138 {args: wrapArgs(files[0].open("rU")), want: NewStr("foo").ToObject()}, 139 {args: wrapArgs(files[1].open("r")), want: NewStr("foo\n").ToObject()}, 140 {args: wrapArgs(files[1].open("rU")), want: NewStr("foo\n").ToObject()}, 141 {args: wrapArgs(files[2].open("r")), want: NewStr("foo\n").ToObject()}, 142 {args: wrapArgs(files[2].open("rU")), want: NewStr("foo\n").ToObject()}, 143 {args: wrapArgs(files[3].open("r")), want: NewStr("foo\r\n").ToObject()}, 144 {args: wrapArgs(files[3].open("rU")), want: NewStr("foo\n").ToObject()}, 145 {args: wrapArgs(files[4].open("r")), want: NewStr("foo\rbar").ToObject()}, 146 {args: wrapArgs(files[4].open("rU")), want: NewStr("foo\n").ToObject()}, 147 {args: wrapArgs(), wantExc: mustCreateException(TypeErrorType, "unbound method next() must be called with file instance as first argument (got nothing instead)")}, 148 {args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFileReadError.Error())}, 149 {args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(ValueErrorType, "I/O operation on closed file")}, 150 } 151 for _, cas := range cases { 152 if err := runInvokeMethodTestCase(FileType, "next", &cas); err != "" { 153 t.Error(err) 154 } 155 } 156 } 157 158 func TestFileRead(t *testing.T) { 159 f := newTestFile("foo\nbar") 160 defer f.cleanup() 161 closedFile := f.open("r") 162 closedFile.file.Close() 163 _, closedFileReadError := closedFile.file.Read(make([]byte, 10)) 164 cases := []invokeTestCase{ 165 {args: wrapArgs(f.open("r")), want: NewStr("foo\nbar").ToObject()}, 166 {args: wrapArgs(f.open("r"), 3), want: NewStr("foo").ToObject()}, 167 {args: wrapArgs(f.open("r"), 1000), want: NewStr("foo\nbar").ToObject()}, 168 {args: wrapArgs(), wantExc: mustCreateException(TypeErrorType, "unbound method read() must be called with file instance as first argument (got nothing instead)")}, 169 {args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFileReadError.Error())}, 170 {args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(ValueErrorType, "I/O operation on closed file")}, 171 {args: wrapArgs(newObject(FileType), "abc"), wantExc: mustCreateException(ValueErrorType, "invalid literal for int() with base 10: abc")}, 172 {args: wrapArgs(newObject(FileType), 123, 456), wantExc: mustCreateException(TypeErrorType, "'read' of 'file' requires 2 arguments")}, 173 } 174 for _, cas := range cases { 175 if err := runInvokeMethodTestCase(FileType, "read", &cas); err != "" { 176 t.Error(err) 177 } 178 } 179 } 180 181 func TestFileReadLine(t *testing.T) { 182 files := makeTestFiles() 183 defer files.cleanup() 184 closedFile := files[0].open("r") 185 closedFile.file.Close() 186 _, closedFileReadError := closedFile.file.Read(make([]byte, 10)) 187 partialReadFile := files[5].open("rU") 188 partialReadFile.readLine(-1) 189 cases := []invokeTestCase{ 190 {args: wrapArgs(files[0].open("r")), want: NewStr("foo").ToObject()}, 191 {args: wrapArgs(files[0].open("rU")), want: NewStr("foo").ToObject()}, 192 {args: wrapArgs(files[1].open("r")), want: NewStr("foo\n").ToObject()}, 193 {args: wrapArgs(files[1].open("rU")), want: NewStr("foo\n").ToObject()}, 194 {args: wrapArgs(files[2].open("r")), want: NewStr("foo\n").ToObject()}, 195 {args: wrapArgs(files[2].open("rU")), want: NewStr("foo\n").ToObject()}, 196 {args: wrapArgs(files[2].open("r"), 2), want: NewStr("fo").ToObject()}, 197 {args: wrapArgs(files[2].open("r"), 3), want: NewStr("foo").ToObject()}, 198 {args: wrapArgs(files[2].open("r"), 4), want: NewStr("foo\n").ToObject()}, 199 {args: wrapArgs(files[2].open("r"), 5), want: NewStr("foo\n").ToObject()}, 200 {args: wrapArgs(files[3].open("r")), want: NewStr("foo\r\n").ToObject()}, 201 {args: wrapArgs(files[3].open("rU")), want: NewStr("foo\n").ToObject()}, 202 {args: wrapArgs(files[3].open("rU"), 3), want: NewStr("foo").ToObject()}, 203 {args: wrapArgs(files[3].open("rU"), 4), want: NewStr("foo\n").ToObject()}, 204 {args: wrapArgs(files[3].open("rU"), 5), want: NewStr("foo\n").ToObject()}, 205 {args: wrapArgs(files[4].open("r")), want: NewStr("foo\rbar").ToObject()}, 206 {args: wrapArgs(files[4].open("rU")), want: NewStr("foo\n").ToObject()}, 207 // Ensure that reading after a \r\n returns the requested 208 // number of bytes when possible. Check that the trailing \n 209 // does not count toward the bytes read. 210 {args: wrapArgs(partialReadFile, 3), want: NewStr("bar").ToObject()}, 211 {args: wrapArgs(), wantExc: mustCreateException(TypeErrorType, "unbound method readline() must be called with file instance as first argument (got nothing instead)")}, 212 {args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFileReadError.Error())}, 213 {args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(ValueErrorType, "I/O operation on closed file")}, 214 {args: wrapArgs(newObject(FileType), "abc"), wantExc: mustCreateException(ValueErrorType, "invalid literal for int() with base 10: abc")}, 215 {args: wrapArgs(newObject(FileType), 123, 456), wantExc: mustCreateException(TypeErrorType, "'readline' of 'file' requires 2 arguments")}, 216 } 217 for _, cas := range cases { 218 if err := runInvokeMethodTestCase(FileType, "readline", &cas); err != "" { 219 t.Error(err) 220 } 221 } 222 } 223 224 func TestFileReadLines(t *testing.T) { 225 files := makeTestFiles() 226 defer files.cleanup() 227 closedFile := files[0].open("r") 228 closedFile.file.Close() 229 _, closedFileReadError := closedFile.file.Read(make([]byte, 10)) 230 partialReadFile := files[5].open("rU") 231 partialReadFile.readLine(-1) 232 cases := []invokeTestCase{ 233 {args: wrapArgs(files[0].open("r")), want: newTestList("foo").ToObject()}, 234 {args: wrapArgs(files[0].open("rU")), want: newTestList("foo").ToObject()}, 235 {args: wrapArgs(files[1].open("r")), want: newTestList("foo\n").ToObject()}, 236 {args: wrapArgs(files[1].open("rU")), want: newTestList("foo\n").ToObject()}, 237 {args: wrapArgs(files[2].open("r")), want: newTestList("foo\n", "bar").ToObject()}, 238 {args: wrapArgs(files[2].open("rU")), want: newTestList("foo\n", "bar").ToObject()}, 239 {args: wrapArgs(files[2].open("r"), 2), want: newTestList("foo\n").ToObject()}, 240 {args: wrapArgs(files[2].open("r"), 3), want: newTestList("foo\n").ToObject()}, 241 {args: wrapArgs(files[2].open("r"), 4), want: newTestList("foo\n").ToObject()}, 242 {args: wrapArgs(files[2].open("r"), 5), want: newTestList("foo\n", "bar").ToObject()}, 243 {args: wrapArgs(files[3].open("r")), want: newTestList("foo\r\n").ToObject()}, 244 {args: wrapArgs(files[3].open("rU")), want: newTestList("foo\n").ToObject()}, 245 {args: wrapArgs(files[3].open("rU"), 3), want: newTestList("foo\n").ToObject()}, 246 {args: wrapArgs(files[3].open("rU"), 4), want: newTestList("foo\n").ToObject()}, 247 {args: wrapArgs(files[3].open("rU"), 5), want: newTestList("foo\n").ToObject()}, 248 {args: wrapArgs(files[4].open("r")), want: newTestList("foo\rbar").ToObject()}, 249 {args: wrapArgs(files[4].open("rU")), want: newTestList("foo\n", "bar").ToObject()}, 250 // Ensure that reading after a \r\n returns the requested 251 // number of bytes when possible. Check that the trailing \n 252 // does not count toward the bytes read. 253 {args: wrapArgs(partialReadFile, 3), want: newTestList("bar\n").ToObject()}, 254 {args: wrapArgs(), wantExc: mustCreateException(TypeErrorType, "unbound method readlines() must be called with file instance as first argument (got nothing instead)")}, 255 {args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFileReadError.Error())}, 256 {args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(ValueErrorType, "I/O operation on closed file")}, 257 {args: wrapArgs(newObject(FileType), "abc"), wantExc: mustCreateException(ValueErrorType, "invalid literal for int() with base 10: abc")}, 258 {args: wrapArgs(newObject(FileType), 123, 456), wantExc: mustCreateException(TypeErrorType, "'readlines' of 'file' requires 2 arguments")}, 259 } 260 for _, cas := range cases { 261 if err := runInvokeMethodTestCase(FileType, "readlines", &cas); err != "" { 262 t.Error(err) 263 } 264 } 265 } 266 267 func TestFileStrRepr(t *testing.T) { 268 fun := newBuiltinFunction("TestFileStrRepr", func(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 269 if raised := checkFunctionArgs(f, "TestFileStrRepr", args, ObjectType, StrType); raised != nil { 270 return nil, raised 271 } 272 o := args[0] 273 re := regexp.MustCompile(toStrUnsafe(args[1]).Value()) 274 s, raised := ToStr(f, o) 275 if raised != nil { 276 return nil, raised 277 } 278 Assert(f, GetBool(re.MatchString(s.Value())).ToObject(), nil) 279 s, raised = Repr(f, o) 280 if raised != nil { 281 return nil, raised 282 } 283 Assert(f, GetBool(re.MatchString(s.Value())).ToObject(), nil) 284 return None, nil 285 }).ToObject() 286 f := newTestFile("foo\nbar") 287 defer f.cleanup() 288 closedFile := f.open("r").ToObject() 289 mustNotRaise(fileClose(NewRootFrame(), []*Object{closedFile}, nil)) 290 // Open a file for write. 291 writeFile := f.open("wb") 292 cases := []invokeTestCase{ 293 {args: wrapArgs(f.open("r"), `^<open file "[^"]+", mode "r" at \w+>$`), want: None}, 294 {args: wrapArgs(writeFile, `^<open file "[^"]+", mode "wb" at \w+>$`), want: None}, 295 {args: wrapArgs(newObject(FileType), `^<closed file "<uninitialized file>", mode "<uninitialized file>" at \w+>$`), want: None}, 296 {args: wrapArgs(closedFile, `^<closed file "[^"]+", mode "r" at \w+>$`), want: None}, 297 } 298 for _, cas := range cases { 299 if err := runInvokeTestCase(fun, &cas); err != "" { 300 t.Error(err) 301 } 302 } 303 } 304 305 func TestFileWrite(t *testing.T) { 306 fun := newBuiltinFunction("TestFileWrite", func(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) { 307 if raised := checkMethodArgs(f, "TestFileWrite", args, StrType, StrType, StrType); raised != nil { 308 return nil, raised 309 } 310 writeFile, raised := FileType.Call(f, args[:2], nil) 311 if raised != nil { 312 return nil, raised 313 } 314 write, raised := GetAttr(f, writeFile, NewStr("write"), nil) 315 if raised != nil { 316 return nil, raised 317 } 318 if _, raised := write.Call(f, args[2:], nil); raised != nil { 319 return nil, raised 320 } 321 contents, err := ioutil.ReadFile(toStrUnsafe(args[0]).Value()) 322 if err != nil { 323 return nil, f.RaiseType(RuntimeErrorType, fmt.Sprintf("error reading file: %s", err.Error())) 324 } 325 return NewStr(string(contents)).ToObject(), nil 326 }).ToObject() 327 // Create a temporary directory and cd to it. 328 dir, err := ioutil.TempDir("", "TestFileWrite") 329 if err != nil { 330 t.Fatalf("failed to create temp dir: %v", err) 331 } 332 defer os.RemoveAll(dir) 333 oldWd, err := os.Getwd() 334 if err != nil { 335 t.Fatalf("Getwd() failed: %s", err) 336 } 337 if err := os.Chdir(dir); err != nil { 338 t.Fatalf("Chdir(%q) failed: %s", dir, err) 339 } 340 defer os.Chdir(oldWd) 341 for _, filename := range []string{"truncate.txt", "readonly.txt", "append.txt", "rplus.txt", "aplus.txt", "wplus.txt"} { 342 if err := ioutil.WriteFile(filename, []byte(filename), 0644); err != nil { 343 t.Fatalf("ioutil.WriteFile(%q) failed: %s", filename, err) 344 } 345 } 346 cases := []invokeTestCase{ 347 {args: wrapArgs("noexist.txt", "w", "foo\nbar"), want: NewStr("foo\nbar").ToObject()}, 348 {args: wrapArgs("truncate.txt", "w", "new contents"), want: NewStr("new contents").ToObject()}, 349 {args: wrapArgs("append.txt", "a", "\nbar"), want: NewStr("append.txt\nbar").ToObject()}, 350 351 {args: wrapArgs("rplus.txt", "r+", "fooey"), want: NewStr("fooey.txt").ToObject()}, 352 {args: wrapArgs("noexistplus1.txt", "r+", "pooey"), wantExc: mustCreateException(IOErrorType, "open noexistplus1.txt: no such file or directory")}, 353 354 {args: wrapArgs("aplus.txt", "a+", "\napper"), want: NewStr("aplus.txt\napper").ToObject()}, 355 {args: wrapArgs("noexistplus3.txt", "a+", "snappbacktoreality"), want: NewStr("snappbacktoreality").ToObject()}, 356 357 {args: wrapArgs("wplus.txt", "w+", "destructo"), want: NewStr("destructo").ToObject()}, 358 {args: wrapArgs("noexistplus2.txt", "w+", "wapper"), want: NewStr("wapper").ToObject()}, 359 360 {args: wrapArgs("readonly.txt", "r", "foo"), wantExc: mustCreateException(IOErrorType, "write readonly.txt: bad file descriptor")}, 361 } 362 for _, cas := range cases { 363 if err := runInvokeTestCase(fun, &cas); err != "" { 364 t.Error(err) 365 } 366 } 367 } 368 369 type testFile struct { 370 path string 371 files []*File 372 } 373 374 func newTestFile(contents string) *testFile { 375 osFile, err := ioutil.TempFile("", "") 376 if err != nil { 377 panic(err) 378 } 379 if _, err := osFile.WriteString(contents); err != nil { 380 panic(err) 381 } 382 if err := osFile.Close(); err != nil { 383 panic(err) 384 } 385 return &testFile{path: osFile.Name()} 386 } 387 388 func (f *testFile) cleanup() { 389 for _, file := range f.files { 390 file.file.Close() 391 } 392 os.Remove(f.path) 393 } 394 395 func (f *testFile) open(mode string) *File { 396 args := wrapArgs(f.path, mode) 397 o := mustNotRaise(FileType.Call(NewRootFrame(), args, nil)) 398 if o == nil || !o.isInstance(FileType) { 399 panic(fmt.Sprintf("file%v = %v, want file object", args, o)) 400 } 401 file := toFileUnsafe(o) 402 f.files = append(f.files, file) 403 return file 404 } 405 406 type testFileSlice []*testFile 407 408 func makeTestFiles() testFileSlice { 409 return []*testFile{ 410 newTestFile("foo"), 411 newTestFile("foo\n"), 412 newTestFile("foo\nbar"), 413 newTestFile("foo\r\n"), 414 newTestFile("foo\rbar"), 415 newTestFile("foo\r\nbar\r\nbaz"), 416 } 417 } 418 419 func (files testFileSlice) cleanup() { 420 for _, f := range files { 421 f.cleanup() 422 } 423 }