github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/gnovm/pkg/doc/doc_test.go (about) 1 package doc 2 3 import ( 4 "bytes" 5 "os" 6 "path/filepath" 7 "strings" 8 "testing" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 ) 13 14 func TestResolveDocumentable(t *testing.T) { 15 p, err := os.Getwd() 16 require.NoError(t, err) 17 path := func(s string) string { return filepath.Join(p, "testdata/integ", s) } 18 dirs := newDirs([]string{path("")}, []string{path("mod")}) 19 getDir := func(p string) bfsDir { return dirs.findDir(path(p))[0] } 20 pdata := func(p string, unexp bool) *pkgData { 21 pd, err := newPkgData(getDir(p), unexp) 22 require.NoError(t, err) 23 return pd 24 } 25 26 tt := []struct { 27 name string 28 args []string 29 unexp bool 30 expect Documentable 31 errContains string 32 }{ 33 {"package", []string{"crypto/rand"}, false, &documentable{bfsDir: getDir("crypto/rand")}, ""}, 34 {"packageMod", []string{"gno.land/mod"}, false, &documentable{bfsDir: getDir("mod")}, ""}, 35 {"dir", []string{"./testdata/integ/crypto/rand"}, false, &documentable{bfsDir: getDir("crypto/rand")}, ""}, 36 {"dirMod", []string{"./testdata/integ/mod"}, false, &documentable{bfsDir: getDir("mod")}, ""}, 37 {"dirAbs", []string{path("crypto/rand")}, false, &documentable{bfsDir: getDir("crypto/rand")}, ""}, 38 // test_notapkg exists in local dir and also path("test_notapkg"). 39 // ResolveDocumentable should first try local dir, and seeing as it is not a valid dir, try searching it as a package. 40 {"dirLocalMisleading", []string{"test_notapkg"}, false, &documentable{bfsDir: getDir("test_notapkg")}, ""}, 41 { 42 "normalSymbol", 43 []string{"crypto/rand.Flag"}, 44 false, 45 &documentable{bfsDir: getDir("crypto/rand"), symbol: "Flag", pkgData: pdata("crypto/rand", false)}, "", 46 }, 47 { 48 "normalAccessible", 49 []string{"crypto/rand.Generate"}, 50 false, 51 &documentable{bfsDir: getDir("crypto/rand"), symbol: "Generate", pkgData: pdata("crypto/rand", false)}, "", 52 }, 53 { 54 "normalSymbolUnexp", 55 []string{"crypto/rand.unexp"}, 56 true, 57 &documentable{bfsDir: getDir("crypto/rand"), symbol: "unexp", pkgData: pdata("crypto/rand", true)}, "", 58 }, 59 { 60 "normalAccessibleFull", 61 []string{"crypto/rand.Rand.Name"}, 62 false, 63 &documentable{bfsDir: getDir("crypto/rand"), symbol: "Rand", accessible: "Name", pkgData: pdata("crypto/rand", false)}, "", 64 }, 65 { 66 "disambiguate", 67 []string{"rand.Flag"}, 68 false, 69 &documentable{bfsDir: getDir("crypto/rand"), symbol: "Flag", pkgData: pdata("crypto/rand", false)}, "", 70 }, 71 { 72 "disambiguate2", 73 []string{"rand.Crypto"}, 74 false, 75 &documentable{bfsDir: getDir("crypto/rand"), symbol: "Crypto", pkgData: pdata("crypto/rand", false)}, "", 76 }, 77 { 78 "disambiguate3", 79 []string{"rand.Normal"}, 80 false, 81 &documentable{bfsDir: getDir("rand"), symbol: "Normal", pkgData: pdata("rand", false)}, "", 82 }, 83 { 84 "disambiguate4", // just "rand" should use the directory that matches it exactly. 85 []string{"rand"}, 86 false, 87 &documentable{bfsDir: getDir("rand")}, "", 88 }, 89 { 90 "wdSymbol", 91 []string{"WdConst"}, 92 false, 93 &documentable{bfsDir: getDir("wd"), symbol: "WdConst", pkgData: pdata("wd", false)}, "", 94 }, 95 96 {"errInvalidArgs", []string{"1", "2", "3"}, false, nil, "invalid arguments: [1 2 3]"}, 97 {"errNoCandidates", []string{"math", "Big"}, false, nil, `package not found: "math"`}, 98 {"errNoCandidates2", []string{"LocalSymbol"}, false, nil, `package not found`}, 99 {"errNoCandidates3", []string{"Symbol.Accessible"}, false, nil, `package not found`}, 100 {"errNonExisting", []string{"rand.NotExisting"}, false, nil, `could not resolve arguments`}, 101 {"errIgnoredMod", []string{"modignored"}, false, nil, `package not found`}, 102 {"errIgnoredMod2", []string{"./testdata/integ/modignored"}, false, nil, `package not found`}, 103 {"errUnexp", []string{"crypto/rand.unexp"}, false, nil, "could not resolve arguments"}, 104 {"errDirNotapkg", []string{"./test_notapkg"}, false, nil, `package not found: "./test_notapkg"`}, 105 } 106 107 for _, tc := range tt { 108 tc := tc 109 t.Run(tc.name, func(t *testing.T) { 110 // Wd prefix mean test relative to local directory - 111 // mock change local dir by setting the fpAbs variable (see doc.go) to match 112 // testdata/integ/wd when we call it on ".". 113 if strings.HasPrefix(tc.args[0], "Wd") { 114 fpAbs = func(s string) (string, error) { return filepath.Clean(filepath.Join(path("wd"), s)), nil } 115 defer func() { fpAbs = filepath.Abs }() 116 } 117 result, err := ResolveDocumentable( 118 []string{path("")}, []string{path("mod")}, 119 tc.args, tc.unexp, 120 ) 121 // we use stripFset because d.pkgData.fset contains sync/atomic values, 122 // which in turn makes reflect.DeepEqual compare the two sync.Atomic values. 123 assert.Equal(t, stripFset(tc.expect), stripFset(result), "documentables should match") 124 if tc.errContains == "" { 125 assert.NoError(t, err) 126 } else { 127 assert.ErrorContains(t, err, tc.errContains) 128 } 129 }) 130 } 131 } 132 133 func stripFset(p Documentable) Documentable { 134 if d, ok := p.(*documentable); ok && d.pkgData != nil { 135 d.pkgData.fset = nil 136 } 137 return p 138 } 139 140 func TestDocument(t *testing.T) { 141 // the format itself can change if the design is to be changed, 142 // we want to make sure that given information is available when calling 143 // Document. 144 abspath, err := filepath.Abs("./testdata/integ/crypto/rand") 145 require.NoError(t, err) 146 dir := bfsDir{ 147 importPath: "crypto/rand", 148 dir: abspath, 149 } 150 151 tt := []struct { 152 name string 153 d *documentable 154 opts *WriteDocumentationOptions 155 contains []string 156 }{ 157 {"base", &documentable{bfsDir: dir}, nil, []string{"func Crypto", "!Crypto symbol", "func NewRand", "!unexp", "type Flag", "!Name"}}, 158 {"func", &documentable{bfsDir: dir, symbol: "crypto"}, nil, []string{"Crypto symbol", "func Crypto", "!func NewRand", "!type Flag"}}, 159 {"funcWriter", &documentable{bfsDir: dir, symbol: "NewWriter"}, nil, []string{"func NewWriter() io.Writer", "!func Crypto"}}, 160 {"tp", &documentable{bfsDir: dir, symbol: "Rand"}, nil, []string{"type Rand", "comment1", "!func Crypto", "!unexp ", "!comment4", "Has unexported"}}, 161 {"tpField", &documentable{bfsDir: dir, symbol: "Rand", accessible: "Value"}, nil, []string{"type Rand", "!comment1", "comment2", "!func Crypto", "!unexp", "elided"}}, 162 { 163 "tpUnexp", 164 &documentable{bfsDir: dir, symbol: "Rand"}, 165 &WriteDocumentationOptions{Unexported: true}, 166 []string{"type Rand", "comment1", "!func Crypto", "unexp ", "comment4", "!Has unexported"}, 167 }, 168 { 169 "symUnexp", 170 &documentable{bfsDir: dir, symbol: "unexp"}, 171 &WriteDocumentationOptions{Unexported: true}, 172 []string{"var unexp", "!type Rand", "!comment1", "!comment4", "!func Crypto", "!Has unexported"}, 173 }, 174 { 175 "fieldUnexp", 176 &documentable{bfsDir: dir, symbol: "Rand", accessible: "unexp"}, 177 &WriteDocumentationOptions{Unexported: true}, 178 []string{"type Rand", "!comment1", "comment4", "!func Crypto", "elided", "!Has unexported"}, 179 }, 180 } 181 182 buf := &bytes.Buffer{} 183 for _, tc := range tt { 184 tc := tc 185 t.Run(tc.name, func(t *testing.T) { 186 buf.Reset() 187 err := tc.d.WriteDocumentation(buf, tc.opts) 188 require.NoError(t, err) 189 s := buf.String() 190 for _, c := range tc.contains { 191 if c[0] == '!' { 192 assert.NotContains(t, s, c[1:]) 193 } else { 194 assert.Contains(t, s, c) 195 } 196 } 197 }) 198 } 199 } 200 201 func Test_parseArgParts(t *testing.T) { 202 tt := []struct { 203 name string 204 args []string 205 exp *docArgs 206 }{ 207 {"noArgs", []string{}, &docArgs{pkg: "."}}, 208 209 {"oneAmbiguous", []string{"ambiguous"}, &docArgs{pkg: "ambiguous", pkgAmbiguous: true}}, 210 {"onePath", []string{"pkg/path"}, &docArgs{pkg: "pkg/path"}}, 211 {"oneSpecial", []string{".."}, &docArgs{pkg: ".."}}, 212 {"oneSpecial2", []string{"../../../.."}, &docArgs{pkg: "../../../.."}}, 213 {"oneSpecial3", []string{"../upper/.."}, &docArgs{pkg: "../upper/.."}}, 214 {"oneSpecial4", []string{"."}, &docArgs{pkg: "."}}, 215 216 {"twoPkgSym", []string{"pkg.sym"}, &docArgs{pkg: "pkg", sym: "sym", pkgAmbiguous: true}}, 217 {"twoPkgPathSym", []string{"path/pkg.sym"}, &docArgs{pkg: "path/pkg", sym: "sym"}}, 218 {"twoPkgUpperSym", []string{"../pkg.sym"}, &docArgs{pkg: "../pkg", sym: "sym"}}, 219 {"twoPkgExportedSym", []string{"Writer.Write"}, &docArgs{pkg: ".", sym: "Writer", acc: "Write"}}, 220 {"twoPkgCapitalPathSym", []string{"Path/Capitalised.Sym"}, &docArgs{pkg: "Path/Capitalised", sym: "Sym"}}, 221 222 {"threePkgSymAcc", []string{"pkg.sym.acc"}, &docArgs{pkg: "pkg", sym: "sym", acc: "acc"}}, 223 {"threePathPkgSymAcc", []string{"./pkg.sym.acc"}, &docArgs{pkg: "./pkg", sym: "sym", acc: "acc"}}, 224 {"threePathPkgSymAcc2", []string{"../pkg.sym.acc"}, &docArgs{pkg: "../pkg", sym: "sym", acc: "acc"}}, 225 {"threePathPkgSymAcc3", []string{"path/to/pkg.sym.acc"}, &docArgs{pkg: "path/to/pkg", sym: "sym", acc: "acc"}}, 226 {"threePathPkgSymAcc4", []string{"path/../to/pkg.sym.acc"}, &docArgs{pkg: "path/../to/pkg", sym: "sym", acc: "acc"}}, 227 228 // the logic on the split is pretty unambiguously that the first argument 229 // is the path, so we can afford to be less thorough on that regard. 230 {"splitTwo", []string{"io", "Writer"}, &docArgs{pkg: "io", sym: "Writer"}}, 231 {"splitThree", []string{"io", "Writer.Write"}, &docArgs{pkg: "io", sym: "Writer", acc: "Write"}}, 232 233 {"errTooManyDots", []string{"io.Writer.Write.Impossible"}, nil}, 234 {"errTooManyDotsSplit", []string{"io", "Writer.Write.Impossible"}, nil}, 235 {"errTooManyArgs", []string{"io", "Writer", "Write"}, nil}, 236 } 237 for _, tc := range tt { 238 tc := tc 239 t.Run(tc.name, func(t *testing.T) { 240 p, ok := parseArgs(tc.args) 241 if ok { 242 _ = assert.NotNil(t, tc.exp, "parseArgs is successful when should have failed") && 243 assert.Equal(t, *tc.exp, p) 244 } else { 245 assert.Nil(t, tc.exp, "parseArgs is unsuccessful") 246 } 247 }) 248 } 249 }