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

Conversation

notJoon
Copy link
Member

@notJoon notJoon commented Jul 23, 2024

Description

closes #1121

Screenshot 2024-09-11 at 6 40 38 PM

Support testing coverage functionality into previous testing framework. This feature tracks and reports which parts of the code were executed during test runs.

Key Changes

  1. Added coverage data structures
    • Implemented CoverageData and FileCoverage structs
    • Per-file executed line tracker
  2. Coverage collection machanism
    • Collect current location information after PopOp in Machine.Run() method
      • Inserted recordCoverage function for each statement to obtaining accurate coverage tracking result
    • Record executed lines throught AddHit method
  3. Coverage reporting
    • Calculate and output coverage percentage per file
      • Excludes non-executable lines (e.g., comments) from the calculation
    • Terminal visualization using ANSI color codes
  4. Connect with external tools
    • Convert coverage data to JSON format

CLI Options

CLI Options

Flag Desc Example
-cover Activate coverage analysis gno test <path> -cover
-view=<pattern> View coverage for files matching the pattern gno test <path> -cover -view=demo/foo
-show-hits Show number of times each line was executed gno test <path> -cover -view=demo/foo -show-hits
-html=<file> Generate HTML coverage report gno test <path> -cover -html=coverage.html

All coverage-related flags must be preceded by the -cover flag to be active.

Notes:

  • The report displays a list of files with their coverage percentages, color-coded based on coverage level.
  • The -view option supports partial file path patterns and can match multiple files.
  • When using -view, the output is color-coded: green for executed lines, yellow for executable but not executed lines, and white for non-executable lines.
  • The -show-hits option adds an orange hit count next to each executed line when used with -view.

The currently added CLI was added for testing during development. I plan to modify it later to provide the same functionality as go tool.

@github-actions github-actions bot added the 📦 🤖 gnovm Issues or PRs gnovm related label Jul 23, 2024
Copy link

codecov bot commented Jul 23, 2024

@notJoon
Copy link
Member Author

notJoon commented Sep 12, 2024

For simplicity for reviewing I'll change this PR as ready for review.

@notJoon notJoon marked this pull request as ready for review September 12, 2024 10:16
@notJoon
Copy link
Member Author

notJoon commented Oct 22, 2024

@zivkovicmilos @thehowl

Now all CI passes successfully. Previously, I had set it up to always generate and store coverage data whenever tests were run. But this caused timeouts in the strings and bytes packages due to increase overhead (#2935). So I modified it to collect data only when the coverage flag (-flag) is activated.

Currently, since it only perfoems basic operations, there are cases where lines that should be executed are not recognized during the execution. However, this case requires branch coverage and other static analysis features to be implemented. So, I plan to make these in a separate PR to prevent the current PR size from becoming too large.

The planned tasks are as follows:

  1. Run static analysis based on the collected line information to calculate more accurate coverage
  2. Provide the same commands and functionalities as go tool cover (ref: gno test -cover ... #1121)

@notJoon notJoon added the review/triage-pending PRs opened by external contributors that are waiting for the 1st review label Oct 22, 2024
@jefft0
Copy link
Contributor

jefft0 commented Oct 25, 2024

Hello @notJoon . It says "This branch has conflicts that must be resolved". Are there merge conflicts with master?

@notJoon
Copy link
Member Author

notJoon commented Oct 25, 2024

Hello @notJoon . It says "This branch has conflicts that must be resolved". Are there merge conflicts with master?

@jefft0 now fixed

@jefft0
Copy link
Contributor

jefft0 commented Oct 25, 2024

The changes look reasonable to me, but we need a core dev to decide if they are needed. Do you need anything else from me for the "triage" review?

@notJoon
Copy link
Member Author

notJoon commented Oct 28, 2024

The changes look reasonable to me, but we need a core dev to decide if they are needed. Do you need anything else from me for the "triage" review?

@jefft0

I think checking just the basic things should be sufficient. I've added flags as per convention.

@jefft0
Copy link
Contributor

jefft0 commented Nov 14, 2024

@notJoon , can you resolve the conflicts with the master branch?

@notJoon
Copy link
Member Author

notJoon commented Nov 14, 2024

@jefft0 resolved. thanks 👍

@jefft0
Copy link
Contributor

jefft0 commented Nov 15, 2024

@notJoon, the failing CI checks are because of a misconfiguration of codecov. This has been fixed. Can you please push an empty commit? This will make the CI checks run again and hopefully pass.

@Kouteki Kouteki requested review from zivkovicmilos and petar-dambovaliev and removed request for jaekwon, moul, deelawn and piux2 November 18, 2024 06:49
Copy link
Member

@zivkovicmilos zivkovicmilos left a comment

Choose a reason for hiding this comment

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

I like the feature, but I think we need to change some things about the implementation 🙏

I'm primarily concerned that this sort of functionality and code doesn't belong in pkg/gnolang, but warrants its own separate package. Please check the comments, and we can take it from there

cc @thehowl

@@ -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()
}

// 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?

@@ -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}
// ...

