Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gnovm): add fuzz and fuzz CLI #3446

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e38cf6f
feat(gnovm): add fuzz and fuzz cli
rlaau Jan 6, 2025
6c503bf
feat(gnovm): add fuzz and fuzz cli
rlaau Jan 6, 2025
f45bcf5
feat(gnovm): add fuzz and fuzz cli
rlaau Jan 6, 2025
b4b359d
feat(gnovm): add fuzz and fuzz cli
rlaau Jan 6, 2025
c6d9618
fix(gnovm): add fuzz cli and delete pkg/mod files
rlaau Jan 7, 2025
436478c
fix(gnovm): add fuzz cli and delete pkg/mod files
rlaau Jan 7, 2025
1525bf6
fix(gnovm): add fuzz cli and delete pkg/mod files
rlaau Jan 7, 2025
612a264
fix(gnovm): add fuzz cli and delete pkg/mod files
rlaau Jan 7, 2025
e3b033c
fix(gnovm): add fuzz cli and delete pkg/mod files
rlaau Jan 7, 2025
8428c00
fix(gnovm): add fuzz cli and delete pkg/mod files
rlaau Jan 7, 2025
e0289f2
logger
rlaau Jan 7, 2025
a85f7f0
변수 이름 변경 1. 소문자 변경시키기
rlaau Jan 7, 2025
8fa044e
변수 변경 완료
rlaau Jan 7, 2025
e52a7bc
코드 배치도 끝
rlaau Jan 7, 2025
438a83d
berfoe fix hn
rlaau Jan 8, 2025
252971d
changing annotation
rlaau Jan 8, 2025
f5f796f
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 8, 2025
902afb8
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 8, 2025
5627a50
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 8, 2025
116978c
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 9, 2025
7d4af0f
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 9, 2025
de60c17
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 9, 2025
2d1a744
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 9, 2025
8f82dca
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 9, 2025
330c295
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 9, 2025
2863741
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 10, 2025
aaa064f
fix(gnovm): add fuzz CLI, and modified the annotation and variable names
rlaau Jan 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions gnovm/cmd/gno/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type testCfg struct {
updateGoldenTests bool
printRuntimeMetrics bool
printEvents bool
fuzzName string
fuzzIters int
}

func newTestCmd(io commands.IO) *commands.Command {
Expand Down Expand Up @@ -143,6 +145,18 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) {
false,
"print emitted events",
)
fs.StringVar(
&c.fuzzName,
"fuzz",
"",
"specify fuzz target name (e.g. FuzzXXX) or 'Fuzz' for all fuzz tests",
)
fs.IntVar(
&c.fuzzIters,
"i",
50000,
"number of fuzz iterations to run",
)
}

