mirror of
https://github.com/golang/go.git
synced 2025-05-18 22:04:38 +00:00
io/fs: add ReadFile and ReadFileFS
Add ReadFile helper function, ReadFileFS interface, and test. Add ReadFile method to fstest.MapFS. Add testing of ReadFile method to fstest.TestFS. For #41190. Change-Id: I5b6a41e2e582824e570463b698b635abaa436c32 Reviewed-on: https://go-review.googlesource.com/c/go/+/243912 Trust: Russ Cox <rsc@golang.org> Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
parent
b1f76f7a22
commit
f098ccf04a
@ -165,7 +165,9 @@ var depsRules = `
|
|||||||
|
|
||||||
os/signal, STR
|
os/signal, STR
|
||||||
< path/filepath
|
< path/filepath
|
||||||
< io/ioutil, os/exec
|
< io/ioutil, os/exec;
|
||||||
|
|
||||||
|
io/ioutil, os/exec, os/signal
|
||||||
< OS;
|
< OS;
|
||||||
|
|
||||||
reflect !< OS;
|
reflect !< OS;
|
||||||
|
63
src/io/fs/readfile.go
Normal file
63
src/io/fs/readfile.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// ReadFileFS is the interface implemented by a file system
|
||||||
|
// that provides an optimized implementation of ReadFile.
|
||||||
|
type ReadFileFS interface {
|
||||||
|
FS
|
||||||
|
|
||||||
|
// ReadFile reads the named file and returns its contents.
|
||||||
|
// A successful call returns a nil error, not io.EOF.
|
||||||
|
// (Because ReadFile reads the whole file, the expected EOF
|
||||||
|
// from the final Read is not treated as an error to be reported.)
|
||||||
|
ReadFile(name string) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFile reads the named file from the file system fs and returns its contents.
|
||||||
|
// A successful call returns a nil error, not io.EOF.
|
||||||
|
// (Because ReadFile reads the whole file, the expected EOF
|
||||||
|
// from the final Read is not treated as an error to be reported.)
|
||||||
|
//
|
||||||
|
// If fs implements ReadFileFS, ReadFile calls fs.ReadFile.
|
||||||
|
// Otherwise ReadFile calls fs.Open and uses Read and Close
|
||||||
|
// on the returned file.
|
||||||
|
func ReadFile(fsys FS, name string) ([]byte, error) {
|
||||||
|
if fsys, ok := fsys.(ReadFileFS); ok {
|
||||||
|
return fsys.ReadFile(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := fsys.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var size int
|
||||||
|
if info, err := file.Stat(); err == nil {
|
||||||
|
size64 := info.Size()
|
||||||
|
if int64(int(size64)) == size64 {
|
||||||
|
size = int(size64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]byte, 0, size+1)
|
||||||
|
for {
|
||||||
|
if len(data) >= cap(data) {
|
||||||
|
d := append(data[:cap(data)], 0)
|
||||||
|
data = d[:len(data)]
|
||||||
|
}
|
||||||
|
n, err := file.Read(data[len(data):cap(data)])
|
||||||
|
data = data[:len(data)+n]
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return data, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/io/fs/readfile_test.go
Normal file
43
src/io/fs/readfile_test.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package fs_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "io/fs"
|
||||||
|
"testing"
|
||||||
|
"testing/fstest"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testFsys = fstest.MapFS{
|
||||||
|
"hello.txt": {
|
||||||
|
Data: []byte("hello, world"),
|
||||||
|
Mode: 0456,
|
||||||
|
ModTime: time.Now(),
|
||||||
|
Sys: &sysValue,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var sysValue int
|
||||||
|
|
||||||
|
type readFileOnly struct{ ReadFileFS }
|
||||||
|
|
||||||
|
func (readFileOnly) Open(name string) (File, error) { return nil, ErrNotExist }
|
||||||
|
|
||||||
|
type openOnly struct{ FS }
|
||||||
|
|
||||||
|
func TestReadFile(t *testing.T) {
|
||||||
|
// Test that ReadFile uses the method when present.
|
||||||
|
data, err := ReadFile(readFileOnly{testFsys}, "hello.txt")
|
||||||
|
if string(data) != "hello, world" || err != nil {
|
||||||
|
t.Fatalf(`ReadFile(readFileOnly, "hello.txt") = %q, %v, want %q, nil`, data, err, "hello, world")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that ReadFile uses Open when the method is not present.
|
||||||
|
data, err = ReadFile(openOnly{testFsys}, "hello.txt")
|
||||||
|
if string(data) != "hello, world" || err != nil {
|
||||||
|
t.Fatalf(`ReadFile(openOnly, "hello.txt") = %q, %v, want %q, nil`, data, err, "hello, world")
|
||||||
|
}
|
||||||
|
}
|
@ -108,6 +108,18 @@ func (fsys MapFS) Open(name string) (fs.File, error) {
|
|||||||
return &mapDir{name, mapFileInfo{elem, file}, list, 0}, nil
|
return &mapDir{name, mapFileInfo{elem, file}, list, 0}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fsOnly is a wrapper that hides all but the fs.FS methods,
|
||||||
|
// to avoid an infinite recursion when implementing special
|
||||||
|
// methods in terms of helpers that would use them.
|
||||||
|
// (In general, implementing these methods using the package fs helpers
|
||||||
|
// is redundant and unnecessary, but having the methods may make
|
||||||
|
// MapFS exercise more code paths when used in tests.)
|
||||||
|
type fsOnly struct{ fs.FS }
|
||||||
|
|
||||||
|
func (fsys MapFS) ReadFile(name string) ([]byte, error) {
|
||||||
|
return fs.ReadFile(fsOnly{fsys}, name)
|
||||||
|
}
|
||||||
|
|
||||||
// A mapFileInfo implements fs.FileInfo and fs.DirEntry for a given map file.
|
// A mapFileInfo implements fs.FileInfo and fs.DirEntry for a given map file.
|
||||||
type mapFileInfo struct {
|
type mapFileInfo struct {
|
||||||
name string
|
name string
|
||||||
|
@ -310,6 +310,27 @@ func (t *fsTester) checkFile(file string) {
|
|||||||
// The return value doesn't matter.
|
// The return value doesn't matter.
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
|
// Check that ReadFile works if present.
|
||||||
|
if fsys, ok := t.fsys.(fs.ReadFileFS); ok {
|
||||||
|
data2, err := fsys.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
t.errorf("%s: fsys.ReadFile: %v", file, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.checkFileRead(file, "ReadAll vs fsys.ReadFile", data, data2)
|
||||||
|
|
||||||
|
t.checkBadPath(file, "ReadFile",
|
||||||
|
func(name string) error { _, err := fsys.ReadFile(name); return err })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that fs.ReadFile works with t.fsys.
|
||||||
|
data2, err := fs.ReadFile(t.fsys, file)
|
||||||
|
if err != nil {
|
||||||
|
t.errorf("%s: fs.ReadFile: %v", file, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.checkFileRead(file, "ReadAll vs fs.ReadFile", data, data2)
|
||||||
|
|
||||||
// Use iotest.TestReader to check small reads, Seek, ReadAt.
|
// Use iotest.TestReader to check small reads, Seek, ReadAt.
|
||||||
f, err = t.fsys.Open(file)
|
f, err = t.fsys.Open(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -329,8 +350,19 @@ func (t *fsTester) checkFileRead(file, desc string, data1, data2 []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkOpen checks that various invalid forms of file's name cannot be opened.
|
// checkBadPath checks that various invalid forms of file's name cannot be opened using t.fsys.Open.
|
||||||
func (t *fsTester) checkOpen(file string) {
|
func (t *fsTester) checkOpen(file string) {
|
||||||
|
t.checkBadPath(file, "Open", func(file string) error {
|
||||||
|
f, err := t.fsys.Open(file)
|
||||||
|
if err == nil {
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkBadPath checks that various invalid forms of file's name cannot be opened using open.
|
||||||
|
func (t *fsTester) checkBadPath(file string, desc string, open func(string) error) {
|
||||||
bad := []string{
|
bad := []string{
|
||||||
"/" + file,
|
"/" + file,
|
||||||
file + "/.",
|
file + "/.",
|
||||||
@ -356,9 +388,8 @@ func (t *fsTester) checkOpen(file string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range bad {
|
for _, b := range bad {
|
||||||
if f, err := t.fsys.Open(b); err == nil {
|
if err := open(b); err == nil {
|
||||||
f.Close()
|
t.errorf("%s: %s(%s) succeeded, want error", file, desc, b)
|
||||||
t.errorf("%s: Open(%s) succeeded, want error", file, b)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user