package generate import ( "bytes" "code.squareroundforest.org/arpio/notation" "fmt" "os" "path" "runtime" "strings" "testing" ) func check(c ...string) map[string]string { m := make(map[string]string) for i := 0; i < len(c); i += 2 { name := c[i] var value string if i < len(c)-1 { value = c[i+1] } m[name] = value } return m } func testGenerate(check map[string]string, errstr string, o options, gopath ...string) func(t *testing.T) { return func(t *testing.T) { d, err := generate(o, gopath...) if errstr != "" { if err == nil { t.Fatal("failed to fail") } if !strings.Contains(err.Error(), errstr) { t.Fatal("unexpected error", err) } return } if err != nil { t.Fatal(err) } var failed bool for name, expect := range check { dn, ok := d[name] if !ok { failed = true break } if !strings.Contains(dn, expect) { failed = true break } } if failed { t.Log("failed to get the right documentation") t.Log("expected matches:") t.Log(notation.Sprintw(check)) t.Log("documetation got:") t.Log(notation.Sprintw(d)) t.Fatal() } } } func TestGenerate(t *testing.T) { wd := path.Clean(path.Join(os.Getenv("PWD"), "..")) h := os.Getenv("HOME") o := options{ wd: wd, goroot: runtime.GOROOT(), gomod: "code.squareroundforest.org/arpio/docreflect", modules: map[string]string{ "golang.org/x/mod@v0.27.0": path.Join(h, "go", "pkg/mod", "golang.org/x/mod@v0.27.0"), "code.squareroundforest.org/arpio/notation@v0.0.0-20241225183158-af3bd591a174": path.Join( h, "go", "pkg/mod", "code.squareroundforest.org/arpio/notation@v0.0.0-20241225183158-af3bd591a174", ), "code.squareroundforest.org/arpio/docreflect": wd, }, } t.Run("generate", func(t *testing.T) { t.Run( "stdlib", testGenerate(check("strings.Join", "Join concatenates", "strings.Join", "func(elems, sep)"), "", o, "strings.Join"), ) notationPrintln := "code.squareroundforest.org/arpio/notation.Println" t.Run( "imported module", testGenerate( check( notationPrintln, "Println prints", notationPrintln, "func(v)", ), "", o, notationPrintln, ), ) localDocs := "code.squareroundforest.org/arpio/docreflect.Docs" t.Run( "local", testGenerate(check(localDocs, "Docs returns the documentation for"), "", o, localDocs), ) packagePath := "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage" t.Run( "package", testGenerate( check( packagePath, "Package testpackage is a test package", fmt.Sprintf("%s.ExportedType", packagePath), "ExportedType has docs", fmt.Sprintf("%s.ExportedType.Bar.Baz", packagePath), "Baz is another field", ), "", o, packagePath, ), ) t.Run("symbol", func(t *testing.T) { t.Run("type", testGenerate( check( "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType", "ExportedType has docs", "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType2", "", ), "", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType", "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType2", )) t.Run("const", testGenerate( check( "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.C1", "C1 is a const", ), "", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.C1", )) t.Run("const grouped", testGenerate( check( "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.C3", "Cx is a const", ), "", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.C3", )) t.Run("var", testGenerate( check( "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.V1", "V1 is a var", ), "", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.V1", )) t.Run("var grouped", testGenerate( check( "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.V3", "Vx is a var", ), "", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.V3", )) t.Run("func", testGenerate( check( "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedFunc", "ExportedFunc has documentation", "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.New", "New create a new instance of ExportedType", ), "", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.New", "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedFunc", )) }) t.Run("field", testGenerate( check( "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType.Foo", "Foo is a field", ), "", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType.Foo", )) t.Run("method", testGenerate( check( "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType.Method", "Method is a method of ExportedType", ), "", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType.Method", )) t.Run("inline struct type expression", testGenerate( check( "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType.Bar", "Bar is an inline struct type expression", "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType.Bar.Baz", "Baz is another field", ), "", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType.Bar", "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType.Bar.Baz", )) t.Run("unexported symbol", testGenerate( check( "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.unexportedFunc", "unexportedFunc can have documentation", ), "", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.unexportedFunc", )) mainFunc := "code.squareroundforest.org/arpio/docreflect/internal/tests/src/command.main" t.Run("main package", testGenerate(check(mainFunc, "main func"), "", o, mainFunc)) anum := fmt.Sprintf("%s.A", packagePath) bnum := fmt.Sprintf("%s.B", packagePath) cnum := fmt.Sprintf("%s.C", packagePath) t.Run( "grouped types", testGenerate( check(anum, "A is a number", bnum, "B is another number", cnum, "C is a third number"), "", o, packagePath, ), ) t.Run( "grouped types as symbol", testGenerate(check(anum, "A is a number"), "", o, anum), ) pfoo := fmt.Sprintf("%s.ExportedType.PFoo", packagePath) barPBaz := fmt.Sprintf("%s.ExportedType.Bar.PBaz", packagePath) t.Run( "pointer field", testGenerate( check(pfoo, "PFoo is a pointer field", barPBaz, "PBaz is another pointer field"), "", o, packagePath, ), ) t.Run( "pointer field as symbol", testGenerate( check(pfoo, "PFoo is a pointer field", barPBaz, "PBaz is another pointer field"), "", o, pfoo, barPBaz, ), ) quxQuux := fmt.Sprintf("%s.ExportedType.Qux.Quux", packagePath) t.Run( "anonymous pointer struct field", testGenerate( check(quxQuux, "Quux is a number"), "", o, packagePath, ), ) t.Run( "anonymous pointer struct field as symbol", testGenerate( check(quxQuux, "Quux is a number"), "", o, quxQuux, ), ) }) t.Run("errors", func(t *testing.T) { t.Run("package not found", testGenerate(nil, "package", o, "foo.bar.baz/qux")) t.Run( "symbol not found", testGenerate(nil, "symbol", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.Qux"), ) t.Run( "field not found", testGenerate( nil, "symbol", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType.Corge", ), ) t.Run( "field not found, second level", testGenerate( nil, "symbol", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType.Foo.Qux", ), ) t.Run( "method not found", testGenerate(nil, "symbol", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.ExportedType2.Qux"), ) t.Run( "invalid path", testGenerate(nil, "foo", o, "./foo/bar"), ) t.Run( "non-existent module path", testGenerate(nil, "package", o, "code.squareroundforest.org/arpio/notation/foo.Foo"), ) t.Run( "parse failed, syntax error", testGenerate(nil, "package", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/syntaxerror"), ) t.Run( "no type spec", testGenerate(nil, "symbol", o, "code.squareroundforest.org/arpio/docreflect/internal/tests/src/testpackage.Foo.Qux"), ) }) t.Run("init options", func(t *testing.T) { o := initOptions() if o.wd != os.Getenv("PWD") { t.Fatal("wd") } if o.goroot != runtime.GOROOT() { t.Fatal("goroot") } if o.gomod != "code.squareroundforest.org/arpio/docreflect" { t.Fatal("gomod") } for _, module := range []string{ "code.squareroundforest.org/arpio/notation", "golang.org/x/mod", } { var found bool for key := range o.modules { if strings.Contains(key, module) { found = true } } if !found { t.Fatal("gomod") } } }) } func TestFormat(t *testing.T) { b := bytes.NewBuffer(nil) d := map[string]string{ "foo": "bar", "baz": "qux", } if err := format(b, "testpackage", d); err != nil { t.Fatal(err) } o := b.String() if o != `package testpackage import "code.squareroundforest.org/arpio/docreflect" func init() { docreflect.Register("baz", "qux") docreflect.Register("foo", "bar") }` { t.Fatal() } }