func execTest(cfg *testCfg, args []string, io commands.IO) error {
Expand Down Expand Up @@ -178,7 +192,7 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error {

// Set up options to run tests.
stdout := goio.Discard
if cfg.verbose {
if cfg.verbose || cfg.fuzzName != "" {
stdout = io.Out()
}
opts := test.NewTestOptions(cfg.rootDir, io.In(), stdout, io.Err())
Expand All @@ -187,7 +201,8 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error {
opts.Verbose = cfg.verbose
opts.Metrics = cfg.printRuntimeMetrics
opts.Events = cfg.printEvents

opts.FuzzName = cfg.fuzzName
opts.FuzzIters = cfg.fuzzIters
buildErrCount := 0
testErrCount := 0
for _, pkg := range subPkgs {
Expand Down Expand Up @@ -216,9 +231,15 @@ func execTest(cfg *testCfg, args []string, io commands.IO) error {
err = test.Test(memPkg, pkg.Dir, opts)
})

if cfg.fuzzName != "" {
if testErrCount > 0 || buildErrCount > 0 {
return fmt.Errorf(" --- %d build errors, %d test errors", buildErrCount, testErrCount)
} else {
return nil
}
}
duration := time.Since(startedAt)
dstr := fmtDuration(duration)

if hasError || err != nil {
if err != nil {
io.ErrPrintfln("%s: test pkg: %v", pkg.Dir, err)
Expand Down
202 changes: 186 additions & 16 deletions gnovm/pkg/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ type TestOptions struct {
Metrics bool
// Uses Error to print the events emitted.
Events bool
// Fuzz target function's name
FuzzName string
// Fuzz iters
FuzzIters int

filetestBuffer bytes.Buffer
outWriter proxyWriter
Expand Down Expand Up @@ -198,26 +202,47 @@ func Test(memPkg *gnovm.MemPackage, fsDir string, opts *TestOptions) error {
// import them from the `pkg_test` tests.
cw := opts.BaseStore.CacheWrap()
gs := opts.TestStore.BeginTransaction(cw, cw, nil)

// Run test files in pkg.
if len(tset.Files) > 0 {
err := opts.runTestFiles(memPkg, tset, cw, gs)
if err != nil {
errs = multierr.Append(errs, err)
if opts.FuzzName == "" {
// Run test files in pkg.
if len(tset.Files) > 0 {
err := opts.runTestFiles(memPkg, tset, cw, gs)
if err != nil {
errs = multierr.Append(errs, err)
}
}
} else {
if len(tset.Files) > 0 {
err := opts.runFuzzFuncInTestFiles(memPkg, tset, cw, gs)
if err != nil {
errs = multierr.Append(errs, err)
}
}
}
if opts.FuzzName == "" {
// Test xxx_test pkg.
if len(itset.Files) > 0 {
itPkg := &gnovm.MemPackage{
Name: memPkg.Name + "_test",
Path: memPkg.Path + "_test",
Files: itfiles,
}

// Test xxx_test pkg.
if len(itset.Files) > 0 {
itPkg := &gnovm.MemPackage{
Name: memPkg.Name + "_test",
Path: memPkg.Path + "_test",
Files: itfiles,
err := opts.runTestFiles(itPkg, itset, cw, gs)
if err != nil {
errs = multierr.Append(errs, err)
}
}

err := opts.runTestFiles(itPkg, itset, cw, gs)
if err != nil {
errs = multierr.Append(errs, err)
} else {
if len(itset.Files) > 0 {
itPkg := &gnovm.MemPackage{
Name: memPkg.Name + "_test",
Path: memPkg.Path + "_test",
Files: itfiles,
}
err := opts.runFuzzFuncInTestFiles(itPkg, itset, cw, gs)
if err != nil {
errs = multierr.Append(errs, err)
}
}
}
}
Expand Down Expand Up @@ -389,6 +414,133 @@ func (opts *TestOptions) runTestFiles(
return errs
}

func (opts *TestOptions) runFuzzFuncInTestFiles(
rlaau marked this conversation as resolved.
Show resolved Hide resolved
memPkg *gnovm.MemPackage,
files *gno.FileSet,
cw storetypes.Store, gs gno.TransactionStore,
) (errs error) {
var m *gno.Machine
defer func() {
if r := recover(); r != nil {
if st := m.ExceptionsStacktrace(); st != "" {
errs = multierr.Append(errors.New(st), errs)
}
errs = multierr.Append(
fmt.Errorf("panic: %v\ngo stacktrace:\n%v\ngno machine: %v\ngno stacktrace:\n%v",
r, string(debug.Stack()), m.String(), m.Stacktrace()),
errs,
)
}
}()

tests := loadFuzzFuncs(memPkg.Name, files)

var alloc *gno.Allocator
if opts.Metrics {
alloc = gno.NewAllocator(math.MaxInt64)
}
// reset store ops, if any - we only need them for some filetests.
opts.TestStore.SetLogStoreOps(false)

// Check if we already have the package - it may have been eagerly
// loaded.
m = Machine(gs, opts.WriterForStore(), memPkg.Path)
m.Alloc = alloc
if opts.TestStore.GetMemPackage(memPkg.Path) == nil {
m.RunMemPackage(memPkg, true)
} else {
m.SetActivePackage(gs.GetPackage(memPkg.Path, false))
}
pv := m.Package

m.RunFiles(files.Files...)

for _, tf := range tests {
// TODO(morgan): we could theoretically use wrapping on the baseStore
// and gno store to achieve per-test isolation. However, that requires
// some deeper changes, as ideally we'd:
// - Run the MemPackage independently (so it can also be run as a
// consequence of an import)
// - Run the test files before this for loop (but persist it to store;
// RunFiles doesn't do that currently)
// - Wrap here.
m = Machine(gs, opts.Output, memPkg.Path)
m.Alloc = alloc.Reset()
m.SetActivePackage(pv)

testingpv := m.Store.GetPackage("testing", false)
testingtv := gno.TypedValue{T: &gno.PackageType{}, V: testingpv}
testingcx := &gno.ConstExpr{TypedValue: testingtv}

eval := m.Eval(gno.Call(
gno.Sel(testingcx, "RunFuzz"), // Call testing.RunFuzz

gno.Str(opts.FuzzName), // fuzz fuzzname
gno.Num(strconv.FormatInt(int64(opts.FuzzIters), 10)),
gno.Nx(strconv.FormatBool(opts.Verbose)), // is verbose?
&gno.CompositeLitExpr{ // Third param, the testing.InternalTest
Type: gno.Sel(testingcx, "InternalFuzz"),
Elts: gno.KeyValueExprs{
{Key: gno.X("Name"), Value: gno.Str(tf.Name)},
{Key: gno.X("F"), Value: gno.Nx(tf.Name)},
},
},
))

if opts.Events {
events := m.Context.(*teststd.TestExecContext).EventLogger.Events()
if events != nil {
res, err := json.Marshal(events)
if err != nil {
panic(err)
}
fmt.Fprintf(opts.Error, "EVENTS: %s\n", string(res))
}
}

ret := eval[0].GetString()
if ret == "" {
err := fmt.Errorf("failed to execute unit test: %q", tf.Name)
errs = multierr.Append(errs, err)
fmt.Fprintf(opts.Error, "--- FAIL: %s [internal gno testing error]", tf.Name)
continue
}

// TODO: replace with amino or send native type?
var rep report
err := json.Unmarshal([]byte(ret), &rep)
if err != nil {
errs = multierr.Append(errs, err)
fmt.Fprintf(opts.Error, "--- FAIL: %s [internal gno testing error]", tf.Name)
continue
}

if rep.Failed {
err := fmt.Errorf("failed: %q", tf.Name)
errs = multierr.Append(errs, err)
}

if opts.Metrics {
// XXX: store changes
// XXX: max mem consumption
allocsVal := "n/a"
if m.Alloc != nil {
maxAllocs, allocs := m.Alloc.Status()
allocsVal = fmt.Sprintf("%s(%.2f%%)",
prettySize(allocs),
float64(allocs)/float64(maxAllocs)*100,
)
}
fmt.Fprintf(opts.Error, "--- runtime: cycle=%s allocs=%s\n",
prettySize(m.Cycles),
allocsVal,
)
}
}

return errs
}

// report is a mirror of Gno's stdlibs/testing.Report.
type report struct {
Failed bool
Expand Down Expand Up @@ -418,6 +570,24 @@ func loadTestFuncs(pkgName string, tfiles *gno.FileSet) (rt []testFunc) {
return
}

func loadFuzzFuncs(pkgName string, tfiles *gno.FileSet) (rt []testFunc) {
for _, tf := range tfiles.Files {
for _, d := range tf.Decls {
if fd, ok := d.(*gno.FuncDecl); ok {
fname := string(fd.Name)
if strings.HasPrefix(fname, "Fuzz") {
tf := testFunc{
Package: pkgName,
Name: fname,
}
rt = append(rt, tf)
}
}
}
}
return
}

// parseMemPackageTests parses test files (skipping filetests) in the memPkg.
func parseMemPackageTests(store gno.Store, memPkg *gnovm.MemPackage) (tset, itset *gno.FileSet, itfiles, ftfiles []*gnovm.MemFile) {
tset = &gno.FileSet{}
Expand Down
Loading
Loading