// subpackages returns the set of packages in the given srcDir whose // import paths start with dir. func subpackages(ctxt *build.Context, srcDir string, dir string) map[string]bool { subs := map[string]bool{dir: true} // Find all packages under srcDir whose import paths start with dir. buildutil.ForEachPackage(ctxt, func(pkg string, err error) { if err != nil { log.Fatalf("unexpected error in ForEachPackage: %v", err) } if !strings.HasPrefix(pkg, path.Join(dir, "")) { return } p, err := ctxt.Import(pkg, "", build.FindOnly) if err != nil { log.Fatalf("unexpected: package %s can not be located by build context: %s", pkg, err) } if p.SrcRoot == "" { log.Fatalf("unexpected: could not determine srcDir for package %s: %s", pkg, err) } if p.SrcRoot != srcDir { return } subs[pkg] = true }) return subs }
// Builds scans the specified Go workspace and builds the forward and // reverse import dependency graphs for all its packages. // It also returns a mapping from import paths to errors for packages // that could not be loaded. func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) { type importEdge struct { from, to string } type pathError struct { path string err error } ch := make(chan interface{}) var wg sync.WaitGroup buildutil.ForEachPackage(ctxt, func(path string, err error) { wg.Add(1) go func() { defer wg.Done() if err != nil { ch <- pathError{path, err} return } bp, err := ctxt.Import(path, "", 0) if _, ok := err.(*build.NoGoError); ok { return // empty directory is not an error } if err != nil { ch <- pathError{path, err} return } for _, imp := range bp.Imports { ch <- importEdge{path, imp} } for _, imp := range bp.TestImports { ch <- importEdge{path, imp} } for _, imp := range bp.XTestImports { ch <- importEdge{path, imp} } }() }) go func() { wg.Wait() close(ch) }() forward = make(Graph) reverse = make(Graph) for e := range ch { switch e := e.(type) { case pathError: if errors == nil { errors = make(map[string]error) } errors[e.path] = e.err case importEdge: if e.to == "C" { continue // "C" is fake } forward.addEdge(e.from, e.to) reverse.addEdge(e.to, e.from) } } return forward, reverse, errors }
// Build scans the specified Go workspace and builds the forward and // reverse import dependency graphs for all its packages. // It also returns a mapping from canonical import paths to errors for packages // whose loading was not entirely successful. // A package may appear in the graph and in the errors mapping. // All package paths are canonical and may contain "/vendor/". func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) { type importEdge struct { from, to string } type pathError struct { path string err error } ch := make(chan interface{}) go func() { sema := make(chan int, 20) // I/O concurrency limiting semaphore var wg sync.WaitGroup buildutil.ForEachPackage(ctxt, func(path string, err error) { if err != nil { ch <- pathError{path, err} return } wg.Add(1) go func() { defer wg.Done() sema <- 1 bp, err := ctxt.Import(path, "", 0) <-sema if err != nil { if _, ok := err.(*build.NoGoError); ok { // empty directory is not an error } else { ch <- pathError{path, err} } // Even in error cases, Import usually returns a package. } // absolutize resolves an import path relative // to the current package bp. // The absolute form may contain "vendor". // // The vendoring feature slows down Build by 3×. // Here are timings from a 1400 package workspace: // 1100ms: current code (with vendor check) // 880ms: with a nonblocking cache around ctxt.IsDir // 840ms: nonblocking cache with duplicate suppression // 340ms: original code (no vendor check) // TODO(adonovan): optimize, somehow. memo := make(map[string]string) absolutize := func(path string) string { canon, ok := memo[path] if !ok { sema <- 1 bp2, _ := ctxt.Import(path, bp.Dir, build.FindOnly) <-sema if bp2 != nil { canon = bp2.ImportPath } else { canon = path } memo[path] = canon } return canon } if bp != nil { for _, imp := range bp.Imports { ch <- importEdge{path, absolutize(imp)} } for _, imp := range bp.TestImports { ch <- importEdge{path, absolutize(imp)} } for _, imp := range bp.XTestImports { ch <- importEdge{path, absolutize(imp)} } } }() }) wg.Wait() close(ch) }() forward = make(Graph) reverse = make(Graph) for e := range ch { switch e := e.(type) { case pathError: if errors == nil { errors = make(map[string]error) } errors[e.path] = e.err case importEdge: if e.to == "C" { continue // "C" is fake } forward.addEdge(e.from, e.to) reverse.addEdge(e.to, e.from) } } return forward, reverse, errors }
func TestMoves(t *testing.T) { tests := []struct { ctxt *build.Context from, to string want map[string]string }{ // Simple example. { ctxt: fakeContext(map[string][]string{ "foo": {`package foo; type T int`}, "main": {`package main import "foo" var _ foo.T `}, }), from: "foo", to: "bar", want: map[string]string{ "/go/src/main/0.go": `package main import "bar" var _ bar.T `, "/go/src/bar/0.go": `package bar type T int `, }, }, // Example with subpackage. { ctxt: fakeContext(map[string][]string{ "foo": {`package foo; type T int`}, "foo/sub": {`package sub; type T int`}, "main": {`package main import "foo" import "foo/sub" var _ foo.T var _ sub.T `}, }), from: "foo", to: "bar", want: map[string]string{ "/go/src/main/0.go": `package main import "bar" import "bar/sub" var _ bar.T var _ sub.T `, "/go/src/bar/0.go": `package bar type T int `, "/go/src/bar/sub/0.go": `package sub; type T int`, }, }, // References into subpackages { ctxt: fakeContext(map[string][]string{ "foo": {`package foo; import "foo/a"; var _ a.T`}, "foo/a": {`package a; type T int`}, "foo/b": {`package b; import "foo/a"; var _ a.T`}, }), from: "foo", to: "bar", want: map[string]string{ "/go/src/bar/0.go": `package bar import "bar/a" var _ a.T `, "/go/src/bar/a/0.go": `package a; type T int`, "/go/src/bar/b/0.go": `package b import "bar/a" var _ a.T `, }, }, // External test packages { ctxt: buildutil.FakeContext(map[string]map[string]string{ "foo": { "0.go": `package foo; type T int`, "0_test.go": `package foo_test; import "foo"; var _ foo.T`, }, "baz": { "0_test.go": `package baz_test; import "foo"; var _ foo.T`, }, }), from: "foo", to: "bar", want: map[string]string{ "/go/src/bar/0.go": `package bar type T int `, "/go/src/bar/0_test.go": `package bar_test import "bar" var _ bar.T `, "/go/src/baz/0_test.go": `package baz_test import "bar" var _ bar.T `, }, }, } for _, test := range tests { ctxt := test.ctxt got := make(map[string]string) // Populate got with starting file set. rewriteFile and moveDirectory // will mutate got to produce resulting file set. buildutil.ForEachPackage(ctxt, func(importPath string, err error) { if err != nil { return } path := filepath.Join("/go/src", importPath, "0.go") if !buildutil.FileExists(ctxt, path) { return } f, err := ctxt.OpenFile(path) if err != nil { t.Errorf("unexpected error opening file: %s", err) return } bytes, err := ioutil.ReadAll(f) f.Close() if err != nil { t.Errorf("unexpected error reading file: %s", err) return } got[path] = string(bytes) }) rewriteFile = func(fset *token.FileSet, f *ast.File, orig string) error { var out bytes.Buffer if err := format.Node(&out, fset, f); err != nil { return err } got[orig] = out.String() return nil } moveDirectory = func(from, to string) error { for path, contents := range got { if strings.HasPrefix(path, from) { newPath := strings.Replace(path, from, to, 1) delete(got, path) got[newPath] = contents } } return nil } err := Move(ctxt, test.from, test.to, "") prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to) if err != nil { t.Errorf("%s: unexpected error: %s", prefix, err) continue } for file, wantContent := range test.want { k := filepath.FromSlash(file) gotContent, ok := got[k] delete(got, k) if !ok { // TODO(matloob): some testcases might have files that won't be // rewritten t.Errorf("%s: file %s not rewritten", prefix, file) continue } if gotContent != wantContent { t.Errorf("%s: rewritten file %s does not match expectation; got <<<%s>>>\n"+ "want <<<%s>>>", prefix, file, gotContent, wantContent) } } // got should now be empty for file := range got { t.Errorf("%s: unexpected rewrite of file %s", prefix, file) } } }
// Builds scans the specified Go workspace and builds the forward and // reverse import dependency graphs for all its packages. // It also returns a mapping from import paths to errors for packages // that could not be loaded. func Build(ctxt *build.Context) (forward, reverse Graph, errors map[string]error) { // The (sole) graph builder goroutine receives a stream of import // edges from the package loading goroutine. forward = make(Graph) reverse = make(Graph) edgec := make(chan [2]string) go func() { for edge := range edgec { if edge[1] == "C" { continue // "C" is fake } forward.addEdge(edge[0], edge[1]) reverse.addEdge(edge[1], edge[0]) } }() // The (sole) error goroutine receives a stream of ReadDir and // Import errors. type pathError struct { path string err error } errorc := make(chan pathError) go func() { for e := range errorc { if errors == nil { errors = make(map[string]error) } errors[e.path] = e.err } }() var wg sync.WaitGroup buildutil.ForEachPackage(ctxt, func(path string, err error) { if err != nil { errorc <- pathError{path, err} return } wg.Add(1) // The import goroutines load the metadata for each package. go func(path string) { defer wg.Done() bp, err := ctxt.Import(path, "", 0) if _, ok := err.(*build.NoGoError); ok { return // empty directory is not an error } if err != nil { errorc <- pathError{path, err} return } for _, imp := range bp.Imports { edgec <- [2]string{path, imp} } for _, imp := range bp.TestImports { edgec <- [2]string{path, imp} } for _, imp := range bp.XTestImports { edgec <- [2]string{path, imp} } }(path) }) wg.Wait() close(edgec) close(errorc) return forward, reverse, errors }
func (w *PkgWalker) LookupObjects(conf *PkgConfig, cursor *FileCursor) { var cursorObj types.Object var cursorSelection *types.Selection var cursorObjIsDef bool //lookup defs var pkg *types.Package var pkgInfo *types.Info if cursor.xtest { pkgInfo = conf.XInfo pkg = conf.XPkg } else { pkgInfo = conf.Info pkg = conf.Pkg } _ = cursorObjIsDef if cursorObj == nil { for sel, obj := range pkgInfo.Selections { if cursor.pos >= sel.Sel.Pos() && cursor.pos <= sel.Sel.End() { cursorObj = obj.Obj() cursorSelection = obj break } } } if cursorObj == nil { for id, obj := range pkgInfo.Defs { if cursor.pos >= id.Pos() && cursor.pos <= id.End() { cursorObj = obj cursorObjIsDef = true break } } } _ = cursorSelection if cursorObj == nil { for id, obj := range pkgInfo.Uses { if cursor.pos >= id.Pos() && cursor.pos <= id.End() { cursorObj = obj break } } } if cursorObj == nil { return } kind, err := parserObjKind(cursorObj) if err != nil { log.Fatalln(err) } if kind == ObjField { if cursorObj.(*types.Var).Anonymous() { typ := orgType(cursorObj.Type()) if named, ok := typ.(*types.Named); ok { cursorObj = named.Obj() } } } cursorPkg := cursorObj.Pkg() cursorPos := cursorObj.Pos() //var fieldTypeInfo *types.Info var fieldTypeObj types.Object // if cursorPkg == pkg { // fieldTypeInfo = pkgInfo // } cursorIsInterfaceMethod := false var cursorInterfaceTypeName string if kind == ObjMethod && cursorSelection != nil && cursorSelection.Recv() != nil { sig := cursorObj.(*types.Func).Type().Underlying().(*types.Signature) if _, ok := sig.Recv().Type().Underlying().(*types.Interface); ok { if named, ok := cursorSelection.Recv().(*types.Named); ok { obj, typ := w.lookupNamedMethod(named, cursorObj.Name()) if obj != nil { cursorObj = obj } if typ != nil { cursorPkg = typ.Obj().Pkg() cursorInterfaceTypeName = typ.Obj().Name() } cursorIsInterfaceMethod = true } } } else if kind == ObjField && cursorSelection != nil { if recv := cursorSelection.Recv(); recv != nil { typ := orgType(recv) if typ != nil { if name, ok := typ.(*types.Named); ok { fieldTypeObj = name.Obj() na := w.lookupNamedField(name, cursorObj.Name()) if na != nil { fieldTypeObj = na.Obj() } } } } } if cursorPkg != nil && cursorPkg != pkg && kind != ObjPkgName && w.isBinaryPkg(cursorPkg.Path()) { conf := &PkgConfig{ IgnoreFuncBodies: true, AllowBinary: true, WithTestFiles: true, Info: &types.Info{ Defs: make(map[*ast.Ident]types.Object), }, } pkg, _ := w.Import("", cursorPkg.Path(), conf) if pkg != nil { if cursorIsInterfaceMethod { for _, obj := range conf.Info.Defs { if obj == nil { continue } if fn, ok := obj.(*types.Func); ok { if fn.Name() == cursorObj.Name() { if sig, ok := fn.Type().Underlying().(*types.Signature); ok { if named, ok := sig.Recv().Type().(*types.Named); ok { if named.Obj() != nil && named.Obj().Name() == cursorInterfaceTypeName { cursorPos = obj.Pos() break } } } } } } } else if kind == ObjField && fieldTypeObj != nil { for _, obj := range conf.Info.Defs { if obj == nil { continue } if _, ok := obj.(*types.TypeName); ok { if IsSameObject(fieldTypeObj, obj) { if t, ok := obj.Type().Underlying().(*types.Struct); ok { for i := 0; i < t.NumFields(); i++ { if t.Field(i).Id() == cursorObj.Id() { cursorPos = t.Field(i).Pos() break } } } break } } } } else { for k, v := range conf.Info.Defs { if k != nil && v != nil && IsSameObject(v, cursorObj) { cursorPos = k.Pos() break } } } } // if kind == ObjField || cursorIsInterfaceMethod { // fieldTypeInfo = conf.Info // } } // if kind == ObjField { // fieldTypeObj = w.LookupStructFromField(fieldTypeInfo, cursorPkg, cursorObj, cursorPos) // } if typesFindDef { fmt.Println(w.fset.Position(cursorPos)) } if typesFindInfo { if kind == ObjField && fieldTypeObj != nil { typeName := fieldTypeObj.Name() if fieldTypeObj.Pkg() != nil && fieldTypeObj.Pkg() != pkg { typeName = fieldTypeObj.Pkg().Name() + "." + fieldTypeObj.Name() } fmt.Println(typeName, simpleObjInfo(cursorObj)) } else if kind == ObjBuiltin { fmt.Println(builtinInfo(cursorObj.Name())) } else if kind == ObjPkgName { fmt.Println(cursorObj.String()) } else if cursorIsInterfaceMethod { fmt.Println(strings.Replace(simpleObjInfo(cursorObj), "(interface)", cursorPkg.Name()+"."+cursorInterfaceTypeName, 1)) } else { fmt.Println(simpleObjInfo(cursorObj)) } } if typesFindDoc && typesFindDef { pos := w.fset.Position(cursorPos) file := w.parsedFileCache[pos.Filename] if file != nil { line := pos.Line var group *ast.CommentGroup for _, v := range file.Comments { lastLine := w.fset.Position(v.End()).Line if lastLine == line || lastLine == line-1 { group = v } else if lastLine > line { break } } if group != nil { fmt.Println(group.Text()) } } } if !typesFindUse { return } var usages []int if kind == ObjPkgName { for id, obj := range pkgInfo.Uses { if obj != nil && obj.Id() == cursorObj.Id() { //!= nil && cursorObj.Pos() == obj.Pos() { usages = append(usages, int(id.Pos())) } } } else { // for id, obj := range pkgInfo.Defs { // if obj == cursorObj { //!= nil && cursorObj.Pos() == obj.Pos() { // usages = append(usages, int(id.Pos())) // } // } for id, obj := range pkgInfo.Uses { if obj == cursorObj { //!= nil && cursorObj.Pos() == obj.Pos() { usages = append(usages, int(id.Pos())) } } } var pkg_path string var xpkg_path string if conf.Pkg != nil { pkg_path = conf.Pkg.Path() } if conf.XPkg != nil { xpkg_path = conf.XPkg.Path() } if cursorPkg != nil && (cursorPkg.Path() == pkg_path || cursorPkg.Path() == xpkg_path) && kind != ObjPkgName { usages = append(usages, int(cursorPos)) } (sort.IntSlice(usages)).Sort() for _, pos := range usages { fmt.Println(w.fset.Position(token.Pos(pos))) } //check look for current pkg.object on pkg_test if typesFindUseAll || IsSamePkg(cursorPkg, conf.Pkg) { var addInfo *types.Info if conf.Cursor.xtest { addInfo = conf.Info } else { addInfo = conf.XInfo } if addInfo != nil && cursorPkg != nil { var usages []int // for id, obj := range addInfo.Defs { // if id != nil && obj != nil && obj.Id() == cursorObj.Id() { // usages = append(usages, int(id.Pos())) // } // } for k, v := range addInfo.Uses { if k != nil && v != nil && IsSameObject(v, cursorObj) { usages = append(usages, int(k.Pos())) } } (sort.IntSlice(usages)).Sort() for _, pos := range usages { fmt.Println(w.fset.Position(token.Pos(pos))) } } } if !typesFindUseAll { return } if cursorPkg == nil { return } var find_def_pkg string var uses_paths []string if cursorPkg.Path() != pkg_path && cursorPkg.Path() != xpkg_path { find_def_pkg = cursorPkg.Path() uses_paths = append(uses_paths, cursorPkg.Path()) } buildutil.ForEachPackage(&build.Default, func(importPath string, err error) { if err != nil { return } if importPath == conf.Pkg.Path() { return } bp, err := w.importPath(importPath, 0) if err != nil { return } find := false if bp.ImportPath == cursorPkg.Path() { find = true } else { for _, v := range bp.Imports { if v == cursorObj.Pkg().Path() { find = true break } } } if find { for _, v := range uses_paths { if v == bp.ImportPath { return } } uses_paths = append(uses_paths, bp.ImportPath) } }) w.imported = make(map[string]*types.Package) for _, v := range uses_paths { conf := &PkgConfig{ IgnoreFuncBodies: false, AllowBinary: true, WithTestFiles: true, Info: &types.Info{ Uses: make(map[*ast.Ident]types.Object), }, XInfo: &types.Info{ Uses: make(map[*ast.Ident]types.Object), }, } w.imported[v] = nil var usages []int vpkg, _ := w.Import("", v, conf) if vpkg != nil && vpkg != pkg { if conf.Info != nil { for k, v := range conf.Info.Uses { if k != nil && v != nil && IsSameObject(v, cursorObj) { usages = append(usages, int(k.Pos())) } } } if conf.XInfo != nil { for k, v := range conf.XInfo.Uses { if k != nil && v != nil && IsSameObject(v, cursorObj) { usages = append(usages, int(k.Pos())) } } } } if v == find_def_pkg { usages = append(usages, int(cursorPos)) } (sort.IntSlice(usages)).Sort() for _, pos := range usages { fmt.Println(w.fset.Position(token.Pos(pos))) } } }
func TestMoves(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("broken on Windows; see golang.org/issue/16384") } tests := []struct { ctxt *build.Context from, to string want map[string]string wantWarnings []string }{ // Simple example. { ctxt: fakeContext(map[string][]string{ "foo": {`package foo; type T int`}, "main": {`package main import "foo" var _ foo.T `}, }), from: "foo", to: "bar", want: map[string]string{ "/go/src/main/0.go": `package main import "bar" var _ bar.T `, "/go/src/bar/0.go": `package bar type T int `, }, }, // Example with subpackage. { ctxt: fakeContext(map[string][]string{ "foo": {`package foo; type T int`}, "foo/sub": {`package sub; type T int`}, "main": {`package main import "foo" import "foo/sub" var _ foo.T var _ sub.T `}, }), from: "foo", to: "bar", want: map[string]string{ "/go/src/main/0.go": `package main import "bar" import "bar/sub" var _ bar.T var _ sub.T `, "/go/src/bar/0.go": `package bar type T int `, "/go/src/bar/sub/0.go": `package sub; type T int`, }, }, // References into subpackages { ctxt: fakeContext(map[string][]string{ "foo": {`package foo; import "foo/a"; var _ a.T`}, "foo/a": {`package a; type T int`}, "foo/b": {`package b; import "foo/a"; var _ a.T`}, }), from: "foo", to: "bar", want: map[string]string{ "/go/src/bar/0.go": `package bar import "bar/a" var _ a.T `, "/go/src/bar/a/0.go": `package a; type T int`, "/go/src/bar/b/0.go": `package b import "bar/a" var _ a.T `, }, }, // External test packages { ctxt: buildutil.FakeContext(map[string]map[string]string{ "foo": { "0.go": `package foo; type T int`, "0_test.go": `package foo_test; import "foo"; var _ foo.T`, }, "baz": { "0_test.go": `package baz_test; import "foo"; var _ foo.T`, }, }), from: "foo", to: "bar", want: map[string]string{ "/go/src/bar/0.go": `package bar type T int `, "/go/src/bar/0_test.go": `package bar_test import "bar" var _ bar.T `, "/go/src/baz/0_test.go": `package baz_test import "bar" var _ bar.T `, }, }, // package import comments { ctxt: fakeContext(map[string][]string{"foo": {`package foo // import "baz"`}}), from: "foo", to: "bar", want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar" `}, }, { ctxt: fakeContext(map[string][]string{"foo": {`package foo /* import "baz" */`}}), from: "foo", to: "bar", want: map[string]string{"/go/src/bar/0.go": `package bar /* import "bar" */ `}, }, { ctxt: fakeContext(map[string][]string{"foo": {`package foo // import "baz"`}}), from: "foo", to: "bar", want: map[string]string{"/go/src/bar/0.go": `package bar // import "bar" `}, }, { ctxt: fakeContext(map[string][]string{"foo": {`package foo // import " this is not an import comment`}}), from: "foo", to: "bar", want: map[string]string{"/go/src/bar/0.go": `package bar // import " this is not an import comment `}, }, { ctxt: fakeContext(map[string][]string{"foo": {`package foo /* import " this is not an import comment */`}}), from: "foo", to: "bar", want: map[string]string{"/go/src/bar/0.go": `package bar /* import " this is not an import comment */ `}, }, // Import name conflict generates a warning, not an error. { ctxt: fakeContext(map[string][]string{ "x": {}, "a": {`package a; type A int`}, "b": {`package b; type B int`}, "conflict": {`package conflict import "a" import "b" var _ a.A var _ b.B `}, "ok": {`package ok import "b" var _ b.B `}, }), from: "b", to: "x/a", want: map[string]string{ "/go/src/a/0.go": `package a; type A int`, "/go/src/ok/0.go": `package ok import "x/a" var _ a.B `, "/go/src/conflict/0.go": `package conflict import "a" import "x/a" var _ a.A var _ b.B `, "/go/src/x/a/0.go": `package a type B int `, }, wantWarnings: []string{ `/go/src/conflict/0.go:4:8: renaming this imported package name "b" to "a"`, `/go/src/conflict/0.go:3:8: conflicts with imported package name in same block`, `/go/src/conflict/0.go:3:8: skipping update of this file`, }, }, // Rename with same base name. { ctxt: fakeContext(map[string][]string{ "x": {}, "y": {}, "x/foo": {`package foo type T int `}, "main": {`package main; import "x/foo"; var _ foo.T`}, }), from: "x/foo", to: "y/foo", want: map[string]string{ "/go/src/y/foo/0.go": `package foo type T int `, "/go/src/main/0.go": `package main import "y/foo" var _ foo.T `, }, }, } for _, test := range tests { ctxt := test.ctxt got := make(map[string]string) // Populate got with starting file set. rewriteFile and moveDirectory // will mutate got to produce resulting file set. buildutil.ForEachPackage(ctxt, func(importPath string, err error) { if err != nil { return } path := filepath.Join("/go/src", importPath, "0.go") if !buildutil.FileExists(ctxt, path) { return } f, err := ctxt.OpenFile(path) if err != nil { t.Errorf("unexpected error opening file: %s", err) return } bytes, err := ioutil.ReadAll(f) f.Close() if err != nil { t.Errorf("unexpected error reading file: %s", err) return } got[path] = string(bytes) }) var warnings []string reportError = func(posn token.Position, message string) { warning := fmt.Sprintf("%s:%d:%d: %s", filepath.ToSlash(posn.Filename), // for MS Windows posn.Line, posn.Column, message) warnings = append(warnings, warning) } writeFile = func(filename string, content []byte) error { got[filename] = string(content) return nil } moveDirectory = func(from, to string) error { for path, contents := range got { if strings.HasPrefix(path, from) { newPath := strings.Replace(path, from, to, 1) delete(got, path) got[newPath] = contents } } return nil } err := Move(ctxt, test.from, test.to, "") prefix := fmt.Sprintf("-from %q -to %q", test.from, test.to) if err != nil { t.Errorf("%s: unexpected error: %s", prefix, err) continue } if !reflect.DeepEqual(warnings, test.wantWarnings) { t.Errorf("%s: unexpected warnings:\n%s\nwant:\n%s", prefix, strings.Join(warnings, "\n"), strings.Join(test.wantWarnings, "\n")) } for file, wantContent := range test.want { k := filepath.FromSlash(file) gotContent, ok := got[k] delete(got, k) if !ok { // TODO(matloob): some testcases might have files that won't be // rewritten t.Errorf("%s: file %s not rewritten", prefix, file) continue } if gotContent != wantContent { t.Errorf("%s: rewritten file %s does not match expectation; got <<<%s>>>\n"+ "want <<<%s>>>", prefix, file, gotContent, wantContent) } } // got should now be empty for file := range got { t.Errorf("%s: unexpected rewrite of file %s", prefix, file) } } }