2017-10-29 21:14:05 +00:00
|
|
|
// Test suite for vfs
|
|
|
|
|
|
|
|
package vfs
|
|
|
|
|
|
|
|
import (
|
2019-06-17 08:34:30 +00:00
|
|
|
"context"
|
2020-04-08 17:29:50 +00:00
|
|
|
"fmt"
|
2018-04-06 18:53:06 +00:00
|
|
|
"io"
|
2017-10-29 21:14:05 +00:00
|
|
|
"os"
|
|
|
|
"testing"
|
2020-04-17 10:18:58 +00:00
|
|
|
"time"
|
2017-10-29 21:14:05 +00:00
|
|
|
|
2019-06-29 10:37:10 +00:00
|
|
|
"github.com/pkg/errors"
|
2019-07-28 17:47:38 +00:00
|
|
|
_ "github.com/rclone/rclone/backend/all" // import all the backends
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/rclone/rclone/fstest"
|
2020-02-28 14:44:15 +00:00
|
|
|
"github.com/rclone/rclone/vfs/vfscommon"
|
2017-10-29 21:14:05 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Some times used in the tests
|
|
|
|
var (
|
|
|
|
t1 = fstest.Time("2001-02-03T04:05:06.499999999Z")
|
|
|
|
t2 = fstest.Time("2011-12-25T12:59:59.123456789Z")
|
|
|
|
t3 = fstest.Time("2011-12-30T12:59:59.000000000Z")
|
|
|
|
)
|
|
|
|
|
2020-04-17 10:18:58 +00:00
|
|
|
// Constants uses in the tests
|
|
|
|
const (
|
|
|
|
writeBackDelay = 100 * time.Millisecond // A short writeback delay for testing
|
2021-10-20 16:24:58 +00:00
|
|
|
waitForWritersDelay = 30 * time.Second // time to wait for existing writers
|
2020-04-17 10:18:58 +00:00
|
|
|
)
|
|
|
|
|
2017-10-29 21:14:05 +00:00
|
|
|
// TestMain drives the tests
|
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
fstest.TestMain(m)
|
|
|
|
}
|
|
|
|
|
2020-04-17 10:18:58 +00:00
|
|
|
// Clean up a test VFS
|
|
|
|
func cleanupVFS(t *testing.T, vfs *VFS) {
|
|
|
|
vfs.WaitForWriters(waitForWritersDelay)
|
|
|
|
err := vfs.CleanUp()
|
|
|
|
require.NoError(t, err)
|
|
|
|
vfs.Shutdown()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new VFS
|
|
|
|
func newTestVFSOpt(t *testing.T, opt *vfscommon.Options) (r *fstest.Run, vfs *VFS, cleanup func()) {
|
|
|
|
r = fstest.NewRun(t)
|
|
|
|
vfs = New(r.Fremote, opt)
|
|
|
|
cleanup = func() {
|
|
|
|
cleanupVFS(t, vfs)
|
|
|
|
r.Finalise()
|
|
|
|
}
|
|
|
|
return r, vfs, cleanup
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create a new VFS with default options
|
|
|
|
func newTestVFS(t *testing.T) (r *fstest.Run, vfs *VFS, cleanup func()) {
|
|
|
|
return newTestVFSOpt(t, nil)
|
|
|
|
}
|
|
|
|
|
2017-10-29 21:14:05 +00:00
|
|
|
// Check baseHandle performs as advertised
|
|
|
|
func TestVFSbaseHandle(t *testing.T) {
|
|
|
|
fh := baseHandle{}
|
|
|
|
|
|
|
|
err := fh.Chdir()
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
err = fh.Chmod(0)
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
err = fh.Chown(0, 0)
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
err = fh.Close()
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
fd := fh.Fd()
|
|
|
|
assert.Equal(t, uintptr(0), fd)
|
|
|
|
|
|
|
|
name := fh.Name()
|
|
|
|
assert.Equal(t, "", name)
|
|
|
|
|
|
|
|
_, err = fh.Read(nil)
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
_, err = fh.ReadAt(nil, 0)
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
_, err = fh.Readdir(0)
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
_, err = fh.Readdirnames(0)
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
2018-04-06 18:53:06 +00:00
|
|
|
_, err = fh.Seek(0, io.SeekStart)
|
2017-10-29 21:14:05 +00:00
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
_, err = fh.Stat()
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
err = fh.Sync()
|
|
|
|
assert.Equal(t, nil, err)
|
|
|
|
|
|
|
|
err = fh.Truncate(0)
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
_, err = fh.Write(nil)
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
_, err = fh.WriteAt(nil, 0)
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
_, err = fh.WriteString("")
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
err = fh.Flush()
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
err = fh.Release()
|
|
|
|
assert.Equal(t, ENOSYS, err)
|
|
|
|
|
|
|
|
node := fh.Node()
|
|
|
|
assert.Nil(t, node)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestNew sees if the New command works properly
|
|
|
|
func TestVFSNew(t *testing.T) {
|
2020-06-15 10:28:42 +00:00
|
|
|
// Check active cache has this many entries
|
|
|
|
checkActiveCacheEntries := func(i int) {
|
|
|
|
_, count := activeCacheEntries()
|
|
|
|
assert.Equal(t, i, count)
|
|
|
|
}
|
|
|
|
|
|
|
|
checkActiveCacheEntries(0)
|
|
|
|
|
2020-04-17 10:18:58 +00:00
|
|
|
r, vfs, cleanup := newTestVFS(t)
|
2017-10-29 21:14:05 +00:00
|
|
|
|
|
|
|
// Check making a VFS with nil options
|
2020-02-28 14:44:15 +00:00
|
|
|
var defaultOpt = vfscommon.DefaultOpt
|
2019-01-09 09:49:34 +00:00
|
|
|
defaultOpt.DirPerms |= os.ModeDir
|
|
|
|
assert.Equal(t, vfs.Opt, defaultOpt)
|
2017-10-29 21:14:05 +00:00
|
|
|
assert.Equal(t, vfs.f, r.Fremote)
|
2020-06-15 10:28:42 +00:00
|
|
|
|
|
|
|
checkActiveCacheEntries(1)
|
|
|
|
|
|
|
|
// Check that we get the same VFS if we ask for it again with
|
|
|
|
// the same options
|
|
|
|
vfs2 := New(r.Fremote, nil)
|
|
|
|
assert.Equal(t, fmt.Sprintf("%p", vfs), fmt.Sprintf("%p", vfs2))
|
|
|
|
|
|
|
|
checkActiveCacheEntries(1)
|
|
|
|
|
|
|
|
// Shut the new VFS down and check the cache still has stuff in
|
|
|
|
vfs2.Shutdown()
|
|
|
|
|
|
|
|
checkActiveCacheEntries(1)
|
|
|
|
|
|
|
|
cleanup()
|
|
|
|
|
|
|
|
checkActiveCacheEntries(0)
|
2020-04-17 10:18:58 +00:00
|
|
|
}
|
2017-10-29 21:14:05 +00:00
|
|
|
|
2020-04-17 10:18:58 +00:00
|
|
|
// TestNew sees if the New command works properly
|
|
|
|
func TestVFSNewWithOpts(t *testing.T) {
|
2020-02-28 14:44:15 +00:00
|
|
|
var opt = vfscommon.DefaultOpt
|
2017-10-29 21:14:05 +00:00
|
|
|
opt.DirPerms = 0777
|
|
|
|
opt.FilePerms = 0666
|
|
|
|
opt.Umask = 0002
|
2020-04-17 10:18:58 +00:00
|
|
|
_, vfs, cleanup := newTestVFSOpt(t, &opt)
|
|
|
|
defer cleanup()
|
|
|
|
|
2017-10-29 21:14:05 +00:00
|
|
|
assert.Equal(t, os.FileMode(0775)|os.ModeDir, vfs.Opt.DirPerms)
|
|
|
|
assert.Equal(t, os.FileMode(0664), vfs.Opt.FilePerms)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestRoot checks root directory is present and correct
|
|
|
|
func TestVFSRoot(t *testing.T) {
|
2020-04-17 10:18:58 +00:00
|
|
|
_, vfs, cleanup := newTestVFS(t)
|
|
|
|
defer cleanup()
|
2017-10-29 21:14:05 +00:00
|
|
|
|
|
|
|
root, err := vfs.Root()
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, vfs.root, root)
|
|
|
|
assert.True(t, root.IsDir())
|
|
|
|
assert.Equal(t, vfs.Opt.DirPerms.Perm(), root.Mode().Perm())
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestVFSStat(t *testing.T) {
|
2020-04-17 10:18:58 +00:00
|
|
|
r, vfs, cleanup := newTestVFS(t)
|
|
|
|
defer cleanup()
|
2017-10-29 21:14:05 +00:00
|
|
|
|
2019-06-17 08:34:30 +00:00
|
|
|
file1 := r.WriteObject(context.Background(), "file1", "file1 contents", t1)
|
|
|
|
file2 := r.WriteObject(context.Background(), "dir/file2", "file2 contents", t2)
|
2017-10-29 21:14:05 +00:00
|
|
|
fstest.CheckItems(t, r.Fremote, file1, file2)
|
|
|
|
|
|
|
|
node, err := vfs.Stat("file1")
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, node.IsFile())
|
|
|
|
assert.Equal(t, "file1", node.Name())
|
|
|
|
|
|
|
|
node, err = vfs.Stat("dir")
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, node.IsDir())
|
|
|
|
assert.Equal(t, "dir", node.Name())
|
|
|
|
|
|
|
|
node, err = vfs.Stat("dir/file2")
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, node.IsFile())
|
|
|
|
assert.Equal(t, "file2", node.Name())
|
|
|
|
|
2020-02-12 11:20:54 +00:00
|
|
|
_, err = vfs.Stat("not found")
|
2017-10-29 21:14:05 +00:00
|
|
|
assert.Equal(t, os.ErrNotExist, err)
|
|
|
|
|
2020-02-12 11:20:54 +00:00
|
|
|
_, err = vfs.Stat("dir/not found")
|
2017-10-29 21:14:05 +00:00
|
|
|
assert.Equal(t, os.ErrNotExist, err)
|
|
|
|
|
2020-02-12 11:20:54 +00:00
|
|
|
_, err = vfs.Stat("not found/not found")
|
2017-10-29 21:14:05 +00:00
|
|
|
assert.Equal(t, os.ErrNotExist, err)
|
|
|
|
|
2020-02-12 11:20:54 +00:00
|
|
|
_, err = vfs.Stat("file1/under a file")
|
2017-10-29 21:14:05 +00:00
|
|
|
assert.Equal(t, os.ErrNotExist, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestVFSStatParent(t *testing.T) {
|
2020-04-17 10:18:58 +00:00
|
|
|
r, vfs, cleanup := newTestVFS(t)
|
|
|
|
defer cleanup()
|
2017-10-29 21:14:05 +00:00
|
|
|
|
2019-06-17 08:34:30 +00:00
|
|
|
file1 := r.WriteObject(context.Background(), "file1", "file1 contents", t1)
|
|
|
|
file2 := r.WriteObject(context.Background(), "dir/file2", "file2 contents", t2)
|
2017-10-29 21:14:05 +00:00
|
|
|
fstest.CheckItems(t, r.Fremote, file1, file2)
|
|
|
|
|
|
|
|
node, leaf, err := vfs.StatParent("file1")
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, node.IsDir())
|
|
|
|
assert.Equal(t, "/", node.Name())
|
|
|
|
assert.Equal(t, "file1", leaf)
|
|
|
|
|
|
|
|
node, leaf, err = vfs.StatParent("dir/file2")
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, node.IsDir())
|
|
|
|
assert.Equal(t, "dir", node.Name())
|
|
|
|
assert.Equal(t, "file2", leaf)
|
|
|
|
|
|
|
|
node, leaf, err = vfs.StatParent("not found")
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.True(t, node.IsDir())
|
|
|
|
assert.Equal(t, "/", node.Name())
|
|
|
|
assert.Equal(t, "not found", leaf)
|
|
|
|
|
2018-05-04 14:19:50 +00:00
|
|
|
_, _, err = vfs.StatParent("not found dir/not found")
|
2017-10-29 21:14:05 +00:00
|
|
|
assert.Equal(t, os.ErrNotExist, err)
|
|
|
|
|
2018-05-04 14:19:50 +00:00
|
|
|
_, _, err = vfs.StatParent("file1/under a file")
|
2017-10-29 21:14:05 +00:00
|
|
|
assert.Equal(t, os.ErrExist, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestVFSOpenFile(t *testing.T) {
|
2020-04-17 10:18:58 +00:00
|
|
|
r, vfs, cleanup := newTestVFS(t)
|
|
|
|
defer cleanup()
|
2017-10-29 21:14:05 +00:00
|
|
|
|
2019-06-17 08:34:30 +00:00
|
|
|
file1 := r.WriteObject(context.Background(), "file1", "file1 contents", t1)
|
|
|
|
file2 := r.WriteObject(context.Background(), "dir/file2", "file2 contents", t2)
|
2017-10-29 21:14:05 +00:00
|
|
|
fstest.CheckItems(t, r.Fremote, file1, file2)
|
|
|
|
|
|
|
|
fd, err := vfs.OpenFile("file1", os.O_RDONLY, 0777)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.NotNil(t, fd)
|
|
|
|
require.NoError(t, fd.Close())
|
|
|
|
|
|
|
|
fd, err = vfs.OpenFile("dir", os.O_RDONLY, 0777)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.NotNil(t, fd)
|
|
|
|
require.NoError(t, fd.Close())
|
|
|
|
|
|
|
|
fd, err = vfs.OpenFile("dir/new_file.txt", os.O_RDONLY, 0777)
|
|
|
|
assert.Equal(t, os.ErrNotExist, err)
|
|
|
|
assert.Nil(t, fd)
|
|
|
|
|
|
|
|
fd, err = vfs.OpenFile("dir/new_file.txt", os.O_WRONLY|os.O_CREATE, 0777)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.NotNil(t, fd)
|
2019-06-29 10:37:10 +00:00
|
|
|
err = fd.Close()
|
|
|
|
if errors.Cause(err) != fs.ErrorCantUploadEmptyFiles {
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
2017-10-29 21:14:05 +00:00
|
|
|
|
|
|
|
fd, err = vfs.OpenFile("not found/new_file.txt", os.O_WRONLY|os.O_CREATE, 0777)
|
|
|
|
assert.Equal(t, os.ErrNotExist, err)
|
|
|
|
assert.Nil(t, fd)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestVFSRename(t *testing.T) {
|
2020-04-17 10:18:58 +00:00
|
|
|
r, vfs, cleanup := newTestVFS(t)
|
|
|
|
defer cleanup()
|
|
|
|
|
2019-02-27 17:14:32 +00:00
|
|
|
features := r.Fremote.Features()
|
|
|
|
if features.Move == nil && features.Copy == nil {
|
2020-04-17 10:18:58 +00:00
|
|
|
t.Skip("skip as can't rename files")
|
2019-02-27 17:14:32 +00:00
|
|
|
}
|
2017-10-29 21:14:05 +00:00
|
|
|
|
2019-06-17 08:34:30 +00:00
|
|
|
file1 := r.WriteObject(context.Background(), "dir/file2", "file2 contents", t2)
|
2017-10-29 21:14:05 +00:00
|
|
|
fstest.CheckItems(t, r.Fremote, file1)
|
|
|
|
|
|
|
|
err := vfs.Rename("dir/file2", "dir/file1")
|
|
|
|
require.NoError(t, err)
|
|
|
|
file1.Path = "dir/file1"
|
|
|
|
fstest.CheckItems(t, r.Fremote, file1)
|
|
|
|
|
|
|
|
err = vfs.Rename("dir/file1", "file0")
|
|
|
|
require.NoError(t, err)
|
|
|
|
file1.Path = "file0"
|
|
|
|
fstest.CheckItems(t, r.Fremote, file1)
|
|
|
|
|
|
|
|
err = vfs.Rename("not found/file0", "file0")
|
|
|
|
assert.Equal(t, os.ErrNotExist, err)
|
|
|
|
|
|
|
|
err = vfs.Rename("file0", "not found/file0")
|
|
|
|
assert.Equal(t, os.ErrNotExist, err)
|
|
|
|
}
|
2018-04-17 22:19:34 +00:00
|
|
|
|
|
|
|
func TestVFSStatfs(t *testing.T) {
|
2020-04-17 10:18:58 +00:00
|
|
|
r, vfs, cleanup := newTestVFS(t)
|
|
|
|
defer cleanup()
|
2018-04-17 22:19:34 +00:00
|
|
|
|
|
|
|
// pre-conditions
|
|
|
|
assert.Nil(t, vfs.usage)
|
|
|
|
assert.True(t, vfs.usageTime.IsZero())
|
|
|
|
|
2019-02-27 17:13:54 +00:00
|
|
|
aboutSupported := r.Fremote.Features().About != nil
|
|
|
|
|
2018-04-17 22:19:34 +00:00
|
|
|
// read
|
|
|
|
total, used, free := vfs.Statfs()
|
2019-02-27 17:13:54 +00:00
|
|
|
if !aboutSupported {
|
2020-04-22 16:56:45 +00:00
|
|
|
assert.Equal(t, int64(unknownFreeBytes), total)
|
|
|
|
assert.Equal(t, int64(unknownFreeBytes), free)
|
|
|
|
assert.Equal(t, int64(0), used)
|
2019-02-27 17:13:54 +00:00
|
|
|
return // can't test anything else if About not supported
|
|
|
|
}
|
2018-04-17 22:19:34 +00:00
|
|
|
require.NotNil(t, vfs.usage)
|
|
|
|
assert.False(t, vfs.usageTime.IsZero())
|
|
|
|
if vfs.usage.Total != nil {
|
|
|
|
assert.Equal(t, *vfs.usage.Total, total)
|
|
|
|
} else {
|
2020-04-22 16:56:45 +00:00
|
|
|
assert.True(t, total >= int64(unknownFreeBytes))
|
2018-04-17 22:19:34 +00:00
|
|
|
}
|
|
|
|
if vfs.usage.Free != nil {
|
|
|
|
assert.Equal(t, *vfs.usage.Free, free)
|
|
|
|
} else {
|
2020-05-25 15:46:56 +00:00
|
|
|
if vfs.usage.Total != nil && vfs.usage.Used != nil {
|
|
|
|
assert.Equal(t, free, total-used)
|
|
|
|
} else {
|
|
|
|
assert.True(t, free >= int64(unknownFreeBytes))
|
|
|
|
}
|
2018-04-17 22:19:34 +00:00
|
|
|
}
|
|
|
|
if vfs.usage.Used != nil {
|
|
|
|
assert.Equal(t, *vfs.usage.Used, used)
|
|
|
|
} else {
|
2020-04-22 16:56:45 +00:00
|
|
|
assert.Equal(t, int64(0), used)
|
2018-04-17 22:19:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// read cached
|
|
|
|
oldUsage := vfs.usage
|
|
|
|
oldTime := vfs.usageTime
|
|
|
|
total2, used2, free2 := vfs.Statfs()
|
|
|
|
assert.Equal(t, oldUsage, vfs.usage)
|
|
|
|
assert.Equal(t, total, total2)
|
|
|
|
assert.Equal(t, used, used2)
|
|
|
|
assert.Equal(t, free, free2)
|
|
|
|
assert.Equal(t, oldTime, vfs.usageTime)
|
|
|
|
}
|
2020-04-08 17:29:50 +00:00
|
|
|
|
|
|
|
func TestFillInMissingSizes(t *testing.T) {
|
|
|
|
const unknownFree = 10
|
|
|
|
for _, test := range []struct {
|
|
|
|
total, free, used int64
|
|
|
|
wantTotal, wantUsed, wantFree int64
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
total: 20, free: 5, used: 15,
|
|
|
|
wantTotal: 20, wantFree: 5, wantUsed: 15,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
total: 20, free: 5, used: -1,
|
|
|
|
wantTotal: 20, wantFree: 5, wantUsed: 15,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
total: 20, free: -1, used: 15,
|
|
|
|
wantTotal: 20, wantFree: 5, wantUsed: 15,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
total: 20, free: -1, used: -1,
|
|
|
|
wantTotal: 20, wantFree: 20, wantUsed: 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
total: -1, free: 5, used: 15,
|
|
|
|
wantTotal: 20, wantFree: 5, wantUsed: 15,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
total: -1, free: 15, used: -1,
|
|
|
|
wantTotal: 15, wantFree: 15, wantUsed: 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
total: -1, free: -1, used: 15,
|
|
|
|
wantTotal: 25, wantFree: 10, wantUsed: 15,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
total: -1, free: -1, used: -1,
|
|
|
|
wantTotal: 10, wantFree: 10, wantUsed: 0,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(fmt.Sprintf("total=%d,free=%d,used=%d", test.total, test.free, test.used), func(t *testing.T) {
|
|
|
|
gotTotal, gotUsed, gotFree := fillInMissingSizes(test.total, test.used, test.free, unknownFree)
|
|
|
|
assert.Equal(t, test.wantTotal, gotTotal, "total")
|
|
|
|
assert.Equal(t, test.wantUsed, gotUsed, "used")
|
|
|
|
assert.Equal(t, test.wantFree, gotFree, "free")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|