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  }