github.com/grumpyhome/grumpy@v0.3.1-0.20201208125205-7b775405bdf1/grumpy-runtime-src/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  }