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

Solidity code coverage #692

Merged
merged 10 commits into from
Aug 10, 2018
Merged

Solidity code coverage #692

merged 10 commits into from
Aug 10, 2018

Conversation

andremedeiros
Copy link
Collaborator

Overview

TL;DR
Added an output of a code coverage report for Solidity

Details

This PR brings in code coverage for Solidity in Embark. It does so by registering a new module ("coverage") that will listen to compiler, and block events, and request traces from Geth with the executed paths. For this effect, it outputs <dapp_root>/.embark/coverage.json after a dApp's tests run.

One thing to note is that, due to the lack of information from the transaction and trace request, we have to do some "matching" of the bytecode to the actual contract/method being executed. This is a bit of a naive approach for now, so I'll keep an eye out on a better approach to reach this goal.

PR with embark coverage command will follow.

@andremedeiros andremedeiros force-pushed the features/code-coverage branch from 39291e5 to 22d809b Compare August 9, 2018 20:11
Copy link
Contributor

@alaibe alaibe left a comment

Choose a reason for hiding this comment

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

💯 very nice, just a few comments about code style:

  • Usage of forEach instead of reduce (there is others than just my 2 comments)
  • Usage of var instead of let

}

// +1 here factors in newline characters.
this.lineOffsets[line] = this.lineOffsets[line-1] + this.lineLengths[line-1] + 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

could it be: this.lineOffsets[line] = 2 * this.lineLengths[line-1] + 1;

Copy link
Contributor

Choose a reason for hiding this comment

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

good eyes, just realized that!

this.lineCount = this.lineLengths.length;

this.lineOffsets = [];
this.lineLengths.forEach((length, line) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you could use reduce here

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Reduce, as far as I know, only returns one value, right?

@@ -90,6 +107,10 @@ class Solidity {
}
}
}

//self.plugins.emitAndRunActionsForEvent('contracts:compiled:solc', output);
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for this comment right?

compileSolc(input) {
var sources = {};

Object.keys(input.sources).forEach((path) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

You could also use reduce here I believe

test/coverage.js Outdated

var trace = JSON.parse(loadFixture('geth-debugtrace-output-h-5.json'));
var coverage = cs.generateCodeCoverage(trace);
// dumpToFile(coverage, '/tmp/coverage.json');
Copy link
Contributor

Choose a reason for hiding this comment

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

What about this comment?

@andremedeiros
Copy link
Collaborator Author

Thanks for the review @alaibe!

I think reduce is designed to compute a value based on an array, so it might not be what we need in these cases.


this.lineOffsets = [];
this.lineLengths.forEach((length, line) => {
if(line == 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Friendly reminder that we try to always use ===. I added it to eslint... but only for area-51 🤦‍♂️

break;
}

for(var i = locations.start.line; i < this.lineCount; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can't you merge these two loops together and add mre ifs to detect the end and start?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I did this originally but it was hard to make sense of what was going on. You'll notice that I offset the second loop so that we're not doing any unnecessary iterations anyway, so this is as optimized as it gets.

// or surpass the offset for last character.
if(!locations.end) {
var lastLine = this.lineCount - 1;
locations.end = {line: lastLine, column: this.lineLengths[lastLine]};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can just do this.lineLengths[this.lineCount - 1]
The lastLine variable is a bit redundant.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We use it twice so I figured I'd declare a variable above.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Right, sorry, my bad.

locations.end = {line: lastLine, column: this.lineLengths[lastLine]};
}

// Istanbul likes lines to be 1-indexed, so we'll increment here before returning.
Copy link
Collaborator

Choose a reason for hiding this comment

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

That's weird, do you know why?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, as per the coverage.json spec:

Location objects are a {start: {line, column}, end: {line, column}} object that describes the start and end of a piece of code. Note that line is 1-based, but column is 0-based.

}
}

/*eslint complexity: ["error", 27]*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Holy, that's a big function. Is there a way you could split it?

parseSolcOutput(output) {
for(var file in output.contracts) {
var basename = path.basename(file);
var contractSource = this.files[basename];
Copy link
Collaborator

Choose a reason for hiding this comment

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

basename is redundant. Can just do this.files[path.basename(file)];


for(var file in this.files) {
var contractSource = this.files[file];
coverageReport[file] = contractSource.generateCodeCoverage(trace);
Copy link
Collaborator

Choose a reason for hiding this comment

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

No need for contractSource. Can just do this.files[file].generateCodeCoverage(trace);


process.on('exit', (_code) => {
const coverageReportPath = fs.dappPath('.embark', 'coverage.json');
fs.writeFileSync(coverageReportPath, JSON.stringify(self.coverageReport));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wouldn't this create a race condition where your file write might not finish before the exit?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not if it's sync. It'll wait for that to finish.


subtract(sourceMap) {
var length = sourceMap.offset - this.offset;
return new SourceMap(this.offset, length);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Redundant variable length

@@ -120,6 +120,7 @@ class Test {
ipcRole: 'client'
});
this.events.request('deploy:setGasLimit', 6000000);
this.engine.startService("codeCoverage");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Picky comment. Can you move this one line up. That way all service starts are in one block.

@jrainville
Copy link
Collaborator

Really nice stuff. I feel like we could/should create an individual repository for the solidity code coverage part and publish it. I feel like it could be a really good standalone tool.
Though, I think there's some hooks that are embark only? Anyway, I think we should consider it. Then, in our module, we would only need to require the coverage tool.

@andremedeiros andremedeiros merged commit f860f36 into develop Aug 10, 2018
@andremedeiros andremedeiros deleted the features/code-coverage branch August 10, 2018 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants