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): Test Coverage Support #2616

Open
wants to merge 44 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ccf344c
basic statement coverage
notJoon Jul 23, 2024
78226e2
add control flow graph
notJoon Jul 23, 2024
509d36b
wip: branch
notJoon Jul 23, 2024
9f5e551
fix branch test
notJoon Jul 23, 2024
9a06318
use cfg later. check more conditions
notJoon Jul 23, 2024
f9e44ce
revert
notJoon Aug 29, 2024
225aac9
Merge branch 'master' into coverage
notJoon Aug 29, 2024
fea3044
coverage type
notJoon Aug 29, 2024
b43bd02
something works
notJoon Aug 29, 2024
6cbd004
save
notJoon Aug 29, 2024
7ec4d19
save
notJoon Aug 29, 2024
4295d5f
update logic
notJoon Sep 5, 2024
6aa2cfd
fix: file line handler
notJoon Sep 6, 2024
c5854d7
inspect only the package that contains the executed test file
notJoon Sep 6, 2024
b8f9e0c
feat: more precise coverage and add colored output command
notJoon Sep 9, 2024
c16d806
Merge branch 'master' into coverage
notJoon Sep 9, 2024
6a458ba
json output
notJoon Sep 9, 2024
38e7be4
Record coverage for variable assignments and expressions in op_assign…
notJoon Sep 9, 2024
75431a0
calculate precise executable lines
notJoon Sep 10, 2024
12fbad6
Refactor coverage data handling in Machine
notJoon Sep 10, 2024
0870a7b
remove unnecessat recodings
notJoon Sep 11, 2024
ecd76ba
update CLI
notJoon Sep 11, 2024
8a35fc6
fix minor bugs
notJoon Sep 12, 2024
3ec1df7
fix test
notJoon Sep 14, 2024
d994325
update docs
notJoon Sep 18, 2024
7efca2c
function coverage save
notJoon Sep 19, 2024
19dd206
update non-executable elements
notJoon Sep 19, 2024
a326278
remove func coverage
notJoon Sep 20, 2024
2fde27e
lint
notJoon Sep 20, 2024
9c09457
Merge branch 'master' into coverage
notJoon Oct 2, 2024
1d02c9a
increase coverage
notJoon Oct 5, 2024
c77ed87
state handler
notJoon Oct 21, 2024
6ac0426
disable recording when not enabled
notJoon Oct 21, 2024
b3a0413
fix failed test
notJoon Oct 22, 2024
cc0b24a
fix
notJoon Oct 22, 2024
e7c0d52
Merge branch 'master' into coverage
notJoon Oct 25, 2024
0d06a6f
fix
notJoon Oct 25, 2024
c59c4a7
fix
notJoon Oct 25, 2024
ae248da
Merge branch 'master' into coverage
notJoon Oct 28, 2024
56cc61f
fix
notJoon Oct 28, 2024
d2fac1d
Merge branch 'master' into coverage
notJoon Nov 14, 2024
003b798
fix lint error
notJoon Nov 14, 2024
abdd0b3
Merge branch 'master' into coverage
notJoon Nov 27, 2024
88128a4
decouple
notJoon Nov 29, 2024
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
107 changes: 104 additions & 3 deletions gnovm/cmd/gno/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ type testCfg struct {
printRuntimeMetrics bool
printEvents bool
withNativeFallback bool

// coverage flags
coverage bool
viewFile string
showHits bool
output string
htmlOutput string
}

