func (v *ShortError) VisitExpr(scope *ast.Scope, expr ast.Expr) ScopeVisitor { if expr, ok := expr.(*ast.CallExpr); ok { if fun, ok := expr.Fun.(*ast.Ident); ok && fun.Name == MustKeyword { if len(expr.Args) != 1 { pos := v.file.Fset.Position(fun.Pos()) fmt.Printf("%s:%d:%d: 'must' builtin must be called with exactly one argument\n", pos.Filename, pos.Line, pos.Column) return nil } tmpVar, tmpErr := v.tempVar("tmp_", scope), v.tempVar("err_", scope) mustexpr := v.file.Get(expr.Args[0]) if v.block == nil { // if in top level decleration v.addToInit("if " + tmpErr + " != nil {panic(" + tmpErr + ")};") *v.patches = append(*v.patches, patch.Replace(expr, tmpVar), patch.Insert(afterImports(v.file.File), ";var "+tmpVar+", "+tmpErr+" = "+mustexpr)) } else { *v.patches = append(*v.patches, patch.Insert(v.stmt.Pos(), fmt.Sprint("var ", tmpVar, ", ", tmpErr, " = ", mustexpr, "; ", "if ", tmpErr, " != nil {panic(", tmpErr, ")};"))) *v.patches = append(*v.patches, patch.Replace(expr, tmpVar)) } } } return v }
func (v *ShortError) VisitStmt(scope *ast.Scope, stmt ast.Stmt) ScopeVisitor { v.stmt = stmt switch stmt := stmt.(type) { case *ast.BlockStmt: return &ShortError{v.file, v.patches, v.stmt, stmt, 0, new([]byte)} case *ast.ExprStmt: if call := calltomust(stmt.X); call != nil { // TODO(elazarl): depends on number of variables it returns, currently we assume one pos := v.file.Fset.Position(stmt.Pos()) fmt.Printf("%s:%d:%d: 'must' builtin must be assigned into variable\n", pos.Filename, pos.Line, pos.Column) } case *ast.AssignStmt: if len(stmt.Rhs) != 1 { return v } if rhs, ok := stmt.Rhs[0].(*ast.CallExpr); ok { if fun, ok := rhs.Fun.(*ast.Ident); ok && fun.Name == MustKeyword { if stmt.Tok == token.DEFINE { tmpVar := v.tempVar("assignerr_", scope) *v.patches = append(*v.patches, patch.Insert(stmt.TokPos, ", "+tmpVar+" "), patch.Replace(fun, ""), patch.Insert(stmt.End(), "; if "+tmpVar+" != nil "+ "{ panic("+tmpVar+") };"), ) for _, arg := range rhs.Args { v.VisitExpr(scope, arg) } return nil } else if stmt.Tok == token.ASSIGN { vars := []string{} for i := 0; i < len(stmt.Lhs); i++ { vars = append(vars, v.tempVar(fmt.Sprint("assgn", i, "_"), scope)) } assgnerr := v.tempVar("assgnErr_", scope) *v.patches = append(*v.patches, patch.Insert(stmt.Pos(), strings.Join(append(vars, assgnerr), ", ")+":="), patch.InsertNode(stmt.Pos(), rhs.Args[0]), patch.Insert(stmt.Pos(), "; if "+assgnerr+" != nil "+ "{ panic("+assgnerr+") };"), patch.Replace(rhs, strings.Join(vars, ", ")), ) v.VisitExpr(scope, rhs.Args[0]) return nil } } } } return v }
func (i *Instrumentable) instrumentPatchable(outdir, relpath string, pkg *patch.PatchablePkg, f func(file *patch.PatchableFile) patch.Patches) error { path := "" if build.IsLocalImport(relpath) { path = filepath.Join("locals", relpath) path = strings.Replace(path, "..", "__", -1) } else if relpath != "" { path = filepath.Join("gopath", i.pkg.ImportPath) } if err := os.MkdirAll(filepath.Join(outdir, path), 0755); err != nil { return err } for filename, file := range pkg.Files { if outfile, err := os.Create(filepath.Join(outdir, path, filepath.Base(filename))); err != nil { return err } else { patches := f(file) // TODO(elazar): check the relative path from current location (aka relpath, path), to the import path // (aka v) for _, imp := range file.File.Imports { switch v := imp.Path.Value[1 : len(imp.Path.Value)-1]; { case v == i.pkg.ImportPath: patches = appendNoContradict(patches, patch.Replace(imp.Path, `"."`)) case !i.relevantImport(v): continue case build.IsLocalImport(v): rel, err := filepath.Rel(path, filepath.Join("locals", v)) if err != nil { return err } patches = appendNoContradict(patches, patch.Replace(imp.Path, `"./`+rel+`"`)) default: if v == i.name { v = "" } else { v = filepath.Join("gopath", v) } rel, err := filepath.Rel(path, v) if err != nil { return err } patches = appendNoContradict(patches, patch.Replace(imp.Path, `"./`+rel+`"`)) } } file.FprintPatched(outfile, file.File, patches) if err := outfile.Close(); err != nil { return err } } } return nil }
func (v *ShortError) VisitDecl(scope *ast.Scope, decl ast.Decl) ScopeVisitor { if decl, ok := decl.(*ast.GenDecl); ok { for _, spec := range decl.Specs { // We'll act only in cases like top level `var a, b, c = must(expr)` if spec, ok := spec.(*ast.ValueSpec); ok && len(spec.Values) == 1 { if fun, ok := spec.Values[0].(*ast.CallExpr); ok { if name, ok := fun.Fun.(*ast.Ident); !ok || name.Name != MustKeyword { return v } if len(fun.Args) != 1 { pos := v.file.Fset.Position(fun.Pos()) fmt.Printf("%s:%d:%d: 'must' builtin must be called with exactly one argument\n", pos.Filename, pos.Line, pos.Column) return nil } tmpErr := v.tempVar("tlderr_", scope) *v.patches = append(*v.patches, patch.Insert(spec.Names[len(spec.Names)-1].End(), ", "+tmpErr), patch.Replace(fun, v.file.Get(fun.Args[0]))) v.addToInit("if " + tmpErr + " != nil { panic(" + tmpErr + ") }") } } } return nil } return v }
func TestGuessSubpackage(t *testing.T) { fs := dir( "test", dir("sub1", file("sub1.go", "package sub1")), dir("sub2", file("sub2.go", "package sub2")), dir("sub3", file("sub3.go", `package sub3;import "../sub1"`)), file("base.go", `package test1;import "./sub1"`), file("a_test.go", `package test1;import "./sub2"`), ) OrFail(fs.Build("."), t) defer func() { OrFail(os.RemoveAll("test"), t) }() func() { pkg, err := ImportDir("", "test/sub3") OrFail(err, t) if fmt.Sprint(pkg.Files()) != "[test/sub3/sub3.go]" { t.Fatal("Expected [test/sub3/sub3.go] got", pkg.Files()) } OrFail(os.Mkdir("temp", 0755), t) defer func() { OrFail(os.RemoveAll("temp"), t) }() _, err = pkg.InstrumentTo(true, "temp", func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.File, "koko")} }) OrFail(err, t) dir("temp", dir("locals", dir("__", dir("sub1", file("sub1.go", "koko")))), file("sub3.go", "koko"), ).AssertEqual("temp", t) }() }
func TestGopath(t *testing.T) { fs := dir( "gopath/src/mypkg", file("a.go", "package mypkg"), file("a_test.go", "package mypkg"), ) OrFail(fs.Build("."), t) defer func() { OrFail(os.RemoveAll("gopath"), t) }() gopath, err := filepath.Abs("gopath") OrFail(err, t) prevgopath := build.Default.GOPATH defer func() { build.Default.GOPATH = prevgopath }() build.Default.GOPATH = gopath pkg, err := Import("mypkg", "mypkg") OrFail(err, t) OrFail(os.Mkdir("temp", 0755), t) defer func() { OrFail(os.RemoveAll("temp"), t) }() _, err = pkg.InstrumentTo(true, "temp", func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.File, "koko")} }) OrFail(err, t) dir("temp", file("a.go", "koko"), file("a_test.go", "koko"), ).AssertEqual("temp", t) }
func TestGuessSubpkgGopath(t *testing.T) { fs := dir( "gopath/src/mypkg", dir("sub1", file("sub1.go", "package sub1")), dir("sub2", file("sub2.go", "package sub2")), dir("sub3", dir("subsub3", file("subsub3.go", `package subsub3;import "mypkg/sub1"`))), file("base.go", `package test1;import "mypkg/sub1"`), file("a_test.go", `package test1;import "mypkg/sub2"`), ) // TODO(elazar): find a way to use build.Context gopath, err := filepath.Abs("gopath") OrFail(err, t) prevgopath := build.Default.GOPATH defer func() { build.Default.GOPATH = prevgopath }() build.Default.GOPATH = gopath OrFail(fs.Build("."), t) defer func() { OrFail(os.RemoveAll("gopath"), t) }() func() { pkg, err := Import("", "mypkg/sub3/subsub3") OrFail(err, t) OrFail(os.Mkdir("temp", 0755), t) defer func() { OrFail(os.RemoveAll("temp"), t) }() _, err = pkg.InstrumentTo(true, "temp", func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.File, "koko")} }) OrFail(err, t) dir("temp", file("subsub3.go", "koko"), ).AssertEqual("temp", t) }() }
func (p *patchUnused) UnusedImport(imp *ast.ImportSpec) { if imp.Name != nil { p.patches = append(p.patches, patch.Replace(imp.Name, "_")) } else { p.patches = append(p.patches, patch.Insert(imp.Pos(), "_ ")) } }
func TestInline(t *testing.T) { OrFail(dir("temp", file("a.go", "package main;func main() {println(`bobo`)}")).Build("."), t) defer os.RemoveAll("temp") pkg, err := ImportDir("", "temp") OrFail(err, t) OrFail(pkg.InstrumentInline(func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.File, "koko")} }), t) dir("temp", file("a.go", "koko")).AssertEqual("temp", t) }
func TestGuessStdlibPkg(t *testing.T) { pkg, err := Import("", "io/ioutil") OrFail(err, t) OrFail(os.Mkdir("temp", 0755), t) defer func() { OrFail(os.RemoveAll("temp"), t) }() _, err = pkg.InstrumentTo(true, "temp", func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.All(), "koko")} }) OrFail(err, t) dir("temp", dir("goroot", dir("src", dir("pkg", dir("io", dir("ioutil", file("ioutil.go", "koko")))))), ).AssertContains("temp", t) }
func TestDontTakeStdLibByDefault(t *testing.T) { fs := dir( "test", file("main.go", `package main;import "fmt";func main() {fmt.Println()}`), ) OrFail(fs.Build("."), t) defer func() { OrFail(os.RemoveAll("test"), t) }() pkg, err := ImportDir("", "test") OrFail(err, t) OrFail(os.Mkdir("temp", 0755), t) defer func() { OrFail(os.RemoveAll("temp"), t) }() _, err = pkg.InstrumentTo(true, "temp", func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.All(), "koko")} }) OrFail(err, t) dir("temp", file("main.go", "koko"), ).AssertEqual("temp", t) }
func TestGetStdlibIfInstructed(t *testing.T) { fs := dir( "test", file("main.go", `package main;import "fmt";func main() {fmt.Println()}`), ) OrFail(fs.Build("."), t) defer func() { OrFail(os.RemoveAll("test"), t) }() pkg, err := ImportDir("fmt", "test") pkg.InstrumentGoroot = true OrFail(err, t) defer func() { OrFail(os.RemoveAll("temp"), t) }() _, err = pkg.InstrumentTo(true, "temp", func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.All(), "koko")} }) OrFail(err, t) dir("temp", dir("goroot", dir("src", dir("pkg", dir("fmt", file("doc.go", "koko")), dir("go", dir("parser"))))), // should be symlinked file("main.go", "koko"), ).AssertContains("temp", t) }
func TestDir(t *testing.T) { fs := dir( "test1", file("a.go", "package test1"), file("a_test.go", "package test1"), ) OrFail(fs.Build("."), t) defer func() { OrFail(os.RemoveAll("test1"), t) }() pkg, err := ImportDir("test1", "test1") OrFail(err, t) if fmt.Sprint(pkg.Files()) != "[test1/a.go]" { t.Fatal("Expected [a.go] got", pkg.Files()) } outdir, _, err := pkg.Instrument(true, func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.File, "koko")} }) defer func() { OrFail(os.RemoveAll(outdir), t) }() OrFail(err, t) dir(filepath.Base(outdir), file("a.go", "koko"), file("a_test.go", "koko"), ).AssertEqual(outdir, t) }
func (i *Instrumentable) instrumentPatchable(outdir, relpath string, pkg *patch.PatchablePkg, f func(file *patch.PatchableFile) patch.Patches) error { path := "" if build.IsLocalImport(relpath) { path = strings.Replace(relpath, "..", "__", -1) path = filepath.Join("locals", path) } else if i.pkg.Goroot { path = filepath.Join("goroot", "src", "pkg", i.pkg.ImportPath) i.gorootPkgs[i.pkg.ImportPath] = true } else if relpath != "" { path = filepath.Join("gopath", i.pkg.ImportPath) } if err := os.MkdirAll(filepath.Join(outdir, path), 0755); err != nil { return err } // copy all none-go files (TODO: symlink? OTOH you wouldn't have standalone package) for _, files := range [][]string{i.pkg.CFiles, i.pkg.HFiles, i.pkg.SFiles, i.pkg.SysoFiles} { for _, file := range files { if err := cp(filepath.Join(outdir, path, file), filepath.Join(i.pkg.Dir, file)); err != nil { return err } } } for filename, file := range pkg.Files { if outfile, err := os.Create(filepath.Join(outdir, path, filepath.Base(filename))); err != nil { return err } else { patches := f(file) // TODO(elazar): check the relative path from current location (aka relpath, path), to the import path // (aka v) for _, imp := range file.File.Imports { switch v := imp.Path.Value[1 : len(imp.Path.Value)-1]; { case v == i.pkg.ImportPath: patches = appendNoContradict(patches, patch.Replace(imp.Path, `"."`)) case !i.relevantImport(v) || i.gorootPkgs[v]: continue case build.IsLocalImport(v): rel, err := filepath.Rel(path, filepath.Join("locals", v)) if err != nil { return err } patches = appendNoContradict(patches, patch.Replace(imp.Path, `"./`+rel+`"`)) default: if v == i.name { v = "" } else { v = filepath.Join("gopath", v) } rel, err := filepath.Rel(path, v) if err != nil { return err } patches = appendNoContradict(patches, patch.Replace(imp.Path, `"./`+rel+`"`)) } } if _, err := file.FprintPatched(outfile, file.All(), patches); err != nil { return err } if err := outfile.Close(); err != nil { return err } } } return nil }
func TestGopathSubDir(t *testing.T) { fs := dir( "gopath/src/mypkg", dir("sub1", file("sub1.go", "package sub1")), dir("sub2", file("sub2.go", "package sub2")), dir("sub3", dir("subsub3", file("subsub3.go", `package subsub3;import "mypkg/sub1"`))), file("base.go", `package test1;import "mypkg/sub1"`), file("a_test.go", `package test1;import "mypkg/sub2"`), ) // TODO(elazar): find a way to use build.Context gopath, err := filepath.Abs("gopath") OrFail(err, t) prevgopath := build.Default.GOPATH defer func() { build.Default.GOPATH = prevgopath }() build.Default.GOPATH = gopath OrFail(fs.Build("."), t) defer func() { OrFail(os.RemoveAll("gopath"), t) }() func() { pkg, err := Import("mypkg", "mypkg") OrFail(err, t) OrFail(os.Mkdir("temp", 0755), t) defer func() { OrFail(os.RemoveAll("temp"), t) }() _, err = pkg.InstrumentTo(true, "temp", func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.File, "koko")} }) OrFail(err, t) dir("temp", dir("gopath", dir("mypkg", dir("sub1", file("sub1.go", "koko")), dir("sub2", file("sub2.go", "koko")))), file("base.go", "koko"), file("a_test.go", "koko"), ).AssertEqual("temp", t) }() func() { pkg, err := Import("mypkg", "mypkg/sub3/subsub3") OrFail(err, t) if len(pkg.Files()) != 1 || pkg.Files()[0] != filepath.Join(gopath, "src", "mypkg", "sub3", "subsub3", "subsub3.go") { t.Fatal("When import \"mypkg/sub3/subsub3\" Expected", filepath.Join(gopath, "src", "mypkg", "sub3", "subsub3", "subsub3.go")) } OrFail(os.Mkdir("temp", 0755), t) defer func() { OrFail(os.RemoveAll("temp"), t) }() _, err = pkg.InstrumentTo(true, "temp", func(pf *patch.PatchableFile) patch.Patches { return nil }) OrFail(err, t) dir("temp", dir("gopath", dir("mypkg", dir("sub1", file("sub1.go", "package sub1")))), file("subsub3.go", `package subsub3;import "./gopath/mypkg/sub1"`), ).AssertEqual("temp", t) }() func() { pkg, err := Import("mypkg/sub3", "mypkg/sub3/subsub3") OrFail(err, t) if len(pkg.Files()) != 1 || pkg.Files()[0] != filepath.Join(gopath, "src", "mypkg", "sub3", "subsub3", "subsub3.go") { t.Fatal(`When import "mypkg/sub3/subsub3" Expected`, filepath.Join(gopath, "src", "mypkg", "sub3", "subsub3", "subsub3.go")) } OrFail(os.Mkdir("temp", 0755), t) defer func() { OrFail(os.RemoveAll("temp"), t) }() _, err = pkg.InstrumentTo(true, "temp", func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.File, "koko")} }) OrFail(err, t) dir("temp", file("subsub3.go", "koko"), ).AssertEqual("temp", t) }() func() { pkg, err := Import("mypkg", "mypkg/sub2") OrFail(err, t) OrFail(os.Mkdir("temp", 0755), t) defer func() { OrFail(os.RemoveAll("temp"), t) }() _, err = pkg.InstrumentTo(true, "temp", func(pf *patch.PatchableFile) patch.Patches { return patch.Patches{patch.Replace(pf.File, "koko")} }) OrFail(err, t) dir("temp", file("sub2.go", "koko"), ).AssertEqual("temp", t) }() }