Skip to content

Commit 4c2c9e5

Browse files
authored
Merge pull request #1 from sc-forks/truffle3
Update to Truffle3, refactor CLI, add CLI unit tests, fix misc bugs
2 parents 6442e73 + 48e77b2 commit 4c2c9e5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1136
-183
lines changed

.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test/cli/truffle-crash.js

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@ allFiredEvents
22
coverage.json
33
coverage/
44
node_modules/
5+
.changelog
6+
.DS_Store

README.md

Lines changed: 85 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,118 @@
1-
#SolCover
1+
# SolCover
22

33
![CircleCI Status](https://circleci.com/gh/JoinColony/solcover.svg?style=shield&circle-token=53d5360d290ef593c7bdce505b86ae8b9414e684)
44
[![codecov](https://codecov.io/gh/JoinColony/solcover/branch/master/graph/badge.svg)](https://codecov.io/gh/JoinColony/solcover)
55

6-
7-
###Code coverage for Solidity testing
6+
### Code coverage for Solidity testing
87
![coverage example](https://cdn-images-1.medium.com/max/800/1*uum8t-31bUaa6dTRVVhj6w.png)
98

10-
For more details about what this is, how it work and potential limitations, see
9+
For more details about what this is, how it works and potential limitations, see
1110
[the accompanying article](https://blog.colony.io/code-coverage-for-solidity-eecfa88668c2).
1211

13-
###Installation and preparation
12+
### Install
13+
```
14+
$ npm install --save-dev https://github.com/JoinColony/solcover.git#truffle3
15+
```
1416

15-
From your truffle directory, clone this repo:
17+
### Run
1618
```
17-
git clone http://github.com/JoinColony/solcover.git
18-
cd solcover
19-
npm install
19+
$ ./node_modules/solcover/exec.js
2020
```
2121

22-
Until [Truffle allows the `--network` flag for the `test` command](https://github.com/ConsenSys/truffle/issues/239), in `truffle.js` you have to set a large gas amount for deployment. While this is set, uninstrumented tests likely won't run correctly, so this should only be set when running the coverage tests. An appropriately modified `truffle.js` might look like
22+
Tests run signficantly slower while coverage is being generated. A 1 to 2 minute delay
23+
between the end of Truffle compilation and the beginning of test execution is possible if your
24+
test suite is large. Large solidity files can also take a while to instrument.
2325

24-
```
26+
### Configuration
27+
28+
By default, Solcover generates a stub `truffle.js` that accomodates its special gas needs and
29+
connects to a modified version of testrpc on port 8555. If your tests will run on the development network
30+
using a standard `truffle.js` and a testrpc instance with no special options, you shouldn't have to
31+
do any configuration. If your tests depend on logic added to `truffle.js` - for example:
32+
[zeppelin-solidity](https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/truffle.js)
33+
uses the file to expose a babel polyfill that its suite requires - you can override the
34+
default behavior by declaring a coverage network in `truffle.js`. Solcover will use your 'truffle.js'
35+
instead of a dynamically generated one.
36+
37+
**Example coverage network config**
38+
```javascript
2539
module.exports = {
26-
rpc: {
27-
host: 'localhost',
28-
gasPrice: 20e9,
29-
gas: 0xfffffff,
40+
networks: {
41+
development: {
42+
host: "localhost",
43+
port: 8545,
44+
network_id: "*" // Match any network id
45+
},
46+
coverage: {
47+
host: "localhost",
48+
network_id: "*",
49+
port: 8555, // <-- Use port 8555
50+
gas: 0xfffffffffff, // <-- Use this high gas value
51+
gasPrice: 0x01 // <-- Use this low gas price
52+
}
3053
}
31-
};
54+
};
3255
```
33-
In the future, hopefully just adding the 'coverage' network to `truffle.js` will be enough. This will look like
3456

57+
You can also create a `.solcover.js` config file in the root directory of your project and specify
58+
some additional options:
59+
+ **port**: <Number> The port you want Solcover to run testrpc on / have truffle connect to.
60+
+ **testrpcOptions**: <String> A string of options to be appended to a command line invocation
61+
of testrpc.
62+
+ Example: `--account="0x89a...b1f',10000" --port 8777`".
63+
+ Note: you should specify the port in your `testrpcOptions` string AND as a `port` option.
64+
+ **testCommand**: <String> By default Solcover runs `truffle test` or `truffle test --network coverage`.
65+
This option lets you run tests some other way: ex: `mocha --timeout 5000`. You
66+
will probably also need to make sure the web3 provider for your tests explicitly connects to the port Solcover's
67+
testrpc is set to run on, e.g:
68+
+ `var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8555"))`
69+
70+
**Example .solcover.js config file**
3571
```
3672
module.exports = {
37-
rpc: {
38-
host: 'localhost',
39-
gasPrice: 20e9,
40-
},
41-
networks:{
42-
"coverage":{
43-
gas: 0xfffffff,
44-
}
45-
}
46-
}
73+
port: 6545,
74+
testrpcOptions: '-p 6545 -u 0x54fd80d6ae7584d8e9a19fe1df43f04e5282cc43',
75+
testCommand: 'mocha --timeout 5000'
76+
};
4777
```
48-
and will not interfere with normal `truffle test` - or other commands - being run during development.
49-
50-
Note that if you have hardcoded gas costs into your tests, some of them may fail when using SolCover. This is because the instrumentation process increases the gas costs for using the contracts, due to the extra events. If this is the case, then the coverage may be incomplete. To avoid this, using `estimateGas` to estimate your gas costs should be more resilient in most cases.
51-
52-
###Execution
5378

54-
Firstly, make sure that your contracts in your truffle directory are saved elsewhere too - this script moves them and modifies them to do the instrumentation and allow `truffle` to run the tests with the instrumented contracts. It returns them after the tests are complete, but if something goes wrong, then `originalContracts` in the truffle directory should contain the unmodified contracts.
79+
### Known Issues
5580

56-
SolCover runs its own (modified) `testrpc` to get the coverage data, so make sure that you've not left a previous instance running on port 8545, otherwise the coverage reported will be.... sparse...
81+
**Hardcoded gas costs**: If you have hardcoded gas costs into your tests some of them may fail when using SolCover.
82+
This is because the instrumentation process increases the gas costs for using the contracts, due to
83+
the extra events. If this is the case, then the coverage may be incomplete. To avoid this, using
84+
`estimateGas` to estimate your gas costs should be more resilient in most cases.
5785

58-
From inside the SolCover directory, run
86+
**Events testing**: Because Solcover injects events into your contracts to log which lines your tests reach,
87+
any tests that ask how many events are fired or where the event sits in the logs array
88+
will probably error while coverage is being generated.
5989

60-
```node ./runCoveredTests.js```
90+
**Using `require` in `migrations.js` files**: Truffle overloads Node's `require` function but
91+
implements a simplified search algorithm for node_modules packages
92+
([see issue #383 at Truffle](https://github.com/trufflesuite/truffle/issues/383)).
93+
Because Solcover copies an instrumented version of your project into a temporary folder, `require`
94+
statements handled by Truffle internally won't resolve correctly.
6195

62-
Upon completion of the tests, open the `./coverage/lcov-report/index.html` file to browse the HTML coverage report.
96+
**Coveralls / CodeCov**: These CI services take the Istanbul reports generated by Solcover and display
97+
line coverage. Istanbul's own html report publishes significantly more detail and can show whether
98+
your tests actually reach all the conditional branches in your code. It can be found inside the
99+
`coverage` folder at `index.html` after you run the tool.
63100

64-
###A few, uh, provisos, a, a couple of quid pro quos...
65-
It is very likely that there are valid Solidity statements that this tool won't instrument correctly, as it's only been developed against a small number of contracts. If (and when) you find such cases, please raise an issue.
101+
### Examples
66102

103+
+ **Metacoin**: The default truffle project
104+
+ [HTML reports](https://sc-forks.github.io/metacoin/)
105+
+ [Metacoin with Solcover installed](https://github.com/sc-forks/metacoin) (simple, without configuration)
106+
+ **zeppelin-solidity** at commit 453a19825013a586751b87c67bebd551a252fb50
107+
+ [HTML reports]( https://sc-forks.github.io/zeppelin-solidity/)
108+
+ [Zeppelin with Solcover installed](https://github.com/sc-forks/zeppelin-solidity) (declares own coverage network in truffle.js)
109+
+ **numeraire** at commit 5ac3fa432c6b4192468c95a66e52ca086c804c95
110+
+ [HTML reports](https://sc-forks.github.io/contract/)
111+
+ [Numeraire with Solcover installed](https://github.com/sc-forks/contract) (uses .solcover.js)
67112

68-
###TODO
113+
### TODO
69114

70-
- [ ] **TESTS**
71115
- [ ] Turn into a true command line tool, rather than just a hacked-together script
72116
- [ ] Release on NPM
73-
- [ ] Do not modify the `../contract/` directory at all during operation (might need changes to truffle)
74117
- [ ] Support for arbitrary testing commands
75118
- [ ] [You tell me](http://github.com/JoinColony/solcover/issues)

coverageMap.js

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,7 @@
55
*/
66
const SolidityCoder = require('web3/lib/solidity/coder.js');
77
const path = require('path');
8-
9-
const lineTopic = 'b8995a65f405d9756b41a334f38d8ff0c93c4934e170d3c1429c3e7ca101014d';
10-
const functionTopic = 'd4ce765fd23c5cc3660249353d61ecd18ca60549dd62cb9ca350a4244de7b87f';
11-
const branchTopic = 'd4cf56ed5ba572684f02f889f12ac42d9583c8e3097802060e949bfbb3c1bff5';
12-
const statementTopic = 'b51abbff580b3a34bbc725f2dc6f736e9d4b45a41293fd0084ad865a31fde0c8';
8+
const keccak = require('keccakjs');
139

1410
/**
1511
* Converts solcover event data into an object that can be
@@ -20,6 +16,10 @@ module.exports = class CoverageMap {
2016

2117
constructor() {
2218
this.coverage = {};
19+
this.lineTopics = [];
20+
this.functionTopics = [];
21+
this.branchTopics = [];
22+
this.statementTopics = [];
2323
}
2424

2525
/**
@@ -29,6 +29,7 @@ module.exports = class CoverageMap {
2929
* @param {String} canonicalContractPath target file location
3030
* @return {Object} coverage map with all values set to zero
3131
*/
32+
3233
addContract(info, canonicalContractPath) {
3334
this.coverage[canonicalContractPath] = {
3435
l: {},
@@ -56,6 +57,17 @@ module.exports = class CoverageMap {
5657
for (let x = 1; x <= Object.keys(info.statementMap).length; x++) {
5758
this.coverage[canonicalContractPath].s[x] = 0;
5859
}
60+
61+
const keccakhex = (x => {
62+
const hash = new keccak(256); // eslint-disable-line new-cap
63+
hash.update(x);
64+
return hash.digest('hex');
65+
});
66+
67+
this.lineTopics.push(keccakhex('__Coverage' + info.contractName + '(string,uint256)'));
68+
this.functionTopics.push(keccakhex('__FunctionCoverage' + info.contractName + '(string,uint256)'));
69+
this.branchTopics.push(keccakhex('__BranchCoverage' + info.contractName + '(string,uint256,uint256)'));
70+
this.statementTopics.push(keccakhex('__StatementCoverage' + info.contractName + '(string,uint256)'));
5971
}
6072

6173
/**
@@ -68,19 +80,20 @@ module.exports = class CoverageMap {
6880
generate(events, pathPrefix) {
6981
for (let idx = 0; idx < events.length; idx++) {
7082
const event = JSON.parse(events[idx]);
71-
if (event.topics.indexOf(lineTopic) >= 0) {
83+
84+
if (event.topics.filter(t => this.lineTopics.indexOf(t) >= 0).length > 0) {
7285
const data = SolidityCoder.decodeParams(['string', 'uint256'], event.data.replace('0x', ''));
7386
const canonicalContractPath = data[0];
7487
this.coverage[canonicalContractPath].l[data[1].toNumber()] += 1;
75-
} else if (event.topics.indexOf(functionTopic) >= 0) {
88+
} else if (event.topics.filter(t => this.functionTopics.indexOf(t) >= 0).length > 0) {
7689
const data = SolidityCoder.decodeParams(['string', 'uint256'], event.data.replace('0x', ''));
7790
const canonicalContractPath = data[0];
7891
this.coverage[canonicalContractPath].f[data[1].toNumber()] += 1;
79-
} else if (event.topics.indexOf(branchTopic) >= 0) {
92+
} else if (event.topics.filter(t => this.branchTopics.indexOf(t) >= 0).length > 0) {
8093
const data = SolidityCoder.decodeParams(['string', 'uint256', 'uint256'], event.data.replace('0x', ''));
8194
const canonicalContractPath = data[0];
8295
this.coverage[canonicalContractPath].b[data[1].toNumber()][data[2].toNumber()] += 1;
83-
} else if (event.topics.indexOf(statementTopic) >= 0) {
96+
} else if (event.topics.filter(t => this.statementTopics.indexOf(t) >= 0).length > 0) {
8497
const data = SolidityCoder.decodeParams(['string', 'uint256'], event.data.replace('0x', ''));
8598
const canonicalContractPath = data[0];
8699
this.coverage[canonicalContractPath].s[data[1].toNumber()] += 1;

0 commit comments

Comments
 (0)