func newTestCmd(io commands.IO) *commands.Command {
Expand Down Expand Up @@ -153,6 +160,43 @@ func (c *testCfg) RegisterFlags(fs *flag.FlagSet) {
"print runtime metrics (gas, memory, cpu cycles)",
)

// test coverage flags

fs.BoolVar(
&c.coverage,
"cover",
false,
"enable coverage analysis",
)

fs.BoolVar(
&c.showHits,
"show-hits",
false,
"show number of times each line was executed",
)

fs.StringVar(
&c.viewFile,
"view",
"",
"view coverage for a specific file",
)

fs.StringVar(
&c.output,
"out",
"",
"save coverage data as JSON to specified file",
)

fs.StringVar(
&c.htmlOutput,
"html",
"",
"output coverage report in HTML format",
)

fs.BoolVar(
&c.printEvents,
"print-events",
Expand Down Expand Up @@ -257,24 +301,33 @@ func gnoTestPkg(
stdout = commands.WriteNopCloser(mockOut)
}

coverageData := gno.NewCoverageData(cfg.rootDir)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick:

coverageData := gno.NewCoverageData(cfg.rootDir).Enable()
if !cfg.coverage {
    coverageData.Disable()
}

if cfg.coverage {
coverageData.Enable()
} else {
coverageData.Disable()
}

// testing with *_test.gno
if len(unittestFiles) > 0 {
// Determine gnoPkgPath by reading gno.mod
var gnoPkgPath string
modfile, err := gnomod.ParseAt(pkgPath)
if err == nil {
// TODO: use pkgPathFromRootDir?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover?

gnoPkgPath = modfile.Module.Mod.Path
coverageData.PkgPath = gnoPkgPath
} else {
gnoPkgPath = pkgPathFromRootDir(pkgPath, rootDir)
if gnoPkgPath == "" {
// unable to read pkgPath from gno.mod, generate a random realm path
io.ErrPrintfln("--- WARNING: unable to read package path from gno.mod or gno root directory; try creating a gno.mod file")
gnoPkgPath = gno.RealmPathPrefix + random.RandStr(8)
}
coverageData.PkgPath = pkgPath
}
memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath)

// tfiles, ifiles := gno.ParseMemPackageTests(memPkg)
var tfiles, ifiles *gno.FileSet

hasError := catchRuntimeError(gnoPkgPath, stderr, func() {
Expand All @@ -298,6 +351,11 @@ func gnoTestPkg(
}

m := tests.TestMachine(testStore, stdout, gnoPkgPath)
if coverageData.IsEnabled() {
m.Coverage = coverageData
m.Coverage.CurrentPackage = memPkg.Path
}

if printRuntimeMetrics {
// from tm2/pkg/sdk/vm/keeper.go
// XXX: make maxAllocTx configurable.
Expand All @@ -306,7 +364,7 @@ func gnoTestPkg(
m.Alloc = gno.NewAllocator(maxAllocTx)
}
m.RunMemPackage(memPkg, true)
err := runTestFiles(m, tfiles, memPkg.Name, verbose, printRuntimeMetrics, printEvents, runFlag, io)
err := runTestFiles(m, tfiles, memPkg.Name, verbose, printRuntimeMetrics, printEvents, runFlag, io, coverageData)
if err != nil {
errs = multierr.Append(errs, err)
}
Expand Down Expand Up @@ -340,7 +398,7 @@ func gnoTestPkg(
memPkg.Path = memPkg.Path + "_test"
m.RunMemPackage(memPkg, true)

err := runTestFiles(m, ifiles, testPkgName, verbose, printRuntimeMetrics, printEvents, runFlag, io)
err := runTestFiles(m, ifiles, testPkgName, verbose, printRuntimeMetrics, printEvents, runFlag, io, coverageData)
if err != nil {
errs = multierr.Append(errs, err)
}
Expand Down Expand Up @@ -392,6 +450,36 @@ func gnoTestPkg(
}
}

if cfg.coverage {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick:

if !coverageData.IsEnabled() {return errs}
// ...

// TODO: consider cache
if cfg.viewFile != "" {
err := coverageData.ViewFiles(cfg.viewFile, cfg.showHits, io)
if err != nil {
return fmt.Errorf("failed to view file coverage: %w", err)
}
return nil // prevent printing out coverage report
}

if cfg.output != "" {
err := coverageData.SaveJSON(cfg.output)
if err != nil {
return fmt.Errorf("failed to save coverage data: %w", err)
}
io.Println("coverage data saved to", cfg.output)
return nil
}

if cfg.htmlOutput != "" {
err := coverageData.SaveHTML(cfg.htmlOutput)
if err != nil {
return fmt.Errorf("failed to save coverage data: %w", err)
}
io.Println("coverage report saved to", cfg.htmlOutput)
return nil
}
coverageData.Report(io)
}

return errs
}

Expand Down Expand Up @@ -433,6 +521,7 @@ func runTestFiles(
printEvents bool,
runFlag string,
io commands.IO,
coverageData *gno.CoverageData,
) (errs error) {
defer func() {
if r := recover(); r != nil {
Expand Down Expand Up @@ -500,6 +589,18 @@ func runTestFiles(
errs = multierr.Append(errs, err)
}

for file, fileCoverage := range m.Coverage.Files {
existingCoverage, exists := coverageData.Files[file]
if !exists {
coverageData.Files[file] = fileCoverage
} else {
for line, count := range fileCoverage.HitLines {
existingCoverage.HitLines[line] += count
}
coverageData.Files[file] = existingCoverage
}
}

if printRuntimeMetrics {
imports := m.Store.NumMemPackages() - numPackagesBefore - 1
// XXX: store changes
Expand Down
Loading
Loading