-
Notifications
You must be signed in to change notification settings - Fork 381
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
notJoon
wants to merge
44
commits into
gnolang:master
Choose a base branch
from
notJoon:coverage
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,416
−1
Open
Changes from 42 commits
Commits
Show all changes
44 commits
Select commit
Hold shift + click to select a range
ccf344c
basic statement coverage
notJoon 78226e2
add control flow graph
notJoon 509d36b
wip: branch
notJoon 9f5e551
fix branch test
notJoon 9a06318
use cfg later. check more conditions
notJoon f9e44ce
revert
notJoon 225aac9
Merge branch 'master' into coverage
notJoon fea3044
coverage type
notJoon b43bd02
something works
notJoon 6cbd004
save
notJoon 7ec4d19
save
notJoon 4295d5f
update logic
notJoon 6aa2cfd
fix: file line handler
notJoon c5854d7
inspect only the package that contains the executed test file
notJoon b8f9e0c
feat: more precise coverage and add colored output command
notJoon c16d806
Merge branch 'master' into coverage
notJoon 6a458ba
json output
notJoon 38e7be4
Record coverage for variable assignments and expressions in op_assign…
notJoon 75431a0
calculate precise executable lines
notJoon 12fbad6
Refactor coverage data handling in Machine
notJoon 0870a7b
remove unnecessat recodings
notJoon ecd76ba
update CLI
notJoon 8a35fc6
fix minor bugs
notJoon 3ec1df7
fix test
notJoon d994325
update docs
notJoon 7efca2c
function coverage save
notJoon 19dd206
update non-executable elements
notJoon a326278
remove func coverage
notJoon 2fde27e
lint
notJoon 9c09457
Merge branch 'master' into coverage
notJoon 1d02c9a
increase coverage
notJoon c77ed87
state handler
notJoon 6ac0426
disable recording when not enabled
notJoon b3a0413
fix failed test
notJoon cc0b24a
fix
notJoon e7c0d52
Merge branch 'master' into coverage
notJoon 0d06a6f
fix
notJoon c59c4a7
fix
notJoon ae248da
Merge branch 'master' into coverage
notJoon 56cc61f
fix
notJoon d2fac1d
Merge branch 'master' into coverage
notJoon 003b798
fix lint error
notJoon abdd0b3
Merge branch 'master' into coverage
notJoon 88128a4
decouple
notJoon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
|
@@ -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", | ||
|
@@ -257,24 +301,33 @@ func gnoTestPkg( | |
stdout = commands.WriteNopCloser(mockOut) | ||
} | ||
|
||
coverageData := gno.NewCoverageData(cfg.rootDir) | ||
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? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() { | ||
|
@@ -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. | ||
|
@@ -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) | ||
} | ||
|
@@ -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) | ||
} | ||
|
@@ -392,6 +450,36 @@ func gnoTestPkg( | |
} | ||
} | ||
|
||
if cfg.coverage { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
|
||
|
@@ -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 { | ||
|
@@ -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 | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: