1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183 |
- // Copyright 2014 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 webdav
- import (
- "context"
- "encoding/xml"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
- "path/filepath"
- "reflect"
- "runtime"
- "sort"
- "strconv"
- "strings"
- "testing"
- )
- func TestSlashClean(t *testing.T) {
- testCases := []string{
- "",
- ".",
- "/",
- "/./",
- "//",
- "//.",
- "//a",
- "/a",
- "/a/b/c",
- "/a//b/./../c/d/",
- "a",
- "a/b/c",
- }
- for _, tc := range testCases {
- got := slashClean(tc)
- want := path.Clean("/" + tc)
- if got != want {
- t.Errorf("tc=%q: got %q, want %q", tc, got, want)
- }
- }
- }
- func TestDirResolve(t *testing.T) {
- testCases := []struct {
- dir, name, want string
- }{
- {"/", "", "/"},
- {"/", "/", "/"},
- {"/", ".", "/"},
- {"/", "./a", "/a"},
- {"/", "..", "/"},
- {"/", "..", "/"},
- {"/", "../", "/"},
- {"/", "../.", "/"},
- {"/", "../a", "/a"},
- {"/", "../..", "/"},
- {"/", "../bar/a", "/bar/a"},
- {"/", "../baz/a", "/baz/a"},
- {"/", "...", "/..."},
- {"/", ".../a", "/.../a"},
- {"/", ".../..", "/"},
- {"/", "a", "/a"},
- {"/", "a/./b", "/a/b"},
- {"/", "a/../../b", "/b"},
- {"/", "a/../b", "/b"},
- {"/", "a/b", "/a/b"},
- {"/", "a/b/c/../../d", "/a/d"},
- {"/", "a/b/c/../../../d", "/d"},
- {"/", "a/b/c/../../../../d", "/d"},
- {"/", "a/b/c/d", "/a/b/c/d"},
- {"/foo/bar", "", "/foo/bar"},
- {"/foo/bar", "/", "/foo/bar"},
- {"/foo/bar", ".", "/foo/bar"},
- {"/foo/bar", "./a", "/foo/bar/a"},
- {"/foo/bar", "..", "/foo/bar"},
- {"/foo/bar", "../", "/foo/bar"},
- {"/foo/bar", "../.", "/foo/bar"},
- {"/foo/bar", "../a", "/foo/bar/a"},
- {"/foo/bar", "../..", "/foo/bar"},
- {"/foo/bar", "../bar/a", "/foo/bar/bar/a"},
- {"/foo/bar", "../baz/a", "/foo/bar/baz/a"},
- {"/foo/bar", "...", "/foo/bar/..."},
- {"/foo/bar", ".../a", "/foo/bar/.../a"},
- {"/foo/bar", ".../..", "/foo/bar"},
- {"/foo/bar", "a", "/foo/bar/a"},
- {"/foo/bar", "a/./b", "/foo/bar/a/b"},
- {"/foo/bar", "a/../../b", "/foo/bar/b"},
- {"/foo/bar", "a/../b", "/foo/bar/b"},
- {"/foo/bar", "a/b", "/foo/bar/a/b"},
- {"/foo/bar", "a/b/c/../../d", "/foo/bar/a/d"},
- {"/foo/bar", "a/b/c/../../../d", "/foo/bar/d"},
- {"/foo/bar", "a/b/c/../../../../d", "/foo/bar/d"},
- {"/foo/bar", "a/b/c/d", "/foo/bar/a/b/c/d"},
- {"/foo/bar/", "", "/foo/bar"},
- {"/foo/bar/", "/", "/foo/bar"},
- {"/foo/bar/", ".", "/foo/bar"},
- {"/foo/bar/", "./a", "/foo/bar/a"},
- {"/foo/bar/", "..", "/foo/bar"},
- {"/foo//bar///", "", "/foo/bar"},
- {"/foo//bar///", "/", "/foo/bar"},
- {"/foo//bar///", ".", "/foo/bar"},
- {"/foo//bar///", "./a", "/foo/bar/a"},
- {"/foo//bar///", "..", "/foo/bar"},
- {"/x/y/z", "ab/c\x00d/ef", ""},
- {".", "", "."},
- {".", "/", "."},
- {".", ".", "."},
- {".", "./a", "a"},
- {".", "..", "."},
- {".", "..", "."},
- {".", "../", "."},
- {".", "../.", "."},
- {".", "../a", "a"},
- {".", "../..", "."},
- {".", "../bar/a", "bar/a"},
- {".", "../baz/a", "baz/a"},
- {".", "...", "..."},
- {".", ".../a", ".../a"},
- {".", ".../..", "."},
- {".", "a", "a"},
- {".", "a/./b", "a/b"},
- {".", "a/../../b", "b"},
- {".", "a/../b", "b"},
- {".", "a/b", "a/b"},
- {".", "a/b/c/../../d", "a/d"},
- {".", "a/b/c/../../../d", "d"},
- {".", "a/b/c/../../../../d", "d"},
- {".", "a/b/c/d", "a/b/c/d"},
- {"", "", "."},
- {"", "/", "."},
- {"", ".", "."},
- {"", "./a", "a"},
- {"", "..", "."},
- }
- for _, tc := range testCases {
- d := Dir(filepath.FromSlash(tc.dir))
- if got := filepath.ToSlash(d.resolve(tc.name)); got != tc.want {
- t.Errorf("dir=%q, name=%q: got %q, want %q", tc.dir, tc.name, got, tc.want)
- }
- }
- }
- func TestWalk(t *testing.T) {
- type walkStep struct {
- name, frag string
- final bool
- }
- testCases := []struct {
- dir string
- want []walkStep
- }{
- {"", []walkStep{
- {"", "", true},
- }},
- {"/", []walkStep{
- {"", "", true},
- }},
- {"/a", []walkStep{
- {"", "a", true},
- }},
- {"/a/", []walkStep{
- {"", "a", true},
- }},
- {"/a/b", []walkStep{
- {"", "a", false},
- {"a", "b", true},
- }},
- {"/a/b/", []walkStep{
- {"", "a", false},
- {"a", "b", true},
- }},
- {"/a/b/c", []walkStep{
- {"", "a", false},
- {"a", "b", false},
- {"b", "c", true},
- }},
- // The following test case is the one mentioned explicitly
- // in the method description.
- {"/foo/bar/x", []walkStep{
- {"", "foo", false},
- {"foo", "bar", false},
- {"bar", "x", true},
- }},
- }
- ctx := context.Background()
- for _, tc := range testCases {
- fs := NewMemFS().(*memFS)
- parts := strings.Split(tc.dir, "/")
- for p := 2; p < len(parts); p++ {
- d := strings.Join(parts[:p], "/")
- if err := fs.Mkdir(ctx, d, 0666); err != nil {
- t.Errorf("tc.dir=%q: mkdir: %q: %v", tc.dir, d, err)
- }
- }
- i, prevFrag := 0, ""
- err := fs.walk("test", tc.dir, func(dir *memFSNode, frag string, final bool) error {
- got := walkStep{
- name: prevFrag,
- frag: frag,
- final: final,
- }
- want := tc.want[i]
- if got != want {
- return fmt.Errorf("got %+v, want %+v", got, want)
- }
- i, prevFrag = i+1, frag
- return nil
- })
- if err != nil {
- t.Errorf("tc.dir=%q: %v", tc.dir, err)
- }
- }
- }
- // find appends to ss the names of the named file and its children. It is
- // analogous to the Unix find command.
- //
- // The returned strings are not guaranteed to be in any particular order.
- func find(ctx context.Context, ss []string, fs FileSystem, name string) ([]string, error) {
- stat, err := fs.Stat(ctx, name)
- if err != nil {
- return nil, err
- }
- ss = append(ss, name)
- if stat.IsDir() {
- f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
- if err != nil {
- return nil, err
- }
- defer f.Close()
- children, err := f.Readdir(-1)
- if err != nil {
- return nil, err
- }
- for _, c := range children {
- ss, err = find(ctx, ss, fs, path.Join(name, c.Name()))
- if err != nil {
- return nil, err
- }
- }
- }
- return ss, nil
- }
- func testFS(t *testing.T, fs FileSystem) {
- errStr := func(err error) string {
- switch {
- case os.IsExist(err):
- return "errExist"
- case os.IsNotExist(err):
- return "errNotExist"
- case err != nil:
- return "err"
- }
- return "ok"
- }
- // The non-"find" non-"stat" test cases should change the file system state. The
- // indentation of the "find"s and "stat"s helps distinguish such test cases.
- testCases := []string{
- " stat / want dir",
- " stat /a want errNotExist",
- " stat /d want errNotExist",
- " stat /d/e want errNotExist",
- "create /a A want ok",
- " stat /a want 1",
- "create /d/e EEE want errNotExist",
- "mk-dir /a want errExist",
- "mk-dir /d/m want errNotExist",
- "mk-dir /d want ok",
- " stat /d want dir",
- "create /d/e EEE want ok",
- " stat /d/e want 3",
- " find / /a /d /d/e",
- "create /d/f FFFF want ok",
- "create /d/g GGGGGGG want ok",
- "mk-dir /d/m want ok",
- "mk-dir /d/m want errExist",
- "create /d/m/p PPPPP want ok",
- " stat /d/e want 3",
- " stat /d/f want 4",
- " stat /d/g want 7",
- " stat /d/h want errNotExist",
- " stat /d/m want dir",
- " stat /d/m/p want 5",
- " find / /a /d /d/e /d/f /d/g /d/m /d/m/p",
- "rm-all /d want ok",
- " stat /a want 1",
- " stat /d want errNotExist",
- " stat /d/e want errNotExist",
- " stat /d/f want errNotExist",
- " stat /d/g want errNotExist",
- " stat /d/m want errNotExist",
- " stat /d/m/p want errNotExist",
- " find / /a",
- "mk-dir /d/m want errNotExist",
- "mk-dir /d want ok",
- "create /d/f FFFF want ok",
- "rm-all /d/f want ok",
- "mk-dir /d/m want ok",
- "rm-all /z want ok",
- "rm-all / want err",
- "create /b BB want ok",
- " stat / want dir",
- " stat /a want 1",
- " stat /b want 2",
- " stat /c want errNotExist",
- " stat /d want dir",
- " stat /d/m want dir",
- " find / /a /b /d /d/m",
- "move__ o=F /b /c want ok",
- " stat /b want errNotExist",
- " stat /c want 2",
- " stat /d/m want dir",
- " stat /d/n want errNotExist",
- " find / /a /c /d /d/m",
- "move__ o=F /d/m /d/n want ok",
- "create /d/n/q QQQQ want ok",
- " stat /d/m want errNotExist",
- " stat /d/n want dir",
- " stat /d/n/q want 4",
- "move__ o=F /d /d/n/z want err",
- "move__ o=T /c /d/n/q want ok",
- " stat /c want errNotExist",
- " stat /d/n/q want 2",
- " find / /a /d /d/n /d/n/q",
- "create /d/n/r RRRRR want ok",
- "mk-dir /u want ok",
- "mk-dir /u/v want ok",
- "move__ o=F /d/n /u want errExist",
- "create /t TTTTTT want ok",
- "move__ o=F /d/n /t want errExist",
- "rm-all /t want ok",
- "move__ o=F /d/n /t want ok",
- " stat /d want dir",
- " stat /d/n want errNotExist",
- " stat /d/n/r want errNotExist",
- " stat /t want dir",
- " stat /t/q want 2",
- " stat /t/r want 5",
- " find / /a /d /t /t/q /t/r /u /u/v",
- "move__ o=F /t / want errExist",
- "move__ o=T /t /u/v want ok",
- " stat /u/v/r want 5",
- "move__ o=F / /z want err",
- " find / /a /d /u /u/v /u/v/q /u/v/r",
- " stat /a want 1",
- " stat /b want errNotExist",
- " stat /c want errNotExist",
- " stat /u/v/r want 5",
- "copy__ o=F d=0 /a /b want ok",
- "copy__ o=T d=0 /a /c want ok",
- " stat /a want 1",
- " stat /b want 1",
- " stat /c want 1",
- " stat /u/v/r want 5",
- "copy__ o=F d=0 /u/v/r /b want errExist",
- " stat /b want 1",
- "copy__ o=T d=0 /u/v/r /b want ok",
- " stat /a want 1",
- " stat /b want 5",
- " stat /u/v/r want 5",
- "rm-all /a want ok",
- "rm-all /b want ok",
- "mk-dir /u/v/w want ok",
- "create /u/v/w/s SSSSSSSS want ok",
- " stat /d want dir",
- " stat /d/x want errNotExist",
- " stat /d/y want errNotExist",
- " stat /u/v/r want 5",
- " stat /u/v/w/s want 8",
- " find / /c /d /u /u/v /u/v/q /u/v/r /u/v/w /u/v/w/s",
- "copy__ o=T d=0 /u/v /d/x want ok",
- "copy__ o=T d=∞ /u/v /d/y want ok",
- "rm-all /u want ok",
- " stat /d/x want dir",
- " stat /d/x/q want errNotExist",
- " stat /d/x/r want errNotExist",
- " stat /d/x/w want errNotExist",
- " stat /d/x/w/s want errNotExist",
- " stat /d/y want dir",
- " stat /d/y/q want 2",
- " stat /d/y/r want 5",
- " stat /d/y/w want dir",
- " stat /d/y/w/s want 8",
- " stat /u want errNotExist",
- " find / /c /d /d/x /d/y /d/y/q /d/y/r /d/y/w /d/y/w/s",
- "copy__ o=F d=∞ /d/y /d/x want errExist",
- }
- ctx := context.Background()
- for i, tc := range testCases {
- tc = strings.TrimSpace(tc)
- j := strings.IndexByte(tc, ' ')
- if j < 0 {
- t.Fatalf("test case #%d %q: invalid command", i, tc)
- }
- op, arg := tc[:j], tc[j+1:]
- switch op {
- default:
- t.Fatalf("test case #%d %q: invalid operation %q", i, tc, op)
- case "create":
- parts := strings.Split(arg, " ")
- if len(parts) != 4 || parts[2] != "want" {
- t.Fatalf("test case #%d %q: invalid write", i, tc)
- }
- f, opErr := fs.OpenFile(ctx, parts[0], os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
- if got := errStr(opErr); got != parts[3] {
- t.Fatalf("test case #%d %q: OpenFile: got %q (%v), want %q", i, tc, got, opErr, parts[3])
- }
- if f != nil {
- if _, err := f.Write([]byte(parts[1])); err != nil {
- t.Fatalf("test case #%d %q: Write: %v", i, tc, err)
- }
- if err := f.Close(); err != nil {
- t.Fatalf("test case #%d %q: Close: %v", i, tc, err)
- }
- }
- case "find":
- got, err := find(ctx, nil, fs, "/")
- if err != nil {
- t.Fatalf("test case #%d %q: find: %v", i, tc, err)
- }
- sort.Strings(got)
- want := strings.Split(arg, " ")
- if !reflect.DeepEqual(got, want) {
- t.Fatalf("test case #%d %q:\ngot %s\nwant %s", i, tc, got, want)
- }
- case "copy__", "mk-dir", "move__", "rm-all", "stat":
- nParts := 3
- switch op {
- case "copy__":
- nParts = 6
- case "move__":
- nParts = 5
- }
- parts := strings.Split(arg, " ")
- if len(parts) != nParts {
- t.Fatalf("test case #%d %q: invalid %s", i, tc, op)
- }
- got, opErr := "", error(nil)
- switch op {
- case "copy__":
- depth := 0
- if parts[1] == "d=∞" {
- depth = infiniteDepth
- }
- _, opErr = copyFiles(ctx, fs, parts[2], parts[3], parts[0] == "o=T", depth, 0)
- case "mk-dir":
- opErr = fs.Mkdir(ctx, parts[0], 0777)
- case "move__":
- _, opErr = moveFiles(ctx, fs, parts[1], parts[2], parts[0] == "o=T")
- case "rm-all":
- opErr = fs.RemoveAll(ctx, parts[0])
- case "stat":
- var stat os.FileInfo
- fileName := parts[0]
- if stat, opErr = fs.Stat(ctx, fileName); opErr == nil {
- if stat.IsDir() {
- got = "dir"
- } else {
- got = strconv.Itoa(int(stat.Size()))
- }
- if fileName == "/" {
- // For a Dir FileSystem, the virtual file system root maps to a
- // real file system name like "/tmp/webdav-test012345", which does
- // not end with "/". We skip such cases.
- } else if statName := stat.Name(); path.Base(fileName) != statName {
- t.Fatalf("test case #%d %q: file name %q inconsistent with stat name %q",
- i, tc, fileName, statName)
- }
- }
- }
- if got == "" {
- got = errStr(opErr)
- }
- if parts[len(parts)-2] != "want" {
- t.Fatalf("test case #%d %q: invalid %s", i, tc, op)
- }
- if want := parts[len(parts)-1]; got != want {
- t.Fatalf("test case #%d %q: got %q (%v), want %q", i, tc, got, opErr, want)
- }
- }
- }
- }
- func TestDir(t *testing.T) {
- switch runtime.GOOS {
- case "nacl":
- t.Skip("see golang.org/issue/12004")
- case "plan9":
- t.Skip("see golang.org/issue/11453")
- }
- td, err := ioutil.TempDir("", "webdav-test")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(td)
- testFS(t, Dir(td))
- }
- func TestMemFS(t *testing.T) {
- testFS(t, NewMemFS())
- }
- func TestMemFSRoot(t *testing.T) {
- ctx := context.Background()
- fs := NewMemFS()
- for i := 0; i < 5; i++ {
- stat, err := fs.Stat(ctx, "/")
- if err != nil {
- t.Fatalf("i=%d: Stat: %v", i, err)
- }
- if !stat.IsDir() {
- t.Fatalf("i=%d: Stat.IsDir is false, want true", i)
- }
- f, err := fs.OpenFile(ctx, "/", os.O_RDONLY, 0)
- if err != nil {
- t.Fatalf("i=%d: OpenFile: %v", i, err)
- }
- defer f.Close()
- children, err := f.Readdir(-1)
- if err != nil {
- t.Fatalf("i=%d: Readdir: %v", i, err)
- }
- if len(children) != i {
- t.Fatalf("i=%d: got %d children, want %d", i, len(children), i)
- }
- if _, err := f.Write(make([]byte, 1)); err == nil {
- t.Fatalf("i=%d: Write: got nil error, want non-nil", i)
- }
- if err := fs.Mkdir(ctx, fmt.Sprintf("/dir%d", i), 0777); err != nil {
- t.Fatalf("i=%d: Mkdir: %v", i, err)
- }
- }
- }
- func TestMemFileReaddir(t *testing.T) {
- ctx := context.Background()
- fs := NewMemFS()
- if err := fs.Mkdir(ctx, "/foo", 0777); err != nil {
- t.Fatalf("Mkdir: %v", err)
- }
- readdir := func(count int) ([]os.FileInfo, error) {
- f, err := fs.OpenFile(ctx, "/foo", os.O_RDONLY, 0)
- if err != nil {
- t.Fatalf("OpenFile: %v", err)
- }
- defer f.Close()
- return f.Readdir(count)
- }
- if got, err := readdir(-1); len(got) != 0 || err != nil {
- t.Fatalf("readdir(-1): got %d fileInfos with err=%v, want 0, <nil>", len(got), err)
- }
- if got, err := readdir(+1); len(got) != 0 || err != io.EOF {
- t.Fatalf("readdir(+1): got %d fileInfos with err=%v, want 0, EOF", len(got), err)
- }
- }
- func TestMemFile(t *testing.T) {
- testCases := []string{
- "wantData ",
- "wantSize 0",
- "write abc",
- "wantData abc",
- "write de",
- "wantData abcde",
- "wantSize 5",
- "write 5*x",
- "write 4*y+2*z",
- "write 3*st",
- "wantData abcdexxxxxyyyyzzststst",
- "wantSize 22",
- "seek set 4 want 4",
- "write EFG",
- "wantData abcdEFGxxxyyyyzzststst",
- "wantSize 22",
- "seek set 2 want 2",
- "read cdEF",
- "read Gx",
- "seek cur 0 want 8",
- "seek cur 2 want 10",
- "seek cur -1 want 9",
- "write J",
- "wantData abcdEFGxxJyyyyzzststst",
- "wantSize 22",
- "seek cur -4 want 6",
- "write ghijk",
- "wantData abcdEFghijkyyyzzststst",
- "wantSize 22",
- "read yyyz",
- "seek cur 0 want 15",
- "write ",
- "seek cur 0 want 15",
- "read ",
- "seek cur 0 want 15",
- "seek end -3 want 19",
- "write ZZ",
- "wantData abcdEFghijkyyyzzstsZZt",
- "wantSize 22",
- "write 4*A",
- "wantData abcdEFghijkyyyzzstsZZAAAA",
- "wantSize 25",
- "seek end 0 want 25",
- "seek end -5 want 20",
- "read Z+4*A",
- "write 5*B",
- "wantData abcdEFghijkyyyzzstsZZAAAABBBBB",
- "wantSize 30",
- "seek end 10 want 40",
- "write C",
- "wantData abcdEFghijkyyyzzstsZZAAAABBBBB..........C",
- "wantSize 41",
- "write D",
- "wantData abcdEFghijkyyyzzstsZZAAAABBBBB..........CD",
- "wantSize 42",
- "seek set 43 want 43",
- "write E",
- "wantData abcdEFghijkyyyzzstsZZAAAABBBBB..........CD.E",
- "wantSize 44",
- "seek set 0 want 0",
- "write 5*123456789_",
- "wantData 123456789_123456789_123456789_123456789_123456789_",
- "wantSize 50",
- "seek cur 0 want 50",
- "seek cur -99 want err",
- }
- ctx := context.Background()
- const filename = "/foo"
- fs := NewMemFS()
- f, err := fs.OpenFile(ctx, filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
- if err != nil {
- t.Fatalf("OpenFile: %v", err)
- }
- defer f.Close()
- for i, tc := range testCases {
- j := strings.IndexByte(tc, ' ')
- if j < 0 {
- t.Fatalf("test case #%d %q: invalid command", i, tc)
- }
- op, arg := tc[:j], tc[j+1:]
- // Expand an arg like "3*a+2*b" to "aaabb".
- parts := strings.Split(arg, "+")
- for j, part := range parts {
- if k := strings.IndexByte(part, '*'); k >= 0 {
- repeatCount, repeatStr := part[:k], part[k+1:]
- n, err := strconv.Atoi(repeatCount)
- if err != nil {
- t.Fatalf("test case #%d %q: invalid repeat count %q", i, tc, repeatCount)
- }
- parts[j] = strings.Repeat(repeatStr, n)
- }
- }
- arg = strings.Join(parts, "")
- switch op {
- default:
- t.Fatalf("test case #%d %q: invalid operation %q", i, tc, op)
- case "read":
- buf := make([]byte, len(arg))
- if _, err := io.ReadFull(f, buf); err != nil {
- t.Fatalf("test case #%d %q: ReadFull: %v", i, tc, err)
- }
- if got := string(buf); got != arg {
- t.Fatalf("test case #%d %q:\ngot %q\nwant %q", i, tc, got, arg)
- }
- case "seek":
- parts := strings.Split(arg, " ")
- if len(parts) != 4 {
- t.Fatalf("test case #%d %q: invalid seek", i, tc)
- }
- whence := 0
- switch parts[0] {
- default:
- t.Fatalf("test case #%d %q: invalid seek whence", i, tc)
- case "set":
- whence = os.SEEK_SET
- case "cur":
- whence = os.SEEK_CUR
- case "end":
- whence = os.SEEK_END
- }
- offset, err := strconv.Atoi(parts[1])
- if err != nil {
- t.Fatalf("test case #%d %q: invalid offset %q", i, tc, parts[1])
- }
- if parts[2] != "want" {
- t.Fatalf("test case #%d %q: invalid seek", i, tc)
- }
- if parts[3] == "err" {
- _, err := f.Seek(int64(offset), whence)
- if err == nil {
- t.Fatalf("test case #%d %q: Seek returned nil error, want non-nil", i, tc)
- }
- } else {
- got, err := f.Seek(int64(offset), whence)
- if err != nil {
- t.Fatalf("test case #%d %q: Seek: %v", i, tc, err)
- }
- want, err := strconv.Atoi(parts[3])
- if err != nil {
- t.Fatalf("test case #%d %q: invalid want %q", i, tc, parts[3])
- }
- if got != int64(want) {
- t.Fatalf("test case #%d %q: got %d, want %d", i, tc, got, want)
- }
- }
- case "write":
- n, err := f.Write([]byte(arg))
- if err != nil {
- t.Fatalf("test case #%d %q: write: %v", i, tc, err)
- }
- if n != len(arg) {
- t.Fatalf("test case #%d %q: write returned %d bytes, want %d", i, tc, n, len(arg))
- }
- case "wantData":
- g, err := fs.OpenFile(ctx, filename, os.O_RDONLY, 0666)
- if err != nil {
- t.Fatalf("test case #%d %q: OpenFile: %v", i, tc, err)
- }
- gotBytes, err := ioutil.ReadAll(g)
- if err != nil {
- t.Fatalf("test case #%d %q: ReadAll: %v", i, tc, err)
- }
- for i, c := range gotBytes {
- if c == '\x00' {
- gotBytes[i] = '.'
- }
- }
- got := string(gotBytes)
- if got != arg {
- t.Fatalf("test case #%d %q:\ngot %q\nwant %q", i, tc, got, arg)
- }
- if err := g.Close(); err != nil {
- t.Fatalf("test case #%d %q: Close: %v", i, tc, err)
- }
- case "wantSize":
- n, err := strconv.Atoi(arg)
- if err != nil {
- t.Fatalf("test case #%d %q: invalid size %q", i, tc, arg)
- }
- fi, err := fs.Stat(ctx, filename)
- if err != nil {
- t.Fatalf("test case #%d %q: Stat: %v", i, tc, err)
- }
- if got, want := fi.Size(), int64(n); got != want {
- t.Fatalf("test case #%d %q: got %d, want %d", i, tc, got, want)
- }
- }
- }
- }
- // TestMemFileWriteAllocs tests that writing N consecutive 1KiB chunks to a
- // memFile doesn't allocate a new buffer for each of those N times. Otherwise,
- // calling io.Copy(aMemFile, src) is likely to have quadratic complexity.
- func TestMemFileWriteAllocs(t *testing.T) {
- if runtime.Compiler == "gccgo" {
- t.Skip("gccgo allocates here")
- }
- ctx := context.Background()
- fs := NewMemFS()
- f, err := fs.OpenFile(ctx, "/xxx", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
- if err != nil {
- t.Fatalf("OpenFile: %v", err)
- }
- defer f.Close()
- xxx := make([]byte, 1024)
- for i := range xxx {
- xxx[i] = 'x'
- }
- a := testing.AllocsPerRun(100, func() {
- f.Write(xxx)
- })
- // AllocsPerRun returns an integral value, so we compare the rounded-down
- // number to zero.
- if a > 0 {
- t.Fatalf("%v allocs per run, want 0", a)
- }
- }
- func BenchmarkMemFileWrite(b *testing.B) {
- ctx := context.Background()
- fs := NewMemFS()
- xxx := make([]byte, 1024)
- for i := range xxx {
- xxx[i] = 'x'
- }
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- f, err := fs.OpenFile(ctx, "/xxx", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
- if err != nil {
- b.Fatalf("OpenFile: %v", err)
- }
- for j := 0; j < 100; j++ {
- f.Write(xxx)
- }
- if err := f.Close(); err != nil {
- b.Fatalf("Close: %v", err)
- }
- if err := fs.RemoveAll(ctx, "/xxx"); err != nil {
- b.Fatalf("RemoveAll: %v", err)
- }
- }
- }
- func TestCopyMoveProps(t *testing.T) {
- ctx := context.Background()
- fs := NewMemFS()
- create := func(name string) error {
- f, err := fs.OpenFile(ctx, name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
- if err != nil {
- return err
- }
- _, wErr := f.Write([]byte("contents"))
- cErr := f.Close()
- if wErr != nil {
- return wErr
- }
- return cErr
- }
- patch := func(name string, patches ...Proppatch) error {
- f, err := fs.OpenFile(ctx, name, os.O_RDWR, 0666)
- if err != nil {
- return err
- }
- _, pErr := f.(DeadPropsHolder).Patch(patches)
- cErr := f.Close()
- if pErr != nil {
- return pErr
- }
- return cErr
- }
- props := func(name string) (map[xml.Name]Property, error) {
- f, err := fs.OpenFile(ctx, name, os.O_RDWR, 0666)
- if err != nil {
- return nil, err
- }
- m, pErr := f.(DeadPropsHolder).DeadProps()
- cErr := f.Close()
- if pErr != nil {
- return nil, pErr
- }
- if cErr != nil {
- return nil, cErr
- }
- return m, nil
- }
- p0 := Property{
- XMLName: xml.Name{Space: "x:", Local: "boat"},
- InnerXML: []byte("pea-green"),
- }
- p1 := Property{
- XMLName: xml.Name{Space: "x:", Local: "ring"},
- InnerXML: []byte("1 shilling"),
- }
- p2 := Property{
- XMLName: xml.Name{Space: "x:", Local: "spoon"},
- InnerXML: []byte("runcible"),
- }
- p3 := Property{
- XMLName: xml.Name{Space: "x:", Local: "moon"},
- InnerXML: []byte("light"),
- }
- if err := create("/src"); err != nil {
- t.Fatalf("create /src: %v", err)
- }
- if err := patch("/src", Proppatch{Props: []Property{p0, p1}}); err != nil {
- t.Fatalf("patch /src +p0 +p1: %v", err)
- }
- if _, err := copyFiles(ctx, fs, "/src", "/tmp", true, infiniteDepth, 0); err != nil {
- t.Fatalf("copyFiles /src /tmp: %v", err)
- }
- if _, err := moveFiles(ctx, fs, "/tmp", "/dst", true); err != nil {
- t.Fatalf("moveFiles /tmp /dst: %v", err)
- }
- if err := patch("/src", Proppatch{Props: []Property{p0}, Remove: true}); err != nil {
- t.Fatalf("patch /src -p0: %v", err)
- }
- if err := patch("/src", Proppatch{Props: []Property{p2}}); err != nil {
- t.Fatalf("patch /src +p2: %v", err)
- }
- if err := patch("/dst", Proppatch{Props: []Property{p1}, Remove: true}); err != nil {
- t.Fatalf("patch /dst -p1: %v", err)
- }
- if err := patch("/dst", Proppatch{Props: []Property{p3}}); err != nil {
- t.Fatalf("patch /dst +p3: %v", err)
- }
- gotSrc, err := props("/src")
- if err != nil {
- t.Fatalf("props /src: %v", err)
- }
- wantSrc := map[xml.Name]Property{
- p1.XMLName: p1,
- p2.XMLName: p2,
- }
- if !reflect.DeepEqual(gotSrc, wantSrc) {
- t.Fatalf("props /src:\ngot %v\nwant %v", gotSrc, wantSrc)
- }
- gotDst, err := props("/dst")
- if err != nil {
- t.Fatalf("props /dst: %v", err)
- }
- wantDst := map[xml.Name]Property{
- p0.XMLName: p0,
- p3.XMLName: p3,
- }
- if !reflect.DeepEqual(gotDst, wantDst) {
- t.Fatalf("props /dst:\ngot %v\nwant %v", gotDst, wantDst)
- }
- }
- func TestWalkFS(t *testing.T) {
- testCases := []struct {
- desc string
- buildfs []string
- startAt string
- depth int
- walkFn filepath.WalkFunc
- want []string
- }{{
- "just root",
- []string{},
- "/",
- infiniteDepth,
- nil,
- []string{
- "/",
- },
- }, {
- "infinite walk from root",
- []string{
- "mkdir /a",
- "mkdir /a/b",
- "touch /a/b/c",
- "mkdir /a/d",
- "mkdir /e",
- "touch /f",
- },
- "/",
- infiniteDepth,
- nil,
- []string{
- "/",
- "/a",
- "/a/b",
- "/a/b/c",
- "/a/d",
- "/e",
- "/f",
- },
- }, {
- "infinite walk from subdir",
- []string{
- "mkdir /a",
- "mkdir /a/b",
- "touch /a/b/c",
- "mkdir /a/d",
- "mkdir /e",
- "touch /f",
- },
- "/a",
- infiniteDepth,
- nil,
- []string{
- "/a",
- "/a/b",
- "/a/b/c",
- "/a/d",
- },
- }, {
- "depth 1 walk from root",
- []string{
- "mkdir /a",
- "mkdir /a/b",
- "touch /a/b/c",
- "mkdir /a/d",
- "mkdir /e",
- "touch /f",
- },
- "/",
- 1,
- nil,
- []string{
- "/",
- "/a",
- "/e",
- "/f",
- },
- }, {
- "depth 1 walk from subdir",
- []string{
- "mkdir /a",
- "mkdir /a/b",
- "touch /a/b/c",
- "mkdir /a/b/g",
- "mkdir /a/b/g/h",
- "touch /a/b/g/i",
- "touch /a/b/g/h/j",
- },
- "/a/b",
- 1,
- nil,
- []string{
- "/a/b",
- "/a/b/c",
- "/a/b/g",
- },
- }, {
- "depth 0 walk from subdir",
- []string{
- "mkdir /a",
- "mkdir /a/b",
- "touch /a/b/c",
- "mkdir /a/b/g",
- "mkdir /a/b/g/h",
- "touch /a/b/g/i",
- "touch /a/b/g/h/j",
- },
- "/a/b",
- 0,
- nil,
- []string{
- "/a/b",
- },
- }, {
- "infinite walk from file",
- []string{
- "mkdir /a",
- "touch /a/b",
- "touch /a/c",
- },
- "/a/b",
- 0,
- nil,
- []string{
- "/a/b",
- },
- }, {
- "infinite walk with skipped subdir",
- []string{
- "mkdir /a",
- "mkdir /a/b",
- "touch /a/b/c",
- "mkdir /a/b/g",
- "mkdir /a/b/g/h",
- "touch /a/b/g/i",
- "touch /a/b/g/h/j",
- "touch /a/b/z",
- },
- "/",
- infiniteDepth,
- func(path string, info os.FileInfo, err error) error {
- if path == "/a/b/g" {
- return filepath.SkipDir
- }
- return nil
- },
- []string{
- "/",
- "/a",
- "/a/b",
- "/a/b/c",
- "/a/b/z",
- },
- }}
- ctx := context.Background()
- for _, tc := range testCases {
- fs, err := buildTestFS(tc.buildfs)
- if err != nil {
- t.Fatalf("%s: cannot create test filesystem: %v", tc.desc, err)
- }
- var got []string
- traceFn := func(path string, info os.FileInfo, err error) error {
- if tc.walkFn != nil {
- err = tc.walkFn(path, info, err)
- if err != nil {
- return err
- }
- }
- got = append(got, path)
- return nil
- }
- fi, err := fs.Stat(ctx, tc.startAt)
- if err != nil {
- t.Fatalf("%s: cannot stat: %v", tc.desc, err)
- }
- err = walkFS(ctx, fs, tc.depth, tc.startAt, fi, traceFn)
- if err != nil {
- t.Errorf("%s:\ngot error %v, want nil", tc.desc, err)
- continue
- }
- sort.Strings(got)
- sort.Strings(tc.want)
- if !reflect.DeepEqual(got, tc.want) {
- t.Errorf("%s:\ngot %q\nwant %q", tc.desc, got, tc.want)
- continue
- }
- }
- }
- func buildTestFS(buildfs []string) (FileSystem, error) {
- // TODO: Could this be merged with the build logic in TestFS?
- ctx := context.Background()
- fs := NewMemFS()
- for _, b := range buildfs {
- op := strings.Split(b, " ")
- switch op[0] {
- case "mkdir":
- err := fs.Mkdir(ctx, op[1], os.ModeDir|0777)
- if err != nil {
- return nil, err
- }
- case "touch":
- f, err := fs.OpenFile(ctx, op[1], os.O_RDWR|os.O_CREATE, 0666)
- if err != nil {
- return nil, err
- }
- f.Close()
- case "write":
- f, err := fs.OpenFile(ctx, op[1], os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
- if err != nil {
- return nil, err
- }
- _, err = f.Write([]byte(op[2]))
- f.Close()
- if err != nil {
- return nil, err
- }
- default:
- return nil, fmt.Errorf("unknown file operation %q", op[0])
- }
- }
- return fs, nil
- }
|