Commit b308355b authored by Dylan Trotter's avatar Dylan Trotter

Implement file readline, readlines, iter, next, __enter__, __exit__ methods.

parent d800f9b5
......@@ -15,10 +15,14 @@
package grumpy
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"reflect"
"strings"
"sync"
)
......@@ -28,10 +32,13 @@ type File struct {
// mutex synchronizes the state of the File struct, not access to the
// underlying os.File. So, for example, when doing file reads and
// writes we only acquire a read lock.
mutex sync.RWMutex
mode string
open bool
file *os.File
mutex sync.Mutex
mode string
open bool
reader *bufio.Reader
file *os.File
skipNextLF bool
univNewLine bool
}
// NewFileFromFD creates a file object from the given file descriptor fd.
......@@ -49,6 +56,37 @@ func (f *File) ToObject() *Object {
return &f.Object
}
func (f *File) readLine(maxBytes int) (string, error) {
var buf bytes.Buffer
numBytesRead := 0
for maxBytes < 0 || numBytesRead < maxBytes {
b, err := f.reader.ReadByte()
if err == io.EOF {
break
}
if err != nil {
return "", err
}
if b == '\r' && f.univNewLine {
f.skipNextLF = true
buf.WriteByte('\n')
break
} else if b == '\n' {
if f.skipNextLF {
f.skipNextLF = false
continue // Do not increment numBytesRead.
} else {
buf.WriteByte(b)
break
}
} else {
buf.WriteByte(b)
}
numBytesRead++
}
return buf.String(), nil
}
// FileType is the object representing the Python 'file' type.
var FileType = newBasisType("file", reflect.TypeOf(File{}), toFileUnsafe, ObjectType)
......@@ -70,14 +108,14 @@ func fileInit(f *Frame, o *Object, args Args, _ KWArgs) (*Object, *BaseException
switch mode {
case "a", "ab":
flag = os.O_WRONLY | os.O_CREATE | os.O_APPEND
case "r", "rb":
case "r", "rb", "rU", "U":
flag = os.O_RDONLY
case "r+", "r+b":
flag = os.O_RDWR
case "w", "wb":
flag = os.O_WRONLY | os.O_CREATE | os.O_TRUNC
default:
return nil, f.RaiseType(ValueErrorType, "invalid mode string")
return nil, f.RaiseType(ValueErrorType, fmt.Sprintf("invalid mode string: %q", mode))
}
file := toFileUnsafe(o)
file.mutex.Lock()
......@@ -89,6 +127,30 @@ func fileInit(f *Frame, o *Object, args Args, _ KWArgs) (*Object, *BaseException
file.mode = mode
file.open = true
file.file = osFile
file.reader = bufio.NewReader(osFile)
file.univNewLine = strings.HasSuffix(mode, "U")
return None, nil
}
func fileEnter(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
if raised := checkMethodArgs(f, "__enter__", args, FileType); raised != nil {
return nil, raised
}
return args[0], nil
}
func fileExit(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
if raised := checkMethodVarArgs(f, "__exit__", args, FileType); raised != nil {
return nil, raised
}
closeFunc, raised := GetAttr(f, args[0], NewStr("close"), nil)
if raised != nil {
return nil, raised
}
_, raised = closeFunc.Call(f, nil, nil)
if raised != nil {
return nil, raised
}
return None, nil
}
......@@ -108,22 +170,34 @@ func fileClose(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
return None, nil
}
func fileRead(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
expectedTypes := []*Type{FileType, IntType}
argc := len(args)
if argc == 1 {
expectedTypes = expectedTypes[:1]
func fileIter(f *Frame, o *Object) (*Object, *BaseException) {
return o, nil
}
func fileNext(f *Frame, o *Object) (ret *Object, raised *BaseException) {
file := toFileUnsafe(o)
file.mutex.Lock()
defer file.mutex.Unlock()
if !file.open {
return nil, f.RaiseType(ValueErrorType, "I/O operation on closed file")
}
if raised := checkMethodArgs(f, "read", args, expectedTypes...); raised != nil {
return nil, raised
line, err := file.readLine(-1)
if err != nil {
return nil, f.RaiseType(IOErrorType, err.Error())
}
file := toFileUnsafe(args[0])
size := -1
if argc > 1 {
size = toIntUnsafe(args[1]).Value()
if line == "" {
return nil, f.Raise(StopIterationType.ToObject(), nil, nil)
}
return NewStr(line).ToObject(), nil
}
func fileRead(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
file, size, raised := fileParseReadArgs(f, "read", args)
if raised != nil {
return nil, raised
}
file.mutex.RLock()
defer file.mutex.RUnlock()
file.mutex.Lock()
defer file.mutex.Unlock()
if !file.open {
return nil, f.RaiseType(ValueErrorType, "I/O operation on closed file")
}
......@@ -134,7 +208,7 @@ func fileRead(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
} else {
data = make([]byte, size)
var n int
n, err = file.file.Read(data)
n, err = file.reader.Read(data)
data = data[:n]
}
if err != nil {
......@@ -143,10 +217,58 @@ func fileRead(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
return NewStr(string(data)).ToObject(), nil
}
func fileReadLine(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
file, size, raised := fileParseReadArgs(f, "readline", args)
if raised != nil {
return nil, raised
}
file.mutex.Lock()
defer file.mutex.Unlock()
if !file.open {
return nil, f.RaiseType(ValueErrorType, "I/O operation on closed file")
}
line, err := file.readLine(size)
if err != nil {
return nil, f.RaiseType(IOErrorType, err.Error())
}
return NewStr(line).ToObject(), nil
}
func fileReadLines(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
// NOTE: The size hint behavior here is slightly different than
// CPython. Here we read no more lines than necessary. In CPython a
// minimum of 8KB or more will be read.
file, size, raised := fileParseReadArgs(f, "readlines", args)
if raised != nil {
return nil, raised
}
file.mutex.Lock()
defer file.mutex.Unlock()
if !file.open {
return nil, f.RaiseType(ValueErrorType, "I/O operation on closed file")
}
var lines []*Object
numBytesRead := 0
for size < 0 || numBytesRead < size {
line, err := file.readLine(-1)
if err != nil {
return nil, f.RaiseType(IOErrorType, err.Error())
}
if line != "" {
lines = append(lines, NewStr(line).ToObject())
}
if !strings.HasSuffix(line, "\n") {
break
}
numBytesRead += len(line)
}
return NewList(lines...).ToObject(), nil
}
func fileRepr(f *Frame, o *Object) (*Object, *BaseException) {
file := toFileUnsafe(o)
file.mutex.RLock()
defer file.mutex.RUnlock()
file.mutex.Lock()
defer file.mutex.Unlock()
var openState string
if file.open {
openState = "open"
......@@ -173,8 +295,8 @@ func fileWrite(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
return nil, raised
}
file := toFileUnsafe(args[0])
file.mutex.RLock()
defer file.mutex.RUnlock()
file.mutex.Lock()
defer file.mutex.Unlock()
if !file.open {
return nil, f.RaiseType(ValueErrorType, "I/O operation on closed file")
}
......@@ -185,9 +307,36 @@ func fileWrite(f *Frame, args Args, _ KWArgs) (*Object, *BaseException) {
}
func initFileType(dict map[string]*Object) {
// TODO: Make enter/exit into slots.
dict["__enter__"] = newBuiltinFunction("__enter__", fileEnter).ToObject()
dict["__exit__"] = newBuiltinFunction("__exit__", fileExit).ToObject()
dict["close"] = newBuiltinFunction("close", fileClose).ToObject()
dict["read"] = newBuiltinFunction("read", fileRead).ToObject()
dict["readline"] = newBuiltinFunction("readline", fileReadLine).ToObject()
dict["readlines"] = newBuiltinFunction("readlines", fileReadLines).ToObject()
dict["write"] = newBuiltinFunction("write", fileWrite).ToObject()
FileType.slots.Init = &initSlot{fileInit}
FileType.slots.Iter = &unaryOpSlot{fileIter}
FileType.slots.Next = &unaryOpSlot{fileNext}
FileType.slots.Repr = &unaryOpSlot{fileRepr}
}
func fileParseReadArgs(f *Frame, method string, args Args) (*File, int, *BaseException) {
expectedTypes := []*Type{FileType, ObjectType}
argc := len(args)
if argc == 1 {
expectedTypes = expectedTypes[:1]
}
if raised := checkMethodArgs(f, method, args, expectedTypes...); raised != nil {
return nil, 0, raised
}
size := -1
if argc > 1 {
o, raised := IntType.Call(f, args[1:], nil)
if raised != nil {
return nil, 0, raised
}
size = toIntUnsafe(o).Value()
}
return toFileUnsafe(args[0]), size, nil
}
......@@ -28,67 +28,203 @@ var (
)
func TestFileInit(t *testing.T) {
tempFilename := mustCreateTempFile("TestFileInit", "blah blah")
defer os.Remove(tempFilename)
f := newTestFile("blah blah")
defer f.cleanup()
cases := []invokeTestCase{
{args: wrapArgs(newObject(FileType), tempFilename), want: None},
{args: wrapArgs(newObject(FileType), f.path), want: None},
{args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(TypeErrorType, "'__init__' requires 2 arguments")},
{args: wrapArgs(newObject(FileType), tempFilename, "abc"), wantExc: mustCreateException(ValueErrorType, "invalid mode string")},
{args: wrapArgs(newObject(FileType), tempFilename, "w+"), wantExc: mustCreateException(ValueErrorType, "invalid mode string")},
{args: wrapArgs(newObject(FileType), f.path, "abc"), wantExc: mustCreateException(ValueErrorType, `invalid mode string: "abc"`)},
{args: wrapArgs(newObject(FileType), f.path, "w+"), wantExc: mustCreateException(ValueErrorType, `invalid mode string: "w+"`)},
{args: wrapArgs(newObject(FileType), "nonexistent-file"), wantExc: mustCreateException(IOErrorType, "open nonexistent-file: no such file or directory")},
}
for _, cas := range cases {
if err := runInvokeMethodTestCase(FileType, "__init__", &cas); err != "" {
t.Error(err)
}
if len(cas.args) > 0 && cas.args[0].isInstance(FileType) {
toFileUnsafe(cas.args[0]).file.Close()
}
}
func TestFileCloseExit(t *testing.T) {
f := newTestFile("foo\nbar")
defer f.cleanup()
for _, method := range []string{"close", "__exit__"} {
closedFile := f.open("r")
// This puts the file into an invalid state since Grumpy thinks
// it's open even though the underlying file was closed.
closedFile.file.Close()
cases := []invokeTestCase{
{args: wrapArgs(newObject(FileType)), want: None},
{args: wrapArgs(f.open("r")), want: None},
{args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, "invalid argument")},
}
for _, cas := range cases {
if err := runInvokeMethodTestCase(FileType, method, &cas); err != "" {
t.Error(err)
}
}
}
}
func TestFileClose(t *testing.T) {
tempFilename := mustCreateTempFile("TestFileClose", "foo\nbar")
defer os.Remove(tempFilename)
closedFile := mustOpenFile(tempFilename)
// This puts the file into an invalid state since Grumpy thinks
// it's open even though the underlying file was closed.
func TestFileIter(t *testing.T) {
files := makeTestFiles()
defer files.cleanup()
closedFile := files[0].open("r")
closedFile.file.Close()
_, closedFileReadError := closedFile.file.Read(make([]byte, 10))
cases := []invokeTestCase{
{args: wrapArgs(newObject(FileType)), want: None},
{args: wrapArgs(mustOpenFile(tempFilename)), want: None},
{args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, "invalid argument")},
{args: wrapArgs(files[0].open("r")), want: newTestList("foo").ToObject()},
{args: wrapArgs(files[0].open("rU")), want: newTestList("foo").ToObject()},
{args: wrapArgs(files[1].open("r")), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[1].open("rU")), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[2].open("r")), want: newTestList("foo\n", "bar").ToObject()},
{args: wrapArgs(files[2].open("rU")), want: newTestList("foo\n", "bar").ToObject()},
{args: wrapArgs(files[3].open("r")), want: newTestList("foo\r\n").ToObject()},
{args: wrapArgs(files[3].open("rU")), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[4].open("r")), want: newTestList("foo\rbar").ToObject()},
{args: wrapArgs(files[4].open("rU")), want: newTestList("foo\n", "bar").ToObject()},
{args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFileReadError.Error())},
{args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(ValueErrorType, "I/O operation on closed file")},
}
for _, cas := range cases {
if err := runInvokeMethodTestCase(FileType, "close", &cas); err != "" {
if err := runInvokeTestCase(ListType.ToObject(), &cas); err != "" {
t.Error(err)
}
if len(cas.args) > 0 && cas.args[0].isInstance(FileType) {
toFileUnsafe(cas.args[0]).file.Close()
}
}
func TestFileNext(t *testing.T) {
files := makeTestFiles()
defer files.cleanup()
closedFile := files[0].open("r")
closedFile.file.Close()
_, closedFileReadError := closedFile.file.Read(make([]byte, 10))
cases := []invokeTestCase{
{args: wrapArgs(files[0].open("r")), want: NewStr("foo").ToObject()},
{args: wrapArgs(files[0].open("rU")), want: NewStr("foo").ToObject()},
{args: wrapArgs(files[1].open("r")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[1].open("rU")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[2].open("r")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[2].open("rU")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[3].open("r")), want: NewStr("foo\r\n").ToObject()},
{args: wrapArgs(files[3].open("rU")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[4].open("r")), want: NewStr("foo\rbar").ToObject()},
{args: wrapArgs(files[4].open("rU")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(), wantExc: mustCreateException(TypeErrorType, "unbound method next() must be called with file instance as first argument (got nothing instead)")},
{args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFileReadError.Error())},
{args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(ValueErrorType, "I/O operation on closed file")},
}
for _, cas := range cases {
if err := runInvokeMethodTestCase(FileType, "next", &cas); err != "" {
t.Error(err)
}
}
}
func TestFileRead(t *testing.T) {
tempFilename := mustCreateTempFile("TestFileRead", "foo\nbar")
defer os.Remove(tempFilename)
closedFile := mustOpenFile(tempFilename)
f := newTestFile("foo\nbar")
defer f.cleanup()
closedFile := f.open("r")
closedFile.file.Close()
_, closedFileReadError := closedFile.file.Read(make([]byte, 10))
cases := []invokeTestCase{
{args: wrapArgs(mustOpenFile(tempFilename)), want: NewStr("foo\nbar").ToObject()},
{args: wrapArgs(mustOpenFile(tempFilename), 3), want: NewStr("foo").ToObject()},
{args: wrapArgs(mustOpenFile(tempFilename), 1000), want: NewStr("foo\nbar").ToObject()},
{args: wrapArgs(f.open("r")), want: NewStr("foo\nbar").ToObject()},
{args: wrapArgs(f.open("r"), 3), want: NewStr("foo").ToObject()},
{args: wrapArgs(f.open("r"), 1000), want: NewStr("foo\nbar").ToObject()},
{args: wrapArgs(), wantExc: mustCreateException(TypeErrorType, "unbound method read() must be called with file instance as first argument (got nothing instead)")},
{args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFileReadError.Error())},
{args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(ValueErrorType, "I/O operation on closed file")},
{args: wrapArgs(newObject(FileType), "abc"), wantExc: mustCreateException(ValueErrorType, "invalid literal for int() with base 10: abc")},
{args: wrapArgs(newObject(FileType), 123, 456), wantExc: mustCreateException(TypeErrorType, "'read' of 'file' requires 2 arguments")},
}
for _, cas := range cases {
if err := runInvokeMethodTestCase(FileType, "read", &cas); err != "" {
t.Error(err)
}
if len(cas.args) > 0 && cas.args[0].isInstance(FileType) {
toFileUnsafe(cas.args[0]).file.Close()
}
}
func TestFileReadLine(t *testing.T) {
files := makeTestFiles()
defer files.cleanup()
closedFile := files[0].open("r")
closedFile.file.Close()
_, closedFileReadError := closedFile.file.Read(make([]byte, 10))
partialReadFile := files[5].open("rU")
partialReadFile.readLine(-1)
cases := []invokeTestCase{
{args: wrapArgs(files[0].open("r")), want: NewStr("foo").ToObject()},
{args: wrapArgs(files[0].open("rU")), want: NewStr("foo").ToObject()},
{args: wrapArgs(files[1].open("r")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[1].open("rU")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[2].open("r")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[2].open("rU")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[2].open("r"), 2), want: NewStr("fo").ToObject()},
{args: wrapArgs(files[2].open("r"), 3), want: NewStr("foo").ToObject()},
{args: wrapArgs(files[2].open("r"), 4), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[2].open("r"), 5), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[3].open("r")), want: NewStr("foo\r\n").ToObject()},
{args: wrapArgs(files[3].open("rU")), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[3].open("rU"), 3), want: NewStr("foo").ToObject()},
{args: wrapArgs(files[3].open("rU"), 4), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[3].open("rU"), 5), want: NewStr("foo\n").ToObject()},
{args: wrapArgs(files[4].open("r")), want: NewStr("foo\rbar").ToObject()},
{args: wrapArgs(files[4].open("rU")), want: NewStr("foo\n").ToObject()},
// Ensure that reading after a \r\n returns the requested
// number of bytes when possible. Check that the trailing \n
// does not count toward the bytes read.
{args: wrapArgs(partialReadFile, 3), want: NewStr("bar").ToObject()},
{args: wrapArgs(), wantExc: mustCreateException(TypeErrorType, "unbound method readline() must be called with file instance as first argument (got nothing instead)")},
{args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFileReadError.Error())},
{args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(ValueErrorType, "I/O operation on closed file")},
{args: wrapArgs(newObject(FileType), "abc"), wantExc: mustCreateException(ValueErrorType, "invalid literal for int() with base 10: abc")},
{args: wrapArgs(newObject(FileType), 123, 456), wantExc: mustCreateException(TypeErrorType, "'readline' of 'file' requires 2 arguments")},
}
for _, cas := range cases {
if err := runInvokeMethodTestCase(FileType, "readline", &cas); err != "" {
t.Error(err)
}
}
}
func TestFileReadLines(t *testing.T) {
files := makeTestFiles()
defer files.cleanup()
closedFile := files[0].open("r")
closedFile.file.Close()
_, closedFileReadError := closedFile.file.Read(make([]byte, 10))
partialReadFile := files[5].open("rU")
partialReadFile.readLine(-1)
cases := []invokeTestCase{
{args: wrapArgs(files[0].open("r")), want: newTestList("foo").ToObject()},
{args: wrapArgs(files[0].open("rU")), want: newTestList("foo").ToObject()},
{args: wrapArgs(files[1].open("r")), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[1].open("rU")), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[2].open("r")), want: newTestList("foo\n", "bar").ToObject()},
{args: wrapArgs(files[2].open("rU")), want: newTestList("foo\n", "bar").ToObject()},
{args: wrapArgs(files[2].open("r"), 2), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[2].open("r"), 3), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[2].open("r"), 4), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[2].open("r"), 5), want: newTestList("foo\n", "bar").ToObject()},
{args: wrapArgs(files[3].open("r")), want: newTestList("foo\r\n").ToObject()},
{args: wrapArgs(files[3].open("rU")), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[3].open("rU"), 3), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[3].open("rU"), 4), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[3].open("rU"), 5), want: newTestList("foo\n").ToObject()},
{args: wrapArgs(files[4].open("r")), want: newTestList("foo\rbar").ToObject()},
{args: wrapArgs(files[4].open("rU")), want: newTestList("foo\n", "bar").ToObject()},
// Ensure that reading after a \r\n returns the requested
// number of bytes when possible. Check that the trailing \n
// does not count toward the bytes read.
{args: wrapArgs(partialReadFile, 3), want: newTestList("bar\n").ToObject()},
{args: wrapArgs(), wantExc: mustCreateException(TypeErrorType, "unbound method readlines() must be called with file instance as first argument (got nothing instead)")},
{args: wrapArgs(closedFile), wantExc: mustCreateException(IOErrorType, closedFileReadError.Error())},
{args: wrapArgs(newObject(FileType)), wantExc: mustCreateException(ValueErrorType, "I/O operation on closed file")},
{args: wrapArgs(newObject(FileType), "abc"), wantExc: mustCreateException(ValueErrorType, "invalid literal for int() with base 10: abc")},
{args: wrapArgs(newObject(FileType), 123, 456), wantExc: mustCreateException(TypeErrorType, "'readlines' of 'file' requires 2 arguments")},
}
for _, cas := range cases {
if err := runInvokeMethodTestCase(FileType, "readlines", &cas); err != "" {
t.Error(err)
}
}
}
......@@ -99,9 +235,6 @@ func TestFileStrRepr(t *testing.T) {
return nil, raised
}
o := args[0]
if o.isInstance(FileType) {
defer toFileUnsafe(o).file.Close()
}
re := regexp.MustCompile(toStrUnsafe(args[1]).Value())
s, raised := ToStr(f, o)
if raised != nil {
......@@ -115,18 +248,14 @@ func TestFileStrRepr(t *testing.T) {
Assert(f, GetBool(re.MatchString(s.Value())).ToObject(), nil)
return None, nil
}).ToObject()
tempFilename := mustCreateTempFile("TestFileStrRepr", "foo\nbar")
defer os.Remove(tempFilename)
closedFile := mustOpenFile(tempFilename).ToObject()
f := newTestFile("foo\nbar")
defer f.cleanup()
closedFile := f.open("r").ToObject()
mustNotRaise(fileClose(newFrame(nil), []*Object{closedFile}, nil))
// Open a file for write.
args := wrapArgs(tempFilename, "wb")
writeFile := mustNotRaise(FileType.Call(newFrame(nil), args, nil))
if !writeFile.isInstance(FileType) {
t.Fatalf("file%v = %v, want file object", args, writeFile)
}
writeFile := f.open("wb")
cases := []invokeTestCase{
{args: wrapArgs(mustOpenFile(tempFilename), `^<open file "[^"]+", mode "r" at \w+>$`), want: None},
{args: wrapArgs(f.open("r"), `^<open file "[^"]+", mode "r" at \w+>$`), want: None},
{args: wrapArgs(writeFile, `^<open file "[^"]+", mode "wb" at \w+>$`), want: None},
{args: wrapArgs(newObject(FileType), `^<closed file "<uninitialized file>", mode "<uninitialized file>" at \w+>$`), want: None},
{args: wrapArgs(closedFile, `^<closed file "[^"]+", mode "r" at \w+>$`), want: None},
......@@ -193,8 +322,13 @@ func TestFileWrite(t *testing.T) {
}
}
func mustCreateTempFile(prefix, contents string) string {
osFile, err := ioutil.TempFile("", prefix)
type testFile struct {
path string
files []*File
}
func newTestFile(contents string) *testFile {
osFile, err := ioutil.TempFile("", "")
if err != nil {
panic(err)
}
......@@ -204,14 +338,42 @@ func mustCreateTempFile(prefix, contents string) string {
if err := osFile.Close(); err != nil {
panic(err)
}
return osFile.Name()
return &testFile{path: osFile.Name()}
}
func mustOpenFile(filename string) *File {
args := wrapArgs(filename)
func (f *testFile) cleanup() {
for _, file := range f.files {
file.file.Close()
}
os.Remove(f.path)
}
func (f *testFile) open(mode string) *File {
args := wrapArgs(f.path, mode)
o := mustNotRaise(FileType.Call(newFrame(nil), args, nil))
if o == nil || !o.isInstance(FileType) {
panic(fmt.Sprintf("file%v = %v, want file object", args, o))
}
return toFileUnsafe(o)
file := toFileUnsafe(o)
f.files = append(f.files, file)
return file
}
type testFileSlice []*testFile
func makeTestFiles() testFileSlice {
return []*testFile{
newTestFile("foo"),
newTestFile("foo\n"),
newTestFile("foo\nbar"),
newTestFile("foo\r\n"),
newTestFile("foo\rbar"),
newTestFile("foo\r\nbar\r\nbaz"),
}
}
func (files testFileSlice) cleanup() {
for _, f := range files {
f.cleanup()
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment