I have been following this answer trying to mock open. I have got exactly no where.
This is the test code I have:
func (m mockedFS) Open(name string) (file, error) {
if m.reportErrOpen {
return nil, errors.New("Fake failure")
}
mockedFile := mockIORead{}
mockedFile.On("ReadAll", mock.AnythingOfType("[]uint8")).Return(0, fmt.Errorf("error reading"))
mockedFile.On("Read", mock.AnythingOfType("[]byte")).Return(0, errors.New("NON"))
return mockedFile, nil
}
type mockIORead struct {
mock.Mock
reportErr bool // Tells if this mocked FS should return error in our tests
reportSize int64 // Tells what size should Stat() report in our test
}
func (m mockIORead) Read(b []byte) (n int, err error) {
if m.reportErr {
return 0, errors.New("A fake failure")
}
s := "Fear the old blood"
copy(b[:], s)
return 0, nil
}
func (m mockIORead) Close() error {
return nil
}
func (m mockIORead) ReadAt([]byte, int64) (int, error) {
return 0, nil
}
func (m mockIORead) Seek(int64, int) (int64, error) {
return 0, nil
}
func (m mockIORead) Stat() (os.FileInfo, error) {
if m.reportErr {
return nil, os.ErrNotExist
}
return mockedFileInfo{size: m.reportSize}, nil
}
func TestOok(t *testing.T) {
oldFs := fs
// Create and "install" mocked fs:
mfs := &mockedFS{}
fs = mfs
// Make sure fs is restored after this test:
defer func() {
fs = oldFs
}()
mfs.reportErr = false
mfs.reportErrOpen = false
token, err := Ook("fake")
assert.NotNil(t, err)
assert.Equal(t, "Fear the old blood", token)
}
And this is the code under test:
func Ook(name string) (string, error) {
_, err := fs.Stat(name)
if err != nil {
return "", nil
}
file, err := fs.Open(name)
if err != nil {
return "", errors.Wrap(err, "Cannot open token file")
}
defer file.Close()
_, err = ioutil.ReadAll(file)
fmt.Print("PING\n")
if err != nil {
return "", errors.Wrap(err, "Could not read token")
}
return "Fear the old blood", nil
//return string(token), nil
}
What the hell am I doing wrong?
The first error is that your mockIORead.Read() returns wrong values. It must return the number of read bytes (bytes written to the slice argument) (e.g. what copy() would return).
Next, mockIORead.Read() must be stateful! Reader.Read() might be called several times, there is no guarantee the passed slice can accommodate all the data you want to return (via the passed b slice).
So mockIORead must store the data you want to return, and it must remember how much of them has been delivered so far, so the next Read() call can continue from there.
An easy implementation of this is to utilize bytes.Buffer:
type mockIORead struct {
mock.Mock
reportErr bool // Tells if this mocked FS should return error in our tests
reportSize int64 // Tells what size should Stat() report in our test
content *bytes.Buffer
}
When returning such a mockIORead, initialize content with the content you wish to return:
func (m mockedFS) Open(name string) (file, error) {
if m.reportErrOpen {
return nil, errors.New("Fake failure")
}
mockedFile := mockIORead{
content: bytes.NewBufferString("Fear the old blood"),
}
return mockedFile, nil
}
And thanks to the available bytes.Buffer.Read() method, the mockIORead.Read() implementation can be as simple as this:
func (m mockIORead) Read(b []byte) (n int, err error) {
if m.reportErr {
return 0, errors.New("A fake failure")
}
return m.content.Read(b)
}
The Ook() function itself should not try to "stat" as you haven't mocked it (and so calling the original os.Stat() will likely yield an error for the "fake" file name used in the test):
func Ook(name string) (string, error) {
file, err := fs.Open(name)
if err != nil {
return "", errors.Wrap(err, "Cannot open token file")
}
defer file.Close()
token, err := ioutil.ReadAll(file)
fmt.Print("PING\n")
if err != nil {
return "", errors.Wrap(err, "Could not read token")
}
return string(token), nil
}
And the testing code:
func TestOok(t *testing.T) {
oldFs := fs
// Create and "install" mocked fs:
mfs := &mockedFS{}
fs = mfs
// Make sure fs is restored after this test:
defer func() {
fs = oldFs
}()
mfs.reportErr = false
mfs.reportErrOpen = false
token, err := Ook("fake")
assert.Nil(t, err)
assert.Equal(t, "Fear the old blood", token)
}
Which yields a successful ("OK") test.
Related
How can I compose the default Go HTTP file server (serve if exists, show file listing otherwise) with additional HTML?
Sample http.go with default file server:
package main
import "net/http"
func main() {
http.Handle("/", http.FileServer(http.Dir(".")))
http.ListenAndServe(":8090", nil)
}
Loading the default page (http://localhost:8090) gives something like:
<pre>LICENSE
README.md
studio.jpg
</pre>
I found it is declared at fs.go.
I want to keep that section, but with my own header and footer (preferably without copying the dirList function and making small changes):
<title>My files</title>
<pre>LICENSE
README.md
studio.jpg
</pre>
<p>And that's all, folks!</p>
Based on this answer, you can implement own FileSystem for a FileServer
This implementation is very buggy at best, and you should probably never ever use it, but it should show you how the FileSystem interface can be implemented for arbitrary 'files'.
type InMemoryFS map[string]http.File
type InMemoryFile struct {
at int64
Name string
data []byte
fs InMemoryFS
}
func NewFile(name string, data []byte) *InMemoryFile {
return &InMemoryFile{at: 0,
Name: name,
data: data,
fs: make(InMemoryFS)}
}
// Implements the http.File interface
func (f *InMemoryFile) Close() error {
return nil
}
func (f *InMemoryFile) Stat() (os.FileInfo, error) {
return &InMemoryFileInfo{f}, nil
}
func (f *InMemoryFile) Readdir(count int) ([]os.FileInfo, error) {
return nil, nil
}
func (f *InMemoryFile) Read(b []byte) (int, error) {
i := 0
for f.at < int64(len(f.data)) && i < len(b) {
b[i] = f.data[f.at]
i++
f.at++
}
return i, nil
}
func (f *InMemoryFile) Seek(offset int64, whence int) (int64, error) {
switch whence {
case 0:
f.at = offset
case 1:
f.at += offset
case 2:
f.at = int64(len(f.data)) + offset
}
return f.at, nil
}
type InMemoryFileInfo struct {
file *InMemoryFile
}
// Implements os.FileInfo
func (s *InMemoryFileInfo) Name() string { return s.file.Name }
func (s *InMemoryFileInfo) Size() int64 { return int64(len(s.file.data)) }
func (s *InMemoryFileInfo) Mode() os.FileMode { return os.ModeTemporary }
func (s *InMemoryFileInfo) ModTime() time.Time { return time.Time{} }
func (s *InMemoryFileInfo) IsDir() bool { return false }
func (s *InMemoryFileInfo) Sys() interface{} { return nil }
// CustomFsDecorator: is `http.FileSystem` decorator
type CustomFsDecorator struct {
http.FileSystem
}
func (fs CustomFsDecorator) Open(name string) (http.File, error) {
file, err := fs.FileSystem.Open(name)
if err != nil {
return nil, err
}
info, err := file.Stat()
if err != nil {
return nil, err
}
if info.IsDir() {
return file, nil
}
b, err := io.ReadAll(file)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
// add header's lines
_, err = buf.Write([]byte("<title>My files</title>\n"))
if err != nil {
return nil, err
}
_, err = buf.Write(b)
if err != nil {
return nil, err
}
// add footer's lines
_, err = buf.Write([]byte("\n<p>And that's all, folks!</p>"))
if err != nil {
return nil, err
}
return NewFile(info.Name(), buf.Bytes()), nil
}
func Test(t *testing.T) {
cfsys := CustomFsDecorator{FileSystem: http.Dir("./static")}
fsys := http.FileServer(cfsys)
req := httptest.NewRequest(http.MethodGet, "/some.html", nil)
w := httptest.NewRecorder()
fsys.ServeHTTP(w, req)
res := w.Result()
defer func() {
_ = res.Body.Close()
}()
data, err := io.ReadAll(res.Body)
if err != nil {
t.Errorf("expected error to be nil got %v", err)
}
fmt.Println(string(data))
}
👇🏻
<title>My files</title>
<pre>LICENSE
README.md
studio.jpg
</pre>
<p>And that's all, folks!</p>
PLAYGROUND
I try to create marshal func for struct Item.
So the question is, why the first example gives stackoverflow for goroutine and second works correctly?
Call method with receiver
type Item struct{
a int
}
func some(items []Item){
for _,i:=range items{
buf,err:=i.MarhsalBinary()
fmt.Println(buf.Bytes(), err)
}
func (i Item) MarshalBinary() ([]byte, error) {
var (
buf bytes.Buffer
err error
)
if err = gob.NewEncoder(&buf).Encode(i); err != nil { // endless loop here
return nil, err
}
return buf.Bytes(), err
}
This is pass by value.
type Item struct{
a int
}
func some(items []Item){
for _,i:=range items{
buf,err:=MarhsalBinary(i)
fmt.Println(buf.Bytes(), err)
}
func MarshalBinary(i Item) ([]byte, error) {
var (
buf bytes.Buffer
err error
)
if err = gob.NewEncoder(&buf).Encode(i); err != nil { // works correctly
return nil, err
}
return buf.Bytes(), err
}
Here's my code, I'm new to Go.
I tried googling the issue, but I can't quite put my finger on it.
I think it has something to do with the Read() method.
package main
import (
...
)
type compressor struct {
content []byte
}
func (r *compressor) compress() []byte {
...
}
func (r *compressor) decompress() []byte {
var buffer bytes.Buffer
dc := flate.NewReader(&buffer)
_, err := dc.Read(r.content)
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
}
return buffer.Bytes()
}
func main() {
fileName := os.Args[1]
fmt.Println(os.Args)
contents, err := ioutil.ReadFile(fileName)
if err != nil {
log.Fatal(err)
}
fmt.Print("Uncompressed data: ")
fmt.Println(len(contents))
comp := compressor{contents}
buffer := comp.decompress()
fmt.Print("Uncompressed data: ")
fmt.Println(len(comp.decompress()))
err = ioutil.WriteFile(fileName+".decjc", buffer, 0644)
if err != nil {
log.Fatal(err)
}
}
Here's the output
dylan#skynet:~/Documents/EXP/jc$ ./jc data.txt.jc
[./jc data.txt.jc]
Uncompressed data: 2364480
2018/06/29 21:41:35 unexpected EOF
After doing a trace on the particular code in question I have come to the following answer.
/src/bytes/reader.go 70
func (r *Reader) ReadByte() (byte, error) {
...
if r.i >= int64(len(r.s)) {
return 0, io.EOF
}
....
}
There are four functions in bytes/reader that can return io.EOF, and zero functions that can return io.ErrUnexpectedEOF. The four functions that can return io.EOF are:
Read(b []byte)
ReadAt(b []byte, off int64)
ReadByte()
ReadRune()
/src/compress/flate/inflate.go 698
func (f *decompressor) moreBits() error {
c, err := f.r.ReadByte()
if err != nil {
return noEOF(err)
}
...
}
Of the four functions that can return io.EOF, only one function in flate/inflate.go calls any of them: moreBits() calls ReadByte()
/src/compress/flate/inflate.go 690
func noEOF(e error) error {
if e == io.EOF {
return io.ErrUnexpectedEOF
}
...
}
When moreBits() receives an error it calls noEOF(), which checks if it had received an io.EOF. If this was the case then io.ErrUnexpectedEOF is returned backed. Everything seems to be working as intended, and it appears that it is the user's responsibility to be on the look out for this particular case. A suggested edit to the code above to handle what appears to be defined behavior is:
func (r *compressor) decompress() []byte {
dc := flate.NewReader(bytes.NewReader(r.content))
defer dc.Close()
rb, err := ioutil.ReadAll(dc)
if err != nil {
if err != io.EOF && err != io.ErrUnexpectedEOF {
log.Fatalf("Err %v\n read %v", err, rb)
}
}
return rb
}
This was checked under go1.12.9
You got the in and outputs mixed up.
flate.NewReader takes the compressed input as an io.Reader and it returns a io.ReadCloser that can be used to get the uncompressed output:
func (r *compressor) decompress() []byte {
dc := flate.NewReader(bytes.NewReader(r.content))
defer dc.Close()
rb, err := ioutil.ReadAll(dc)
if err != nil {
if err != io.EOF {
log.Fatalf("Err %v\n read %v", err, rb)
}
}
return rb
}
I'm implementing a Go wrapper for a REST API. It basically parses JSON and should return the appropriate struct type. I find myself doing a lot of this:
// GetBlueprintDetails returns details about a blueprint
func (c *Client) GetBlueprintDetails(projectID string, blueprintID string) (*BlueprintDetails, *APIError) {
path := fmt.Sprintf("projects/%s/blueprints/%s", projectID, blueprintID)
res, err := c.Request("GET", path, nil, nil)
if err != nil {
return nil, err
}
var ret BlueprintDetails
e := json.Unmarshal(res.Body, &ret)
if e != nil {
return nil, &APIError{Error: &e}
}
return &ret, nil
}
// GetProjects returns a list of projects for the user
func (c *Client) GetProjects() (*[]Project, *APIError) {
res, err := c.Request("GET", "projects", nil, nil)
if err != nil {
return nil, err
}
var ret []Project
e := json.Unmarshal(res.Body, &ret)
if e != nil {
return nil, &APIError{Error: &e}
}
return &ret, nil
}
The only difference between the two functions is the type of the unmarshaled struct basically. I know there are no generic in Go, but there has to be a pattern to make this more DRY.
Any ideas?
You may create a MakeRequest function that does the http request part and unmarshal the json to struct
Here is how you may do it, have a look at the MakeRequest function
// GetBlueprintDetails returns details about a blueprint
func (c *Client) GetBlueprintDetails(projectID string, blueprintID string) (*BlueprintDetails, *APIError) {
path := fmt.Sprintf("projects/%s/blueprints/%s", projectID, blueprintID)
bluePrintDetails = new(BlueprintDetails)
err := c.MakeRequest("GET", path, bluePrintDetails)
return bluePrintDetails, err
}
// GetProjects returns a list of projects for the user
func (c *Client) GetProjects() (*[]Project, *APIError) {
projects = make([]Project, 0)
err := c.MakeRequest("GET", "project", &projects)
return &projects, err
}
func (c *Client) MakeRequest(method string, path string, response interface{}) *APIError {
res, err := c.Request(method, path, nil, nil)
if err != nil {
return nil, err
}
e := json.Unmarshal(res.Body, response)
if e != nil {
return &APIError{Error: &e}
}
return nil
}
Well, part of my code was working without a method approach, I'm trying to test
append text to a file and reading from goroutines, but I'm stuck here trying to
write it.
What is wrong? the file is created, but I can't append text to it, maybe something obvious, but seems I'm blind, maybe I'm failing understanding some language concepts...
package main
import (
"bufio"
"fmt"
"os"
"sync"
"time"
)
var w sync.WaitGroup
type Buffer struct {
F *os.File
}
func (buff *Buffer) Open(pathName string) (err error) {
buff.F, err = os.OpenFile(pathName, os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
return
}
fmt.Println("Open() ok")
return nil
}
func (buff *Buffer) Close() (err error) {
err = buff.F.Close()
if err != nil {
return
}
fmt.Println("Close() ok")
return nil
}
func (buff *Buffer) Push(data string) (err error) {
w := bufio.NewWriter(buff.F)
_, err = fmt.Fprintf(w, "data=%s", data)
if err != nil {
return
}
w.Flush()
fmt.Println("Push() ok")
return nil
}
func checkErr(err error) {
if err != nil {
fmt.Println(err.Error())
}
}
func worker() {
var err error
buffer := new(Buffer)
err = buffer.Open("test")
checkErr(err)
err = buffer.Push("data\n")
checkErr(err)
time.Sleep(5 * time.Second)
err = buffer.Close()
checkErr(err)
w.Done()
}
func main() {
w.Add(2)
go worker()
go worker()
w.Wait()
}
Thanks
Open the file like this:
buff.F, err = os.OpenFile(pathName, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
The write flag is required to write to the file.
You missed the write error because the return from bufio Flush is ignored. Change Push to:
func (buff *Buffer) Push(data string) (err error) {
w := bufio.NewWriter(buff.F)
_, err = fmt.Fprintf(w, "data=%s", data)
if err != nil {
return
}
err = w.Flush()
if err != nil {
return err
}
fmt.Println("Push() ok")
return nil
}
To cleanly append data without intermixing with other pushes, the data must be written with a single call to the file Write method. Use a bytes.Buffer instead of a bufio.Writer to ensure a single call to the file Write method:
func (buff *Buffer) Push(data string) (err error) {
var b bytes.Buffer
_, err = fmt.Fprintf(&b, "data=%s", data)
if err != nil {
return
}
_, err := buff.F.Write(b.Bytes())
if err != nil {
return err
}
fmt.Println("Push() ok")
return nil
}