@@ -433,6 +433,7 @@ EXEC_SWITCH:
}
switch cs := s.(type) {
case *AssignStmt:
m.recordCoverage(cs)
Copy link
Member

Choose a reason for hiding this comment

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

What is the overhead of something like this, for VM ops?

Copy link
Member Author

Choose a reason for hiding this comment

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

This should have condition check also. I forgot

@@ -81,6 +82,9 @@ type Machine struct {
// it is executed. It is reset to zero after the defer functions in the current
// scope have finished executing.
DeferPanicScope uint

// Test Coverage
Coverage *CoverageData
Copy link
Member

Choose a reason for hiding this comment

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

Does the VM usually keep track of this sort of data? cc @mvertes

Copy link
Member Author

Choose a reason for hiding this comment

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

I’ve added a this field to update executed lines directly on the Machine, but it might be better to separate this functionality if possible.

Maybe I can fetch the executed lines separately using a getter, and then handle them within the coverage package. This would eliminate the dependency and overhead on the Machine as well.

strings.HasSuffix(pkgPath, "_filetest.gno")
}

type jsonCoverageMap map[string]JSONFileCoverage
Copy link
Member

Choose a reason for hiding this comment

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

You should move these types to the ToJSON method, and not have them be package-level types

return json.MarshalIndent(jsonCov, "", " ")
}

func (c *CoverageData) SaveJSON(fileName string) error {
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't need to be a method on CoverageData

return os.WriteFile(fileName, data, 0o644)
}

// TODO: temporary HTML output. need to match with go coverage tool
Copy link
Member

Choose a reason for hiding this comment

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

Leftover?

}

// TODO: temporary HTML output. need to match with go coverage tool
func (c *CoverageData) SaveHTML(outputFileName string) error {
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't need to be a method on CoverageData

return t.Execute(file, data)
}

func (m *Machine) addFileToCodeCoverage(file string, totalLines int) {
Copy link
Member

Choose a reason for hiding this comment

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

The Machine doesn't need this method, function can simply take in CoverageData

@Kouteki Kouteki removed the review/triage-pending PRs opened by external contributors that are waiting for the 1st review label Nov 20, 2024
@Gno2D2
Copy link

Gno2D2 commented Nov 29, 2024

Merge Requirements

The following requirements must be fulfilled before a pull request can be merged.
Some requirement checks are automated and can be verified by the CI, while others need manual verification by a staff member.

These requirements are defined in this configuration file.

Automated Checks

🟢 Maintainers must be able to edit this pull request
🔴 The pull request head branch must be up-to-date with its base

Details
Maintainers must be able to edit this pull request

If

🟢 Condition met
└── 🟢 On every pull request

Then

🟢 Requirement satisfied
└── 🟢 Maintainer can modify this pull request

The pull request head branch must be up-to-date with its base

If

🟢 Condition met
└── 🟢 On every pull request

Then

🔴 Requirement not satisfied
└── 🔴 Head branch (notJoon:coverage) is up to date with base (master): behind by 8 / ahead by 44

Manual Checks

  • The pull request description provides enough details
Details
The pull request description provides enough details

If

🟢 Condition met
└── 🟢 Not (🔴 Pull request author is a member of the team: core-contributors)

Can be checked by

  • team core-contributors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📦 🤖 gnovm Issues or PRs gnovm related
Projects
Status: In Progress
Status: In Review
Development

Successfully merging this pull request may close these issues.

gno test -cover ...
6 participants