Move to staging branch for 0.8.0 restructure (#502)

Additional Coverage Measurements
pull/716/head
cgewecke 3 years ago committed by GitHub
commit 0bf470ac0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 133
      BUIDLER_README.md
  2. 12
      README.md
  3. 24
      docs/advanced.md
  4. 421
      docs/matrix.md
  5. 99
      lib/abi.js
  6. 73
      lib/api.js
  7. 8
      lib/collector.js
  8. 9
      lib/coverage.js
  9. 240
      lib/injector.js
  10. 24
      lib/instrumenter.js
  11. 79
      lib/parse.js
  12. 254
      lib/registrar.js
  13. 185
      lib/ternary/conditional.js
  14. 121
      lib/ternary/ternary.js
  15. 14
      lib/validator.js
  16. 15
      package.json
  17. 148
      plugins/buidler.plugin.js
  18. 39
      plugins/hardhat.plugin.js
  19. 7
      plugins/nomiclabs.plugin.js
  20. 145
      plugins/resources/matrix.js
  21. 8
      plugins/resources/nomiclabs.ui.js
  22. 52
      plugins/resources/nomiclabs.utils.js
  23. 1
      plugins/resources/truffle.ui.js
  24. 70
      plugins/resources/truffle.utils.js
  25. 30
      plugins/truffle.plugin.js
  26. 10
      scripts/run-metacoin.sh
  27. 32
      scripts/run-nomiclabs.sh
  28. 2
      test/integration/generic/assets/SimpleError.sol
  29. 2
      test/integration/generic/contracts/Migrations.sol
  30. 3
      test/integration/projects/contract-subfolders/contracts/A/ContractA2.sol
  31. 2
      test/integration/projects/contract-subfolders/contracts/B/ContractB2.sol
  32. 2
      test/integration/projects/contract-subfolders/hardhat.config.js
  33. 2
      test/integration/projects/ganache-solcoverjs/contracts/ContractA.sol
  34. 2
      test/integration/projects/ganache-solcoverjs/contracts/ContractB.sol
  35. 2
      test/integration/projects/ganache-solcoverjs/contracts/ContractC.sol
  36. 2
      test/integration/projects/ganache-solcoverjs/hardhat.config.js
  37. 2
      test/integration/projects/hardhat-compile-config/contracts/ContractA1.sol
  38. 2
      test/integration/projects/hardhat-gas-reporter/contracts/ContractA.sol
  39. 2
      test/integration/projects/hardhat-gas-reporter/hardhat.config.js
  40. 2
      test/integration/projects/import-paths/assets/RelativePathImport.sol
  41. 23
      test/integration/projects/import-paths/contracts/Migrations.sol
  42. 2
      test/integration/projects/import-paths/contracts/OnlyUsesImports.sol
  43. 2
      test/integration/projects/import-paths/contracts/UsesImports.sol
  44. 2
      test/integration/projects/import-paths/hardhat.config.js
  45. 5
      test/integration/projects/import-paths/migrations/1_initial_migration.js
  46. 2
      test/integration/projects/import-paths/node_modules/package/AnotherImport.sol
  47. 2
      test/integration/projects/import-paths/node_modules/package/NodeModulesImport.sol
  48. 2
      test/integration/projects/libraries/contracts/CLibrary.sol
  49. 2
      test/integration/projects/libraries/contracts/Migrations.sol
  50. 6
      test/integration/projects/libraries/contracts/PureView.sol
  51. 6
      test/integration/projects/libraries/contracts/UsesPure.sol
  52. 2
      test/integration/projects/libraries/contracts/_Interface.sol
  53. 2
      test/integration/projects/libraries/hardhat.config.js
  54. 17
      test/integration/projects/matrix/.solcover.js
  55. 21
      test/integration/projects/matrix/contracts/MatrixA.sol
  56. 17
      test/integration/projects/matrix/contracts/MatrixB.sol
  57. 99
      test/integration/projects/matrix/expectedMochaOutput.json
  58. 48
      test/integration/projects/matrix/expectedTestMatrixHardhat.json
  59. 46
      test/integration/projects/matrix/expectedTestMatrixTruffle.json
  60. 9
      test/integration/projects/matrix/hardhat.config.js
  61. 15
      test/integration/projects/matrix/test/matrix_a.js
  62. 30
      test/integration/projects/matrix/test/matrix_a_b.js
  63. 10
      test/integration/projects/matrix/truffle-config.js
  64. 8
      test/integration/projects/modifiers/.solcover.js
  65. 45
      test/integration/projects/modifiers/contracts/ModifiersA.sol
  66. 19
      test/integration/projects/modifiers/contracts/ModifiersB.sol
  67. 43
      test/integration/projects/modifiers/contracts/ModifiersC.sol
  68. 9
      test/integration/projects/modifiers/hardhat.config.js
  69. 40
      test/integration/projects/modifiers/test/modifiers.js
  70. 9
      test/integration/projects/modifiers/truffle-config.js
  71. 2
      test/integration/projects/multiple-migrations/contracts/ContractA.sol
  72. 2
      test/integration/projects/multiple-migrations/contracts/ContractB.sol
  73. 2
      test/integration/projects/multiple-migrations/contracts/ContractC.sol
  74. 2
      test/integration/projects/multiple-migrations/contracts/Migrations.sol
  75. 2
      test/integration/projects/multiple-suites/contracts/ContractA.sol
  76. 2
      test/integration/projects/multiple-suites/contracts/ContractB.sol
  77. 2
      test/integration/projects/multiple-suites/contracts/ContractC.sol
  78. 23
      test/integration/projects/multiple-suites/contracts/Migrations.sol
  79. 2
      test/integration/projects/multiple-suites/hardhat.config.js
  80. 4
      test/integration/projects/multiple-suites/truffle-config.js
  81. 2
      test/integration/projects/no-sources/hardhat.config.js
  82. 2
      test/integration/projects/skipping/contracts/ContractA.sol
  83. 23
      test/integration/projects/skipping/contracts/Migrations.sol
  84. 2
      test/integration/projects/skipping/contracts/skipped-folder/ContractB.sol
  85. 2
      test/integration/projects/skipping/hardhat.config.js
  86. 5
      test/integration/projects/skipping/migrations/1_initial_migration.js
  87. 8
      test/integration/projects/ternary-and-logical-or/.solcover.js
  88. 46
      test/integration/projects/ternary-and-logical-or/contracts/Contract_OR.sol
  89. 54
      test/integration/projects/ternary-and-logical-or/contracts/Contract_ternary.sol
  90. 9
      test/integration/projects/ternary-and-logical-or/hardhat.config.js
  91. 37
      test/integration/projects/ternary-and-logical-or/test/test_or.js
  92. 16
      test/integration/projects/ternary-and-logical-or/test/test_ternary.js
  93. 7
      test/integration/projects/ternary-and-logical-or/truffle-config.js
  94. 2
      test/integration/projects/test-files/contracts/ContractA.sol
  95. 2
      test/integration/projects/test-files/contracts/ContractB.sol
  96. 2
      test/integration/projects/test-files/contracts/ContractC.sol
  97. 2
      test/integration/projects/test-files/contracts/Migrations.sol
  98. 2
      test/integration/projects/test-files/hardhat.config.js
  99. 2
      test/integration/projects/tests-folder/contracts/ContractA.sol
  100. 2
      test/integration/projects/tests-folder/contracts/ContractB.sol
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,133 +0,0 @@
[![Gitter chat](https://badges.gitter.im/sc-forks/solidity-coverage.svg)][18]
![npm (tag)](https://img.shields.io/npm/v/solidity-coverage/latest)
[![CircleCI](https://circleci.com/gh/sc-forks/solidity-coverage.svg?style=svg)][20]
[![codecov](https://codecov.io/gh/sc-forks/solidity-coverage/branch/beta/graph/badge.svg)][21]
[![buidler](https://buidler.dev/buidler-plugin-badge.svg?1)][26]
# solidity-coverage
Solidity code coverage plugin for [buidler](http://getbuidler.com).
## What
![coverage example][22]
+ For more details about how it works and potential limitations, see [the accompanying article][16].
+ `solidity-coverage` is also [JoinColony/solcover][17]
## Installation
```bash
$ npm install --save-dev solidity-coverage
```
And add the following to your `buidler.config.js`:
```js
usePlugin("solidity-coverage");
module.exports = {
networks: {
coverage: {
url: 'http://localhost:8555'
}
},
}
```
## Tasks
This plugin implements a `coverage` task
```bash
npx buidler coverage --network coverage [options]
```
| Option <img width=200/> | Example <img width=750/>| Description <img width=1000/> |
|--------------|------------------------------------|--------------------------------|
| testfiles | `--testfiles "test/registry/*.ts"` | Test file(s) to run. (Globs must be enclosed by quotes.)|
| solcoverjs | `--solcoverjs ./../.solcover.js` | Relative path from working directory to config. Useful for monorepo packages that share settings. (Path must be "./" prefixed) |
| network | `--network development` | Use network settings defined in the Buidler config |
## Configuration
Options can be specified in a `.solcover.js` config file located in the root directory of your project.
**Project Examples:**
+ Simple: [buidler-metacoin][29]
+ More complex: [MolochDao/moloch][30]
**Config Example:**
```javascript
module.exports = {
skipFiles: ['Routers/EtherRouter.sol']
};
```
| Option <img width=200/>| Type <img width=200/> | Default <img width=1300/> | Description <img width=800/> |
| ------ | ---- | ------- | ----------- |
| silent | *Boolean* | false | Suppress logging output |
| client | *Object* | `require("ganache-core")` | Useful if you need a specific ganache version. |
| providerOptions | *Object* | `{ }` | [ganache-core options][1] |
| skipFiles | *Array* | `['Migrations.sol']` | Array of contracts or folders (with paths expressed relative to the `contracts` directory) that should be skipped when doing instrumentation. |
| istanbulFolder | *String* | `./coverage` | Folder location for Istanbul coverage reports. |
| istanbulReporter | *Array* | `['html', 'lcov', 'text', 'json']` | [Istanbul coverage reporters][2] |
| mocha | *Object* | `{ }` | [Mocha options][3] to merge into existing mocha config. `grep` and `invert` are useful for skipping certain tests under coverage using tags in the test descriptions.|
| onServerReady[<sup>*</sup>][14] | *Function* | | Hook run *after* server is launched, *before* the tests execute. Useful if you need to use the Oraclize bridge or have setup scripts which rely on the server's availability. [More...][23] |
| onCompileComplete[<sup>*</sup>][14] | *Function* | | Hook run *after* compilation completes, *before* tests are run. Useful if you have secondary compilation steps or need to modify built artifacts. [More...][23]|
| onTestsComplete[<sup>*</sup>][14] | *Function* | | Hook run *after* the tests complete, *before* Istanbul reports are generated. [More...][23]|
| onIstanbulComplete[<sup>*</sup>][14] | *Function* | | Hook run *after* the Istanbul reports are generated, *before* the ganache server is shut down. Useful if you need to clean resources up. [More...][23]|
[<sup>*</sup> Advanced use][14]
## Usage
+ Coverage runs tests a little more slowly.
+ Coverage launches its own in-process ganache server.
+ You can set [ganache options][1] using the `providerOptions` key in your `.solcover.js` [config][15].
+ Coverage [distorts gas consumption][13]. Tests that check exact gas consumption should be [skipped][24].
+ :warning: Contracts are compiled **without optimization**. Please report unexpected compilation faults to [issue 417][25]
## Documentation
More documentation, including FAQ and information about solidity-coverage's API [is available here][28].
[1]: https://github.com/trufflesuite/ganache-core#options
[2]: https://istanbul.js.org/docs/advanced/alternative-reporters/
[3]: https://mochajs.org/api/mocha
[4]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/faq.md#running-out-of-gas
[5]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/faq.md#running-out-of-memory
[6]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/faq.md#running-out-of-time
[7]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/faq.md#continuous-integration
[8]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/faq.md#notes-on-branch-coverage
[9]: https://sc-forks.github.io/metacoin/
[10]: https://coveralls.io/github/OpenZeppelin/openzeppelin-solidity?branch=master
[11]: https://github.com/sc-forks/solidity-coverage/tree/master/test/units
[12]: https://github.com/sc-forks/solidity-coverage/issues
[13]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/faq.md#notes-on-gas-distortion
[14]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/advanced.md
[15]: #config-options
[16]: https://blog.colony.io/code-coverage-for-solidity-eecfa88668c2
[17]: https://github.com/JoinColony/solcover
[18]: https://gitter.im/sc-forks/solidity-coverage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
[19]: https://badge.fury.io/js/solidity-coverage
[20]: https://circleci.com/gh/sc-forks/solidity-coverage
[21]: https://codecov.io/gh/sc-forks/solidity-coverage
[22]: https://cdn-images-1.medium.com/max/800/1*uum8t-31bUaa6dTRVVhj6w.png
[23]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/advanced.md#workflow-hooks
[24]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/advanced.md#skipping-tests
[25]: https://github.com/sc-forks/solidity-coverage/issues/417
[26]: https://buidler.dev/
[27]: https://www.trufflesuite.com/docs
[28]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/api.md
[29]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/upgrade.md#upgrading-from-06x-to-070
[30]: https://github.com/sc-forks/solidity-coverage/tree/0.6.x-final#solidity-coverage
[31]: https://github.com/sc-forks/solidity-coverage/releases/tag/v0.7.0
[32]: https://github.com/sc-forks/buidler-e2e/tree/coverage
[33]: https://github.com/sc-forks/moloch
[34]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/advanced.md#reducing-the-instrumentation-footprint

@ -71,10 +71,12 @@ A working example can be found at [openzeppelin-contracts, here.][35]
| Option <img width=200/> | Example <img width=750/>| Description <img width=1000/> |
|--------------|------------------------------------|--------------------------------|
| file | `--file="test/registry/*.js"` | (Truffle) Filename or glob describing a subset of tests to run. (Globs must be enclosed by quotes and use [globby matching patterns][38])|
| testfiles | `--testfiles "test/registry/*.ts"` | (Buidler) Test file(s) to run. (Globs must be enclosed by quotes and use [globby matching patterns][38])|
| testfiles | `--testfiles "test/registry/*.ts"` | (Hardhat) Test file(s) to run. (Globs must be enclosed by quotes and use [globby matching patterns][38])|
| solcoverjs | `--solcoverjs ./../.solcover.js` | Relative path from working directory to config. Useful for monorepo packages that share settings. (Path must be "./" prefixed) |
| network | `--network development` | Use network settings defined in the Truffle or Buidler config |
| network | `--network development` | Use network settings defined in the Truffle or Hardhat config |
| temp[<sup>*</sup>][14] | `--temp build` | :warning: **Caution** :warning: Path to a *disposable* folder to store compilation artifacts in. Useful when your test setup scripts include hard-coded paths to a build directory. [More...][14] |
| matrix | `--matrix` | Generate a JSON object that maps which mocha tests hit which lines of code. (Useful
as an input for some fuzzing, mutation testing and fault-localization algorithms.) [More...][39]|
[<sup>*</sup> Advanced use][14]
@ -98,6 +100,11 @@ module.exports = {
| skipFiles | *Array* | `['Migrations.sol']` | Array of contracts or folders (with paths expressed relative to the `contracts` directory) that should be skipped when doing instrumentation. |
| measureStatementCoverage | *boolean* | `true` | Computes statement (in addition to line) coverage. [More...][34] |
| measureFunctionCoverage | *boolean* | `true` | Computes function coverage. [More...][34] |
| measureModifierCoverage | *boolean* | `true` | Computes each modifier invocation as a code branch. [More...][34] |
| modifierWhitelist | *String[]* | `[]` | List of modifier names (ex: "onlyOwner") to exclude from branch measurement. (Useful for modifiers which prepare something instead of acting as a gate.)) |
| matrixOutputPath | *String* | `./testMatrix.json` | Relative path to write test matrix JSON object to. [More...][39]|
| mochaJsonOutputPath | *String* | `./mochaOutput.json` | Relative path to write mocha JSON reporter object to. [More...][39]|
| abiOutputPath | *String* | `./humanReadableAbis.json` | Relative path to write diff-able ABI data to |
| istanbulFolder | *String* | `./coverage` | Folder location for Istanbul coverage reports. |
| istanbulReporter | *Array* | `['html', 'lcov', 'text', 'json']` | [Istanbul coverage reporters][2] |
| mocha | *Object* | `{ }` | [Mocha options][3] to merge into existing mocha config. `grep` and `invert` are useful for skipping certain tests under coverage using tags in the test descriptions.|
@ -228,6 +235,7 @@ $ yarn
[36]: https://hardhat.org/
[37]: https://github.com/sc-forks/solidity-coverage/blob/master/HARDHAT_README.md
[38]: https://github.com/sindresorhus/globby#globbing-patterns
[39]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/advanced.md#generating-a-test-matrix
[1001]: https://docs.soliditylang.org/en/v0.8.0/using-the-compiler.html#input-description
[1002]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/faq.md#running-out-of-stack

@ -106,3 +106,27 @@ Setting the `measureStatementCoverage` and/or `measureFunctionCoverage` options
improve performance, lower the cost of execution and minimize complications that arise from `solc`'s
limits on how large the compilation payload can be.
## Generating a test matrix
Some advanced testing strategies benefit from knowing which tests in a suite hit a
specific line of code. Examples include:
+ [mutation testing][22], where this data lets you select the correct subset of tests to check
a mutation with.
+ [fault localization techniques][23], where the complete data set is a key input to algorithms that try
to guess where bugs might exist in a given codebase.
Running the coverage command with `--matrix` will write [a JSON test matrix][25] which maps greppable
test names to each line of code to a file named `testMatrix.json` in your project's root.
It also generates a `mochaOutput.json` file which contains test run data similar to that
generated by mocha's built-in [JSON reporter][27].
In combination these data sets can be passed to Joram's Honig's [tarantula][29] tool which uses
a fault localization algorithm to generate 'suspiciousness' ratings for each line of
Solidity code in your project.
[22]: https://github.com/JoranHonig/vertigo#vertigo
[23]: http://spideruci.org/papers/jones05.pdf
[25]: https://github.com/sc-forks/solidity-coverage/blob/master/docs/matrix.md
[27]: https://mochajs.org/api/reporters_json.js.html
[29]: https://github.com/JoranHonig/tarantula

@ -0,0 +1,421 @@
### Test Matrix Example
An example of output written to the file `./testMatrix.json` when coverage
is run with the `--matrix` cli flag. (Source project: [sc-forks/hardhat-e2e][1])
[1]: https://github.com/sc-forks/hardhat-e2e
```js
// Paths are relative to the project root directory
{
// Solidity file name
"contracts/EtherRouter/EtherRouter.sol": {
// Line number
"23": [
{
// Grep-able mocha test title
"title": "Resolves methods routed through an EtherRouter proxy",
// Selectable mocha test file
"file": "test/etherrouter.js"
}
],
"42": [
{
"title": "Resolves methods routed through an EtherRouter proxy",
"file": "test/etherrouter.js"
}
],
"45": [
{
"title": "Resolves methods routed through an EtherRouter proxy",
"file": "test/etherrouter.js"
}
],
"61": [
{
"title": "Resolves methods routed through an EtherRouter proxy",
"file": "test/etherrouter.js"
}
]
},
"contracts/EtherRouter/Factory.sol": {
"19": [
{
"title": "Resolves methods routed through an EtherRouter proxy",
"file": "test/etherrouter.js"
}
]
},
"contracts/EtherRouter/Resolver.sol": {
"22": [
{
"title": "Resolves methods routed through an EtherRouter proxy",
"file": "test/etherrouter.js"
}
],
"26": [
{
"title": "Resolves methods routed through an EtherRouter proxy",
"file": "test/etherrouter.js"
}
],
"30": [
{
"title": "Resolves methods routed through an EtherRouter proxy",
"file": "test/etherrouter.js"
}
]
},
"contracts/MetaCoin.sol": {
"16": [
{
"title": "should put 10000 MetaCoin in the first account",
"file": "test/metacoin.js"
},
{
"title": "should call a function that depends on a linked library",
"file": "test/metacoin.js"
},
{
"title": "should send coin correctly",
"file": "test/metacoin.js"
},
{
"title": "a and b",
"file": "test/multicontract.js"
}
],
"20": [
{
"title": "should send coin correctly",
"file": "test/metacoin.js"
}
],
"21": [
{
"title": "should send coin correctly",
"file": "test/metacoin.js"
}
],
"22": [
{
"title": "should send coin correctly",
"file": "test/metacoin.js"
}
],
"23": [
{
"title": "should send coin correctly",
"file": "test/metacoin.js"
}
],
"24": [
{
"title": "should send coin correctly",
"file": "test/metacoin.js"
}
],
"28": [
{
"title": "should call a function that depends on a linked library",
"file": "test/metacoin.js"
}
],
"32": [
{
"title": "should put 10000 MetaCoin in the first account",
"file": "test/metacoin.js"
},
{
"title": "should call a function that depends on a linked library",
"file": "test/metacoin.js"
},
{
"title": "should send coin correctly",
"file": "test/metacoin.js"
}
]
},
"contracts/ConvertLib.sol": {
"6": [
{
"title": "should call a function that depends on a linked library",
"file": "test/metacoin.js"
}
]
},
"contracts/MultiContractFile.sol": {
"7": [
{
"title": "a and b",
"file": "test/multicontract.js"
},
{
"title": "methods that call methods in other contracts",
"file": "test/variablecosts.js"
}
],
"15": [
{
"title": "a and b",
"file": "test/multicontract.js"
}
]
},
"contracts/VariableConstructor.sol": {
"8": [
{
"title": "should should initialize with a short string",
"file": "test/variableconstructor.js"
},
{
"title": "should should initialize with a medium length string",
"file": "test/variableconstructor.js"
},
{
"title": "should should initialize with a long string",
"file": "test/variableconstructor.js"
},
{
"title": "should should initialize with a random length string",
"file": "test/variableconstructor.js"
}
]
},
"contracts/VariableCosts.sol": {
"13": [
{
"title": "should should initialize with a short string",
"file": "test/variableconstructor.js"
},
{
"title": "should should initialize with a medium length string",
"file": "test/variableconstructor.js"
},
{
"title": "should should initialize with a long string",
"file": "test/variableconstructor.js"
},
{
"title": "should should initialize with a random length string",
"file": "test/variableconstructor.js"
},
{
"title": "should add one",
"file": "test/variablecosts.js"
},
{
"title": "should add three",
"file": "test/variablecosts.js"
},
{
"title": "should add even 5!",
"file": "test/variablecosts.js"
},
{
"title": "should delete one",
"file": "test/variablecosts.js"
},
{
"title": "should delete three",
"file": "test/variablecosts.js"
},
{
"title": "should delete five",
"file": "test/variablecosts.js"
},
{
"title": "should add five and delete one",
"file": "test/variablecosts.js"
},
{
"title": "should set a random length string",
"file": "test/variablecosts.js"
},
{
"title": "methods that do not throw",
"file": "test/variablecosts.js"
},
{
"title": "methods that throw",
"file": "test/variablecosts.js"
},
{
"title": "methods that call methods in other contracts",
"file": "test/variablecosts.js"
},
{
"title": "should allow contracts to have identically named methods",
"file": "test/variablecosts.js"
}
],
"29": [
{
"title": "should add one",
"file": "test/variablecosts.js"
},
{
"title": "should add three",
"file": "test/variablecosts.js"
},
{
"title": "should add even 5!",
"file": "test/variablecosts.js"
},
{
"title": "should add five and delete one",
"file": "test/variablecosts.js"
}
],
"30": [
{
"title": "should add one",
"file": "test/variablecosts.js"
},
{
"title": "should add three",
"file": "test/variablecosts.js"
},
{
"title": "should add even 5!",
"file": "test/variablecosts.js"
},
{
"title": "should add five and delete one",
"file": "test/variablecosts.js"
}
],
"34": [
{
"title": "should delete one",
"file": "test/variablecosts.js"
},
{
"title": "should delete three",
"file": "test/variablecosts.js"
},
{
"title": "should delete five",
"file": "test/variablecosts.js"
},
{
"title": "should add five and delete one",
"file": "test/variablecosts.js"
}
],
"35": [
{
"title": "should delete one",
"file": "test/variablecosts.js"
},
{
"title": "should delete three",
"file": "test/variablecosts.js"
},
{
"title": "should delete five",
"file": "test/variablecosts.js"
},
{
"title": "should add five and delete one",
"file": "test/variablecosts.js"
}
],
"43": [
{
"title": "should set a random length string",
"file": "test/variablecosts.js"
}
],
"47": [
{
"title": "methods that do not throw",
"file": "test/variablecosts.js"
},
{
"title": "methods that throw",
"file": "test/variablecosts.js"
}
],
"48": [
{
"title": "methods that do not throw",
"file": "test/variablecosts.js"
}
],
"52": [
{
"title": "methods that call methods in other contracts",
"file": "test/variablecosts.js"
}
],
"53": [
{
"title": "methods that call methods in other contracts",
"file": "test/variablecosts.js"
}
],
"54": [
{
"title": "methods that call methods in other contracts",
"file": "test/variablecosts.js"
}
]
},
"contracts/Wallets/Wallet.sol": {
"8": [
{
"title": "should allow contracts to have identically named methods",
"file": "test/variablecosts.js"
},
{
"title": "should should allow transfers and sends",
"file": "test/wallet.js"
}
],
"12": [
{
"title": "should allow contracts to have identically named methods",
"file": "test/variablecosts.js"
},
{
"title": "should should allow transfers and sends",
"file": "test/wallet.js"
}
],
"17": [
{
"title": "should allow contracts to have identically named methods",
"file": "test/variablecosts.js"
},
{
"title": "should should allow transfers and sends",
"file": "test/wallet.js"
}
],
"22": [
{
"title": "should allow contracts to have identically named methods",
"file": "test/variablecosts.js"
},
{
"title": "should should allow transfers and sends",
"file": "test/wallet.js"
}
],
"23": [
{
"title": "should allow contracts to have identically named methods",
"file": "test/variablecosts.js"
},
{
"title": "should should allow transfers and sends",
"file": "test/wallet.js"
}
]
}
}
```

@ -0,0 +1,99 @@
const ethersABI = require("@ethersproject/abi");
const difflib = require('difflib');
class AbiUtils {
diff(orig={}, cur={}){
let plus = 0;
let minus = 0;
const unifiedDiff = difflib.unifiedDiff(
orig.humanReadableAbiList,
cur.humanReadableAbiList,
{
fromfile: orig.contractName,
tofile: cur.contractName,
fromfiledate: `sha: ${orig.sha}`,
tofiledate: `sha: ${cur.sha}`,
lineterm: ''
}
);
// Count changes (unified diff always has a plus & minus in header);
if (unifiedDiff.length){
plus = -1;
minus = -1;
}
unifiedDiff.forEach(line => {
if (line[0] === `+`) plus++;
if (line[0] === `-`) minus++;
})
return {
plus,
minus,
unifiedDiff
}
}
toHumanReadableFunctions(contract){
const human = [];
const ethersOutput = new ethersABI.Interface(contract.abi).functions;
const signatures = Object.keys(ethersOutput);
for (const sig of signatures){
const method = ethersOutput[sig];
let returns = '';
method.outputs.forEach(output => {
(returns.length)
? returns += `, ${output.type}`
: returns += output.type;
});
let readable = `${method.type} ${sig} ${method.stateMutability}`;
if (returns.length){
readable += ` returns (${returns})`
}
human.push(readable);
}
return human;
}
toHumanReadableEvents(contract){
const human = [];
const ethersOutput = new ethersABI.Interface(contract.abi).events;
const signatures = Object.keys(ethersOutput);
for (const sig of signatures){
const method = ethersOutput[sig];
const readable = `${ethersOutput[sig].type} ${sig}`;
human.push(readable);
}
return human;
}
generateHumanReadableAbiList(_artifacts, sha){
const list = [];
if (_artifacts.length){
for (const item of _artifacts){
const fns = this.toHumanReadableFunctions(item);
const evts = this.toHumanReadableEvents(item);
const all = fns.concat(evts);
list.push({
contractName: item.contractName,
sha: sha,
humanReadableAbiList: all
})
}
}
return list;
}
}
module.exports = AbiUtils;

@ -12,14 +12,17 @@ const Instrumenter = require('./instrumenter');
const Coverage = require('./coverage');
const DataCollector = require('./collector');
const AppUI = require('./ui').AppUI;
const AbiUtils = require('./abi');
/**
* Coverage Runner
*/
class API {
constructor(config={}) {
this.validator = new ConfigValidator()
this.validator = new ConfigValidator();
this.abiUtils = new AbiUtils();
this.config = config || {};
this.testMatrix = {};
// Validate
this.validator.validate(this.config);
@ -30,6 +33,10 @@ class API {
this.testsErrored = false;
this.cwd = config.cwd || process.cwd();
this.abiOutputPath = config.abiOutputPath || "humanReadableAbis.json";
this.matrixOutputPath = config.matrixOutputPath || "testMatrix.json";
this.mochaJsonOutputPath = config.mochaJsonOutputPath || "mochaOutput.json";
this.matrixReporterPath = config.matrixReporterPath || "solidity-coverage/plugins/resources/matrix.js"
this.defaultHook = () => {};
this.onServerReady = config.onServerReady || this.defaultHook;
@ -296,6 +303,54 @@ class API {
}
}*/
// ==========================
// Test Matrix Data Collector
// ==========================
/**
* @param {Object} testInfo Mocha object passed to reporter 'test end' event
*/
collectTestMatrixData(testInfo){
const hashes = Object.keys(this.instrumenter.instrumentationData);
const title = testInfo.title;
const file = path.relative(this.cwd, testInfo.file);
for (const hash of hashes){
const {
contractPath,
hits,
type,
id
} = this.instrumenter.instrumentationData[hash];
if (type === 'line'){
if (!this.testMatrix[contractPath]){
this.testMatrix[contractPath] = {};
}
if (!this.testMatrix[contractPath][id]){
this.testMatrix[contractPath][id] = [];
}
if (hits > 0){
// Search for and exclude duplicate entries
let duplicate = false;
for (const item of this.testMatrix[contractPath][id]){
if (item.title === title && item.file === file){
duplicate = true;
break;
}
}
if (!duplicate) {
this.testMatrix[contractPath][id].push({title, file});
}
// Reset line data
this.instrumenter.instrumentationData[hash].hits = 0;
}
}
}
}
// ========
// File I/O
// ========
@ -305,6 +360,22 @@ class API {
fs.writeFileSync(covPath, JSON.stringify(data));
}
saveTestMatrix(){
const matrixPath = path.join(this.cwd, this.matrixOutputPath);
const mapping = this.makeKeysRelative(this.testMatrix, this.cwd);
fs.writeFileSync(matrixPath, JSON.stringify(mapping, null, ' '));
}
saveMochaJsonOutput(data){
const outputPath = path.join(this.cwd, this.mochaJsonOutputPath);
fs.writeFileSync(outputPath, JSON.stringify(data, null, ' '));
}
saveHumanReadableAbis(data){
const abiPath = path.join(this.cwd, this.abiOutputPath);
fs.writeFileSync(abiPath, JSON.stringify(data, null, ' '));
}
// =====
// Paths
// =====

@ -53,17 +53,17 @@ class DataCollector {
}
/**
* Left-pads zero prefixed bytes 32 hashes to length 66. The '59' in the
* Left-pads zero prefixed bytes8 hashes to length 18. The '11' in the
* comparison below is arbitrary. It provides a margin for recurring zeros
* but prevents left-padding shorter irrelevant hashes (like fn sigs)
* but prevents left-padding shorter irrelevant hashes
*
* @param {String} hash data hash from evm stack.
* @return {String} 0x prefixed hash of length 66.
*/
_normalizeHash(hash){
if (hash.length < 66 && hash.length > 59){
if (hash.length < 18 && hash.length > 11){
hash = hash.slice(2);
while(hash.length < 64) hash = '0' + hash;
while(hash.length < 16) hash = '0' + hash;
hash = '0x' + hash
}
return hash;

@ -69,6 +69,9 @@ class Coverage {
const data = collectedData[hash];
const contractPath = collectedData[hash].contractPath;
const id = collectedData[hash].id;
// NB: Any branch using the injected fn which returns boolean will have artificially
// doubled hits (because of something internal to Solidity about how the stack is managed)
const hits = collectedData[hash].hits;
switch(collectedData[hash].type){
@ -76,8 +79,10 @@ class Coverage {
case 'function': this.data[contractPath].f[id] = hits; break;
case 'statement': this.data[contractPath].s[id] = hits; break;
case 'branch': this.data[contractPath].b[id][data.locationIdx] = hits; break;
case 'requirePre': this.requireData[contractPath][id].preEvents = hits; break;
case 'requirePost': this.requireData[contractPath][id].postEvents = hits; break;
case 'and-true': this.data[contractPath].b[id][data.locationIdx] = hits; break;
case 'or-false': this.data[contractPath].b[id][data.locationIdx] = hits; break;
case 'requirePre': this.requireData[contractPath][id].preEvents = hits; break;
case 'requirePost': this.requireData[contractPath][id].postEvents = hits; break;
}
}

@ -3,6 +3,8 @@ const web3Utils = require("web3-utils");
class Injector {
constructor(){
this.hashCounter = 0;
this.modifierCounter = 0;
this.modifiers = {};
}
_split(contract, injectionPoint){
@ -13,16 +15,40 @@ class Injector {
}
_getInjectable(id, hash, type){
return `${this._getMethodIdentifier(id)}(${hash}); /* ${type} */ \n`;
switch(type){
case 'and-true':
return ` && ${this._getTrueMethodIdentifier(id)}(${hash}))`;
case 'or-false':
return ` || ${this._getFalseMethodIdentifier(id)}(${hash}))`;
case 'modifier':
return ` ${this._getModifierIdentifier(id)} `;
default:
return `${this._getDefaultMethodIdentifier(id)}(${hash}); /* ${type} */ \n`;
}
}
_getHash(id) {
this.hashCounter++;
return web3Utils.keccak256(`${id}:${this.hashCounter}`);
return web3Utils.keccak256(`${id}:${this.hashCounter}`).slice(0,18);
}
// Method returns void
_getDefaultMethodIdentifier(id){
return `c_${web3Utils.keccak256(id).slice(2,10)}`
}
// Method returns boolean: true
_getTrueMethodIdentifier(id){
return `c_true${web3Utils.keccak256(id).slice(2,10)}`
}
// Method returns boolean: false
_getFalseMethodIdentifier(id){
return `c_false${web3Utils.keccak256(id).slice(2,10)}`
}
_getMethodIdentifier(id){
return `c_${web3Utils.keccak256(id).slice(0,10)}`
_getModifierIdentifier(id){
return `c_mod${web3Utils.keccak256(id).slice(2,10)}`
}
_getInjectionComponents(contract, injectionPoint, id, type){
@ -44,10 +70,10 @@ class Injector {
* @param {String} id
* @return {String}
*/
_getHashMethodDefinition(id, contract){
const hash = web3Utils.keccak256(id).slice(0,10);
const method = this._getMethodIdentifier(id);
return `\nfunction ${method}(bytes32 c__${hash}) internal pure {}\n`;
_getDefaultMethodDefinition(id){
const hash = web3Utils.keccak256(id).slice(2,10);
const method = this._getDefaultMethodIdentifier(id);
return `\nfunction ${method}(bytes8 c__${hash}) internal pure {}\n`;
}
/**
@ -57,9 +83,100 @@ class Injector {
* @return {String}
*/
_getFileScopedHashMethodDefinition(id, contract){
const hash = web3Utils.keccak256(id).slice(0,10);
const method = this._getMethodIdentifier(id);
return `\nfunction ${method}(bytes32 c__${hash}) pure {}\n`;
const hash = web3Utils.keccak256(id).slice(2,10);
const method = this._getDefaultMethodIdentifier(id);
return `\nfunction ${method}(bytes8 c__${hash}) pure {}\n`;
}
/**
* Generates a solidity statement injection defining a method
* *which returns boolean true* to pass instrumentation hash to.
* @param {String} fileName
* @return {String} ex: bytes32[1] memory _sc_82e0891
*/
_getTrueMethodDefinition(id){
const hash = web3Utils.keccak256(id).slice(2,10);
const method = this._getTrueMethodIdentifier(id);
return `function ${method}(bytes8 c__${hash}) internal pure returns (bool){ return true; }\n`;
}
/**
* Generates a solidity statement injection defining a method
* *which returns boolean true* to pass instrumentation hash to.
* Declared once per file. (Has no visibility modifier)
* @param {String} fileName
* @return {String} ex: bytes32[1] memory _sc_82e0891
*/
_getFileScopeTrueMethodDefinition(id){
const hash = web3Utils.keccak256(id).slice(2,10);
const method = this._getTrueMethodIdentifier(id);
return `function ${method}(bytes8 c__${hash}) pure returns (bool){ return true; }\n`;
}
/**
* Generates a solidity statement injection defining a method
* *which returns boolean false* to pass instrumentation hash to.
* @param {String} fileName
* @return {String} ex: bytes32[1] memory _sc_82e0891
*/
_getFalseMethodDefinition(id){
const hash = web3Utils.keccak256(id).slice(2,10);
const method = this._getFalseMethodIdentifier(id);
return `function ${method}(bytes8 c__${hash}) internal pure returns (bool){ return false; }\n`;
}
/**
* Generates a solidity statement injection defining a method
* *which returns boolean false* to pass instrumentation hash to.
* Declared once per file. (Has no visibility modifier)
* @param {String} fileName
* @return {String} ex: bytes32[1] memory _sc_82e0891
*/
_getFileScopedFalseMethodDefinition(id){
const hash = web3Utils.keccak256(id).slice(2,10);
const method = this._getFalseMethodIdentifier(id);
return `function ${method}(bytes8 c__${hash}) pure returns (bool){ return false; }\n`;
}
_getModifierDefinitions(contractId, instrumentation){
let injection = '';
if (this.modifiers[contractId]){
for (const item of this.modifiers[contractId]){
injection += `modifier ${this._getModifierIdentifier(item.modifierId)}{ `;
let hash = this._getHash(item.modifierId);
let comment = `modifier-${item.condition}`;
let injectable = this._getInjectable(item.contractId, hash, comment);
// Process modifiers in the same step as `require` stmts in coverage.js
let type = (item.condition === 'pre') ? 'requirePre' : 'requirePost';
instrumentation[hash] = {
id: item.branchId,
type: type,
contractPath: item.fileName,
hits: 0
}
injection += `${injectable} _; }\n`;
}
}
return injection;
}
_cacheModifier(injection){
if (!this.modifiers[injection.contractId]) {
this.modifiers[injection.contractId] = [];
}
this.modifiers[injection.contractId].push(injection);
}
resetModifierMapping(){
this.modifiers = {};
}
injectLine(contract, fileName, injectionPoint, injection, instrumentation){
@ -217,9 +334,104 @@ class Injector {
const end = contract.instrumented.slice(injectionPoint);
const id = `${fileName}:${injection.contractName}`;
contract.instrumented = (injection.isFileScoped)
? `${start}${this._getFileScopedHashMethodDefinition(id)}${end}`
: `${start}${this._getHashMethodDefinition(id)}${end}`;
const methodDefinition = (injection.isFileScoped)
? this._getFileScopedHashMethodDefinition(id)
: this._getDefaultMethodDefinition(id);
const trueMethodDefinition = (injection.isFileScoped)
? this._getFileScopeTrueMethodDefinition(id)
: this._getTrueMethodDefinition(id);
const falseMethodDefinition = (injection.isFileScoped)
? this._getFileScopedFalseMethodDefinition(id)
: this._getFalseMethodDefinition(id);
const modifierDefinition = (injection.isFileScoped)
? ""
: this._getModifierDefinitions(id, instrumentation);
contract.instrumented = `${start}` +
`${methodDefinition}` +
`${trueMethodDefinition}` +
`${falseMethodDefinition}` +
`${modifierDefinition}` +
`${end}`;
}
injectOpenParen(contract, fileName, injectionPoint, injection, instrumentation){
const start = contract.instrumented.slice(0, injectionPoint);
const end = contract.instrumented.slice(injectionPoint);
contract.instrumented = `${start}(${end}`;
}
injectAndTrue(contract, fileName, injectionPoint, injection, instrumentation){
const type = 'and-true';
const id = `${fileName}:${injection.contractName}`;
const {
start,
end,
hash,
injectable
} = this._getInjectionComponents(contract, injectionPoint, id, type);
instrumentation[hash] = {
id: injection.branchId,
locationIdx: injection.locationIdx,
type: type,
contractPath: fileName,
hits: 0
}
contract.instrumented = `${start}${injectable}${end}`;
}
injectOrFalse(contract, fileName, injectionPoint, injection, instrumentation){
const type = 'or-false';
const id = `${fileName}:${injection.contractName}`;
const {
start,
end,
hash,
injectable
} = this._getInjectionComponents(contract, injectionPoint, id, type);
instrumentation[hash] = {
id: injection.branchId,
locationIdx: injection.locationIdx,
type: type,
contractPath: fileName,
hits: 0
}
contract.instrumented = `${start}${injectable}${end}`;
}
injectModifier(contract, fileName, injectionPoint, injection, instrumentation){
this.modifierCounter++;
const type = 'modifier';
const contractId = `${fileName}:${injection.contractName}`;
const modifierId = `${fileName}:${injection.contractName}:` +
`${injection.modifierName}:${injection.fnId}:` +
`${injection.condition}:${this.modifierCounter}`;
const {
start,
end,
hash,
injectable
} = this._getInjectionComponents(contract, injectionPoint, modifierId, type);
this._cacheModifier({
contractId,
modifierId,
fileName,
...injection
});
contract.instrumented = `${start}${injectable}${end}`;
}
};

@ -15,8 +15,14 @@ class Instrumenter {
constructor(config={}){
this.instrumentationData = {};
this.injector = new Injector();
this.measureStatementCoverage = (config.measureStatementCoverage === false) ? false : true;
this.measureFunctionCoverage = (config.measureFunctionCoverage === false) ? false: true;
this.modifierWhitelist = config.modifierWhitelist || [];
this.enabled = {
statements: (config.measureStatementCoverage === false) ? false : true,
functions: (config.measureFunctionCoverage === false) ? false: true,
modifiers: (config.measureModifierCoverage === false) ? false: true,
branches: (config.measureBranchCoverage === false) ? false: true,
lines: (config.measureLineCoverage === false) ? false: true
};
}
_isRootNode(node){
@ -56,16 +62,17 @@ class Instrumenter {
instrument(contractSource, fileName) {
const contract = {};
this.injector.resetModifierMapping();
parse.configure(this.enabled, this.modifierWhitelist);
contract.source = contractSource;
contract.instrumented = contractSource;
this._initializeCoverageFields(contract);
parse.configureStatementCoverage(this.measureStatementCoverage)
parse.configureFunctionCoverage(this.measureFunctionCoverage)
// First, we run over the original contract to get the source mapping.
let ast = SolidityParser.parse(contract.source, {loc: true, range: true});
//console.log(JSON.stringify(ast, null, ' '))
parse[ast.type](contract, ast);
const retValue = JSON.parse(JSON.stringify(contract)); // Possibly apotropaic.
@ -88,7 +95,12 @@ class Instrumenter {
// Line instrumentation has to happen first
contract.injectionPoints[injectionPoint].sort((a, b) => {
const injections = ['injectBranch', 'injectEmptyBranch', 'injectLine'];
const injections = [
'injectLogicalOR',
'injectBranch',
'injectEmptyBranch',
'injectLine'
];
return injections.indexOf(b.type) - injections.indexOf(a.type);
});

@ -11,12 +11,9 @@ const FILE_SCOPED_ID = "fileScopedId";
const parse = {};
// Utilities
parse.configureStatementCoverage = function(val){
register.measureStatementCoverage = val;
}
parse.configureFunctionCoverage = function(val){
register.measureFunctionCoverage = val;
parse.configure = function(_enabled, _whitelist){
register.enabled = Object.assign(register.enabled, _enabled);
register.modifierWhitelist = _whitelist;
}
// Nodes
@ -32,17 +29,55 @@ parse.Block = function(contract, expression) {
}
};
parse.BinaryOperation = function(contract, expression) {
register.statement(contract, expression);
parse.BinaryOperation = function(contract, expression, skipStatementRegistry) {
// Free-floating ternary conditional
if (expression.left && expression.left.type === 'Conditional'){
parse[expression.left.type](contract, expression.left, true);
// Ternary conditional assignment
} else if (expression.right && expression.right.type === 'Conditional'){
parse[expression.right.type](contract, expression.right, true);
// Regular binary operation
} else if(!skipStatementRegistry){
// noop
// LogicalOR condition search...
} else {
parse[expression.left.type] &&
parse[expression.left.type](contract, expression.left, true);
parse[expression.right.type] &&
parse[expression.right.type](contract, expression.right, true);
if (expression.operator === '||'){
register.logicalOR(contract, expression);
}
}
}
parse.FunctionCall = function(contract, expression) {
parse.TupleExpression = function(contract, expression, skipStatementRegistry) {
expression.components.forEach(component => {
parse[component.type] &&
parse[component.type](contract, component, skipStatementRegistry);
});
}
parse.FunctionCall = function(contract, expression, skipStatementRegistry) {
// In any given chain of call expressions, only the last one will fail this check.
// This makes sure we don't instrument a chain of expressions multiple times.
if (expression.expression.type !== 'FunctionCall') {
register.statement(contract, expression);
// Don't register sub-expressions (like intermediate method calls)
if (!skipStatementRegistry){
register.statement(contract, expression);
}
if (expression.expression.name === 'require') {
register.requireBranch(contract, expression);
expression.arguments.forEach(arg => {
parse[arg.type] && parse[arg.type](contract, arg, true);
});
}
parse[expression.expression.type] &&
parse[expression.expression.type](contract, expression.expression);
@ -52,10 +87,11 @@ parse.FunctionCall = function(contract, expression) {
}
};
parse.Conditional = function(contract, expression) {
register.statement(contract, expression);
// TODO: Investigate node structure
// There are potential substatements here we aren't measuring
parse.Conditional = function(contract, expression, skipStatementRegistry) {
parse[expression.condition.type] &&
parse[expression.condition.type](contract, expression.condition, true);
register.conditional(contract, expression);
};
parse.ContractDefinition = function(contract, expression) {
@ -145,6 +181,10 @@ parse.FunctionDefinition = function(contract, expression) {
parse.IfStatement = function(contract, expression) {
register.statement(contract, expression);
register.ifStatement(contract, expression);
parse[expression.condition.type] &&
parse[expression.condition.type](contract, expression.condition, true);
parse[expression.trueBody.type] &&
parse[expression.trueBody.type](contract, expression.trueBody);
@ -220,6 +260,10 @@ parse.SourceUnit = function(contract, expression) {
parse.ReturnStatement = function(contract, expression) {
register.statement(contract, expression);
expression.expression &&
parse[expression.expression.type] &&
parse[expression.expression.type](contract, expression.expression, true);
};
// TODO:Investigate node structure
@ -250,11 +294,18 @@ parse.UsingStatement = function (contract, expression) {
};
parse.VariableDeclarationStatement = function (contract, expression) {
if (expression.initialValue && expression.initialValue.type === 'Conditional'){
parse[expression.initialValue.type](contract, expression.initialValue, true)
}
register.statement(contract, expression);
};
parse.WhileStatement = function (contract, expression) {
register.statement(contract, expression);
parse[expression.condition.type] &&
parse[expression.condition.type](contract, expression.condition, true);
parse[expression.body.type] &&
parse[expression.body.type](contract, expression.body);
};

@ -12,8 +12,15 @@ class Registrar {
this.trackStatements = true;
// These are set by user option and enable/disable the measurement completely
this.measureStatementCoverage = true;
this.measureFunctionCoverage = true;
this.enabled = {
statements: true,
functions: true,
modifiers: true,
branches: true,
lines: true
}
this.modifierWhitelist = [];
}
/**
@ -36,7 +43,7 @@ class Registrar {
* @param {Object} expression AST node
*/
statement(contract, expression) {
if (!this.trackStatements || !this.measureStatementCoverage) return;
if (!this.trackStatements || !this.enabled.statements) return;
const startContract = contract.instrumented.slice(0, expression.range[0]);
const startline = ( startContract.match(/\n/g) || [] ).length + 1;
@ -76,6 +83,8 @@ class Registrar {
* @param {Object} expression AST node
*/
line(contract, expression) {
if (!this.enabled.lines) return;
const startchar = expression.range[0];
const endchar = expression.range[1] + 1;
const lastNewLine = contract.instrumented.slice(0, startchar).lastIndexOf('\n');
@ -107,17 +116,52 @@ class Registrar {
* @param {Object} expression AST node
*/
functionDeclaration(contract, expression) {
if (!this.measureFunctionCoverage) return;
if (!this.enabled.functions) return;
let start = 0;
contract.fnId += 1;
// It's possible functions will have modifiers that take string args
// which contains an open curly brace. Skip ahead...
if (expression.modifiers && expression.modifiers.length){
for (let modifier of expression.modifiers ){
// It's possible functions will have modifiers that take string args
// which contains an open curly brace. Skip ahead...
if (modifier.range[1] > start){
start = modifier.range[1];
}
// Add modifier branch coverage
if (
!this.enabled.modifiers ||
expression.isConstructor ||
this.modifierWhitelist.includes(modifier.name)
) {
continue;
}
this.addNewBranch(contract, modifier);
this._createInjectionPoint(
contract,
modifier.range[0],
{
type: 'injectModifier',
branchId: contract.branchId,
modifierName: modifier.name,
fnId: contract.fnId,
condition: 'pre'
}
);
this._createInjectionPoint(
contract,
modifier.range[1] + 1,
{
type: 'injectModifier',
branchId: contract.branchId,
modifierName: modifier.name,
fnId: contract.fnId,
condition: 'post'
}
);
}
} else {
start = expression.range[0];
@ -133,7 +177,6 @@ class Registrar {
start + endlineDelta
);
contract.fnId += 1;
contract.fnMap[contract.fnId] = {
name: expression.isConstructor ? 'constructor' : expression.name,
line: startline,
@ -186,12 +229,207 @@ class Registrar {
};
};
addNewConditionalBranch(contract, expression){
let start;
// Instabul HTML highlighting location data...
const trueZero = expression.trueExpression.range[0];
const trueOne = expression.trueExpression.range[1];
const falseZero = expression.falseExpression.range[0];
const falseOne = expression.falseExpression.range[1];
start = contract.instrumented.slice(0, trueZero);
const trueStartLine = ( start.match(/\n/g) || [] ).length + 1;
const trueStartCol = trueZero - start.lastIndexOf('\n') - 1;
start = contract.instrumented.slice(0, trueOne);
const trueEndLine = ( start.match(/\n/g) || [] ).length + 1;
const trueEndCol = trueOne - start.lastIndexOf('\n') - 1;
start = contract.instrumented.slice(0, falseZero);
const falseStartLine = ( start.match(/\n/g) || [] ).length + 1;
const falseStartCol = falseZero - start.lastIndexOf('\n') - 1;
start = contract.instrumented.slice(0, falseOne);
const falseEndLine = ( start.match(/\n/g) || [] ).length + 1;
const falseEndCol = falseOne - start.lastIndexOf('\n') - 1;
contract.branchId += 1;
contract.branchMap[contract.branchId] = {
line: trueStartLine,
type: 'if',
locations: [{
start: {
line: trueStartLine, column: trueStartCol,
},
end: {
line: trueEndLine, column: trueEndCol,
},
}, {
start: {
line: falseStartLine, column: falseStartCol,
},
end: {
line: falseEndLine, column: falseEndCol,
},
}],
};
}
/**
* Registers injections for branch measurements. Used by logicalOR registration method.
* @param {Object} contract instrumentation target
* @param {Object} expression AST node
*/
addNewLogicalORBranch(contract, expression) {
let start;
// Instabul HTML highlighting location data...
const leftZero = expression.left.range[0];
const leftOne = expression.left.range[1];
const rightZero = expression.right.range[0];
const rightOne = expression.right.range[1];
start = contract.instrumented.slice(0, leftZero);
const leftStartLine = ( start.match(/\n/g) || [] ).length + 1;
const leftStartCol = leftZero - start.lastIndexOf('\n') - 1;
start = contract.instrumented.slice(0, leftOne);
const leftEndLine = ( start.match(/\n/g) || [] ).length + 1;
const leftEndCol = leftOne - start.lastIndexOf('\n') - 1;
start = contract.instrumented.slice(0, rightZero);
const rightStartLine = ( start.match(/\n/g) || [] ).length + 1;
const rightStartCol = rightZero - start.lastIndexOf('\n') - 1;
start = contract.instrumented.slice(0, rightOne);
const rightEndLine = ( start.match(/\n/g) || [] ).length + 1;
const rightEndCol = rightOne - start.lastIndexOf('\n') - 1;
contract.branchId += 1;
// NB locations for if branches in istanbul are zero
// length and associated with the start of the if.
contract.branchMap[contract.branchId] = {
line: leftStartLine,
type: 'cond-expr',
locations: [{
start: {
line: leftStartLine, column: leftStartCol,
},
end: {
line: leftEndLine, column: leftEndCol,
},
}, {
start: {
line: rightStartLine, column: rightStartCol,
},
end: {
line: rightEndLine, column: rightEndCol,
},
}],
};
};
conditional(contract, expression){
if (!this.enabled.branches) return;
this.addNewConditionalBranch(contract, expression);
// Double open parens
this._createInjectionPoint(
contract,
expression.condition.range[0],
{
type: 'injectOpenParen',
}
);
this._createInjectionPoint(
contract,
expression.condition.range[0],
{
type: 'injectOpenParen',
}
);
// False condition: (these get sorted in reverse order, so create in reversed order)
this._createInjectionPoint(
contract,
expression.condition.range[1] + 1,
{
type: 'injectOrFalse',
branchId: contract.branchId,
locationIdx: 1
}
);
// True condition
this._createInjectionPoint(
contract,
expression.condition.range[1] + 1,
{
type: 'injectAndTrue',
branchId: contract.branchId,
locationIdx: 0
}
);
}
/**
* Registers injections for logicalOR clause measurements (branches)
* @param {Object} contract instrumentation target
* @param {Object} expression AST node
* @param {Number} injectionIdx pre/post branch index (left=0, right=1)
*/
logicalOR(contract, expression) {
if (!this.enabled.branches) return;
this.addNewLogicalORBranch(contract, expression);
// Left
this._createInjectionPoint(
contract,
expression.left.range[0],
{
type: 'injectOpenParen',
}
);
this._createInjectionPoint(
contract,
expression.left.range[1] + 1,
{
type: 'injectAndTrue',
branchId: contract.branchId,
locationIdx: 0
}
);
// Right
this._createInjectionPoint(
contract,
expression.right.range[0],
{
type: 'injectOpenParen',
}
);
this._createInjectionPoint(
contract,
expression.right.range[1] + 1,
{
type: 'injectAndTrue',
branchId: contract.branchId,
locationIdx: 1
}
);
}
/**
* Registers injections for require statement measurements (branches)
* @param {Object} contract instrumentation target
* @param {Object} expression AST node
*/
requireBranch(contract, expression) {
if (!this.enabled.branches) return;
this.addNewBranch(contract, expression);
this._createInjectionPoint(
contract,
@ -217,6 +455,8 @@ class Registrar {
* @param {Object} expression AST node
*/
ifStatement(contract, expression) {
if (!this.enabled.branches) return;
this.addNewBranch(contract, expression);
if (expression.trueBody.type === 'Block') {

@ -1,185 +0,0 @@
/* eslint-env node, mocha */
/*const path = require('path');
const getInstrumentedVersion = require('./../lib/instrumentSolidity.js');
const util = require('./util/util.js');
const CoverageMap = require('./../lib/coverageMap');
const vm = require('./util/vm');
const assert = require('assert');
describe.skip('conditional statements', () => {
const filePath = path.resolve('./test.sol');
const pathPrefix = './';
it('should cover a conditional that reaches the consequent (same-line)', done => {
const contract = util.getCode('conditional/sameline-consequent.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [1, 0],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover a conditional that reaches the alternate (same-line)', done => {
const contract = util.getCode('conditional/sameline-alternate.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover a conditional that reaches the consequent (multi-line)', done => {
const contract = util.getCode('conditional/multiline-consequent.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [1, 0],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover a conditional that reaches the alternate (multi-line)', done => {
const contract = util.getCode('conditional/multiline-alternate.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover a DeclarativeExpression assignment by conditional that reaches the alternate', done => {
const contract = util.getCode('conditional/declarative-exp-assignment-alternate.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
// Runs bool z = (x) ? false : true;
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover an Identifier assignment by conditional that reaches the alternate', done => {
const contract = util.getCode('conditional/identifier-assignment-alternate.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
// Runs z = (x) ? false : true;
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
5: 1, 6: 1, 7: 1, 8: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1, 4: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
it('should cover an assignment to a member expression (reaches the alternate)', done => {
const contract = util.getCode('conditional/mapping-assignment.sol');
const info = getInstrumentedVersion(contract, filePath);
const coverage = new CoverageMap();
coverage.addContract(info, filePath);
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
11: 1, 12: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,
});
done();
}).catch(done);
});
});
*/

@ -1,121 +0,0 @@
/**
* This is logic to instrument ternary conditional assignment statements. Preserving
* here for the time being, because instrumentation of these became impossible in
* solc >= 0.5.0
*/
function instrumentAssignmentExpression(contract, expression) {
// This is suspended for 0.5.0 which tries to accomodate the new `emit` keyword.
// Solc is not allowing us to use the construction `emit SomeEvent()` within the parens :/
return;
// --------------------------------------------------------------------------------------------
// The only time we instrument an assignment expression is if there's a conditional expression on
// the right
/*if (expression.right.type === 'ConditionalExpression') {
if (expression.left.type === 'DeclarativeExpression' || expression.left.type === 'Identifier') {
// Then we need to go from bytes32 varname = (conditional expression)
// to bytes32 varname; (,varname) = (conditional expression)
createOrAppendInjectionPoint(contract, expression.left.range[1], {
type: 'literal', string: '; (,' + expression.left.name + ')',
});
instrumenter.instrumentConditionalExpression(contract, expression.right);
} else if (expression.left.type === 'MemberExpression') {
createOrAppendInjectionPoint(contract, expression.left.range[0], {
type: 'literal', string: '(,',
});
createOrAppendInjectionPoint(contract, expression.left.range[1], {
type: 'literal', string: ')',
});
instrumenter.instrumentConditionalExpression(contract, expression.right);
} else {
const err = 'Error instrumenting assignment expression @ solidity-coverage/lib/instrumenter.js';
console.log(err, contract, expression.left);
process.exit();
}
}*/
};
function instrumentConditionalExpression(contract, expression) {
// ----------------------------------------------------------------------------------------------
// This is suspended for 0.5.0 which tries to accomodate the new `emit` keyword.
// Solc is not allowing us to use the construction `emit SomeEvent()` within the parens :/
// Very sad, this is the coolest thing in here.
return;
// ----------------------------------------------------------------------------------------------
/*contract.branchId += 1;
const startline = (contract.instrumented.slice(0, expression.range[0]).match(/\n/g) || []).length + 1;
const startcol = expression.range[0] - contract.instrumented.slice(0, expression.range[0]).lastIndexOf('\n') - 1;
const consequentStartCol = startcol + (contract, expression.trueBody.range[0] - expression.range[0]);
const consequentEndCol = consequentStartCol + (contract, expression.trueBody.range[1] - expression.trueBody.range[0]);
const alternateStartCol = startcol + (contract, expression.falseBody.range[0] - expression.range[0]);
const alternateEndCol = alternateStartCol + (contract, expression.falseBody.range[1] - expression.falseBody.range[0]);
// NB locations for conditional branches in istanbul are length 1 and associated with the : and ?.
contract.branchMap[contract.branchId] = {
line: startline,
type: 'cond-expr',
locations: [{
start: {
line: startline, column: consequentStartCol,
},
end: {
line: startline, column: consequentEndCol,
},
}, {
start: {
line: startline, column: alternateStartCol,
},
end: {
line: startline, column: alternateEndCol,
},
}],
};
// Right, this could be being used just by itself or as an assignment. In the case of the latter, because
// the comma operator doesn't exist, we're going to have to get funky.
// if we're on a line by ourselves, this is easier
//
// Now if we've got to wrap the expression it's being set equal to, do that...
// Wrap the consequent
createOrAppendInjectionPoint(contract, expression.trueBody.range[0], {
type: 'openParen',
});
createOrAppendInjectionPoint(contract, expression.trueBody.range[0], {
type: 'callBranchEvent', comma: true, branchId: contract.branchId, locationIdx: 0,
});
createOrAppendInjectionPoint(contract, expression.trueBody.range[1], {
type: 'closeParen',
});
// Wrap the alternate
createOrAppendInjectionPoint(contract, expression.falseBody.range[0], {
type: 'openParen',
});
createOrAppendInjectionPoint(contract, expression.falseBody.range[0], {
type: 'callBranchEvent', comma: true, branchId: contract.branchId, locationIdx: 1,
});
createOrAppendInjectionPoint(contract, expression.falseBody.range[1], {
type: 'closeParen',
});*/
};
// Paren / Literal injectors
/*
injector.openParen = function injectOpenParen(contract, fileName, injectionPoint, injection) {
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + '(' + contract.instrumented.slice(injectionPoint);
};
injector.closeParen = function injectCloseParen(contract, fileName, injectionPoint, injection) {
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + ')' + contract.instrumented.slice(injectionPoint);
};
injector.literal = function injectLiteral(contract, fileName, injectionPoint, injection) {
contract.instrumented = contract.instrumented.slice(0, injectionPoint) + injection.string + contract.instrumented.slice(injectionPoint);
};
*/

@ -14,14 +14,19 @@ const configSchema = {
client: {type: "object"},
cwd: {type: "string"},
host: {type: "string"},
abiOutputPath: {type: "string"},
matrixOutputPath: {type: "string"},
matrixReporterPath: {type: "string"},
port: {type: "number"},
providerOptions: {type: "object"},
silent: {type: "boolean"},
autoLaunchServer: {type: "boolean"},
istanbulFolder: {type: "string"},
measureStatementCoverage: {type: "boolean"},
measureFunctionCoverage: {type: "boolean"},
measureFunctionCoverage: {type: "boolean"},
measureModifierCoverage: {type: "boolean"},
measureLineCoverage: {type: "boolean"},
measureBranchCoverage: {type: "boolean"},
// Hooks:
onServerReady: {type: "function", format: "isFunction"},
@ -39,6 +44,11 @@ const configSchema = {
type: "array",
items: {type: "string"}
},
modifierWhitelist: {
type: "array",
items: {type: "string"}
}
},
};

@ -12,7 +12,7 @@
"scripts": {
"nyc": "SILENT=true nyc --exclude '**/sc_temp/**' --exclude '**/test/**'",
"test": "SILENT=true node --max-old-space-size=4096 ./node_modules/.bin/nyc --exclude '**/sc_temp/**' --exclude '**/test/**/' -- mocha test/units/* --timeout 100000 --no-warnings --exit",
"test:ci": "SILENT=true node --max-old-space-size=4096 ./node_modules/.bin/nyc --reporter=lcov --exclude '**/sc_temp/**' --exclude '**/test/**/' -- mocha test/units/* --timeout 100000 --no-warnings --exit",
"test:ci": "SILENT=true node --max-old-space-size=4096 ./node_modules/.bin/nyc --reporter=lcov --exclude '**/sc_temp/**' --exclude '**/test/**/' --exclude 'plugins/resources/matrix.js' -- mocha test/units/* --timeout 100000 --no-warnings --exit",
"test:debug": "node --max-old-space-size=4096 ./node_modules/.bin/mocha test/units/* --timeout 100000 --no-warnings --exit",
"netlify": "./scripts/run-netlify.sh"
},
@ -24,17 +24,20 @@
"author": "",
"license": "ISC",
"dependencies": {
"@solidity-parser/parser": "^0.14.0",
"@ethersproject/abi": "^5.0.9",
"@solidity-parser/parser": "^0.14.1",
"@truffle/provider": "^0.2.24",
"chalk": "^2.4.2",
"death": "^1.1.0",
"detect-port": "^1.3.0",
"difflib": "^0.2.4",
"fs-extra": "^8.1.0",
"ghost-testrpc": "^0.0.2",
"global-modules": "^2.0.0",
"globby": "^10.0.1",
"jsonschema": "^1.2.4",
"lodash": "^4.17.15",
"mocha": "7.1.2",
"node-emoji": "^1.10.0",
"pify": "^4.0.1",
"recursive-readdir": "^2.2.2",
@ -44,9 +47,6 @@
"web3-utils": "^1.3.0"
},
"devDependencies": {
"@nomiclabs/buidler": "^1.3.6",
"@nomiclabs/buidler-truffle5": "^1.3.4",
"@nomiclabs/buidler-web3": "^1.3.4",
"@nomiclabs/hardhat-ethers": "^2.0.4",
"@nomiclabs/hardhat-truffle5": "^2.0.0",
"@nomiclabs/hardhat-waffle": "^2.0.1",
@ -60,10 +60,9 @@
"ganache-cli": "6.12.2",
"hardhat": "^2.9.3",
"hardhat-gas-reporter": "^1.0.1",
"mocha": "5.2.0",
"nyc": "^14.1.1",
"solc": "^0.5.10",
"truffle": "5.0.31",
"solc": "^0.7.5",
"truffle": "5.1.43",
"truffle-config": "^1.1.18"
}
}

@ -1,148 +0,0 @@
const API = require('./../lib/api');
const utils = require('./resources/plugin.utils');
const buidlerUtils = require('./resources/nomiclabs.utils');
const PluginUI = require('./resources/nomiclabs.ui');
const pkg = require('./../package.json');
const death = require('death');
const path = require('path');
const { task, types } = require("@nomiclabs/buidler/config");
const { ensurePluginLoadedWithUsePlugin } = require("@nomiclabs/buidler/plugins");
const {
TASK_TEST,
TASK_COMPILE,
TASK_COMPILE_GET_COMPILER_INPUT
} = require("@nomiclabs/buidler/builtin-tasks/task-names");
ensurePluginLoadedWithUsePlugin();
function plugin() {
// UI for the task flags...
const ui = new PluginUI();
// Unset useLiteralContent due to solc metadata size restriction
task(TASK_COMPILE_GET_COMPILER_INPUT).setAction(async (_, __, runSuper) => {
const input = await runSuper();
input.settings.metadata.useLiteralContent = false;
return input;
})
task("coverage", "Generates a code coverage report for tests")
.addOptionalParam("testfiles", ui.flags.file, "", types.string)
.addOptionalParam("solcoverjs", ui.flags.solcoverjs, "", types.string)
.addOptionalParam('temp', ui.flags.temp, "", types.string)
.setAction(async function(args, env){
let error;
let ui;
let api;
let config;
try {
death(buidlerUtils.finish.bind(null, config, api)); // Catch interrupt signals
config = buidlerUtils.normalizeConfig(env.config, args);
ui = new PluginUI(config.logger.log);
api = new API(utils.loadSolcoverJS(config));
// ==============
// Server launch
// ==============
const network = buidlerUtils.setupBuidlerNetwork(env, api, ui);
const client = api.client || require('ganache-cli');
const address = await api.ganache(client);
const accountsRequest = await utils.getAccountsGanache(api.server.provider);
const nodeInfoRequest = await utils.getNodeInfoGanache(api.server.provider);
const ganacheVersion = nodeInfoRequest.result.split('/')[1];
// Set default account
network.from = accountsRequest.result[0];
// Version Info
ui.report('versions', [
ganacheVersion,
pkg.version
]);
ui.report('ganache-network', [
env.network.name,
api.port
]);
// Run post-launch server hook;
await api.onServerReady(config);
// ================
// Instrumentation
// ================
const skipFiles = api.skipFiles || [];
let {
targets,
skipped
} = utils.assembleFiles(config, skipFiles);
targets = api.instrument(targets);
utils.reportSkipped(config, skipped);
// ==============
// Compilation
// ==============
config.temp = args.temp;
const {
tempArtifactsDir,
tempContractsDir
} = utils.getTempLocations(config);
utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir)
utils.save(targets, config.paths.sources, tempContractsDir);
utils.save(skipped, config.paths.sources, tempContractsDir);
config.paths.sources = tempContractsDir;
config.paths.artifacts = tempArtifactsDir;
config.paths.cache = buidlerUtils.tempCacheDir(config);
config.solc.optimizer.enabled = false;
await env.run(TASK_COMPILE);
await api.onCompileComplete(config);
// ======
// Tests
// ======
const testfiles = args.testfiles
? buidlerUtils.getTestFilePaths(args.testfiles)
: [];
try {
await env.run(TASK_TEST, {testFiles: testfiles})
} catch (e) {
error = e;
}
await api.onTestsComplete(config);
// ========
// Istanbul
// ========
await api.report();
await api.onIstanbulComplete(config);
} catch(e) {
error = e;
}
await buidlerUtils.finish(config, api);
if (error !== undefined ) throw error;
if (process.exitCode > 0) throw new Error(ui.generate('tests-fail', [process.exitCode]));
})
}
module.exports = plugin;

@ -9,6 +9,7 @@ const {
TASK_COMPILE,
TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT,
TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE,
TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS
} = require("hardhat/builtin-tasks/task-names");
// Toggled true for `coverage` task only.
@ -75,6 +76,18 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE).setAction(async (_,
return compilationJob;
});
// Suppress compilation warnings because injected trace function triggers
// complaint about unused variable
subtask(TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS).setAction(async (_, __, runSuper) => {
const defaultWarn = console.warn;
if (measureCoverage) {
console.warn = () => {};
}
await runSuper();
console.warn = defaultWarn;
});
/**
* Coverage task implementation
* @param {HardhatUserArgs} args
@ -84,6 +97,8 @@ task("coverage", "Generates a code coverage report for tests")
.addOptionalParam("testfiles", ui.flags.file, "", types.string)
.addOptionalParam("solcoverjs", ui.flags.solcoverjs, "", types.string)
.addOptionalParam('temp', ui.flags.temp, "", types.string)
.addFlag('matrix', ui.flags.testMatrix)
.addFlag('abi', ui.flags.abi)
.setAction(async function(args, env){
const API = require('./../lib/api');
@ -127,6 +142,16 @@ task("coverage", "Generates a code coverage report for tests")
}
env.hardhatArguments = Object.assign(env.hardhatArguments, flags)
// ===========================
// Generate abi diff component
// (This flag only useful within codecheck context)
// ===========================
if (args.abi){
measureCoverage = false;
await nomiclabsUtils.generateHumanReadableAbiList(env, api, TASK_COMPILE);
return;
}
// ================
// Instrumentation
// ================
@ -219,6 +244,9 @@ task("coverage", "Generates a code coverage report for tests")
? nomiclabsUtils.getTestFilePaths(args.testfiles)
: [];
// Optionally collect tests-per-line-of-code data
nomiclabsUtils.collectTestMatrixData(args, env, api);
try {
failedTests = await env.run(TASK_TEST, {testFiles: testfiles})
} catch (e) {
@ -226,10 +254,13 @@ task("coverage", "Generates a code coverage report for tests")
}
await api.onTestsComplete(config);
// ========
// Istanbul
// ========
await api.report();
// =================================
// Output (Istanbul or Test Matrix)
// =================================
(args.matrix)
? await api.saveTestMatrix()
: await api.report();
await api.onIstanbulComplete(config);
} catch(e) {

@ -1,6 +1 @@
if (global && global.__hardhatContext){
require("./hardhat.plugin");
return;
}
module.exports = require('./buidler.plugin')
require("./hardhat.plugin");

@ -0,0 +1,145 @@
const mocha = require("mocha");
const inherits = require("util").inherits;
const Spec = mocha.reporters.Spec;
const path = require('path');
/**
* This file adapted from mocha's stats-collector
* https://github.com/mochajs/mocha/blob/54475eb4ca35a2c9044a1b8c59a60f09c73e6c01/lib/stats-collector.js#L1-L83
*/
const Date = global.Date;
/**
* Provides stats such as test duration, number of tests passed / failed etc., by
* listening for events emitted by `runner`.
*/
function mochaStats(runner) {
const stats = {
suites: 0,
tests: 0,
passes: 0,
pending: 0,
failures: 0
};
if (!runner) throw new Error("Missing runner argument");
runner.stats = stats;
runner.on("pass", () => stats.passes++);
runner.on("fail", () => stats.failures++);
runner.on("pending", () => stats.pending++);
runner.on("test end", () => stats.tests++);
runner.once("start", () => (stats.start = new Date()));
runner.once("end", function() {
stats.end = new Date();
stats.duration = stats.end - stats.start;
});
}
/**
* Based on the Mocha 'Spec' reporter.
*
* Watches an Ethereum test suite run and collects data about which tests hit
* which lines of code. This "test matrix" can be used as an input to fault localization tools
* like: https://github.com/JoranHonig/tarantula
*
* Mocha's JSON reporter output is also generated and saved to a separate file
*
* @param {Object} runner mocha's runner
* @param {Object} options reporter.options (see README example usage)
*/
function Matrix(runner, options) {
// Spec reporter
Spec.call(this, runner, options);
const self = this;
const tests = [];
const failures = [];
const passes = [];
// Initialize stats for Mocha 6+ epilogue
if (!runner.stats) {
mochaStats(runner);
this.stats = runner.stats;
}
runner.on("test end", (info) => {
options.reporterOptions.collectTestMatrixData(info);
tests.push(info);
});
runner.on('pass', function(info) {
passes.push(info)
})
runner.on('fail', function(info) {
failures.push(info)
});
runner.once('end', function() {
delete self.stats.start;
delete self.stats.end;
delete self.stats.duration;
var obj = {
stats: self.stats,
tests: tests.map(clean),
failures: failures.map(clean),
passes: passes.map(clean)
};
runner.testResults = obj;
options.reporterOptions.saveMochaJsonOutput(obj)
});
// >>>>>>>>>>>>>>>>>>>>>>>>>
// Mocha JSON Reporter Utils
// Code taken from:
// https://mochajs.org/api/reporters_json.js.html
// >>>>>>>>>>>>>>>>>>>>>>>>>
function clean(info) {
var err = info.err || {};
if (err instanceof Error) {
err = errorJSON(err);
}
return {
title: info.title,
fullTitle: info.fullTitle(),
file: path.relative(options.reporterOptions.cwd, info.file),
currentRetry: info.currentRetry(),
err: cleanCycles(err)
};
}
function cleanCycles(obj) {
var cache = [];
return JSON.parse(
JSON.stringify(obj, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// Instead of going in a circle, we'll print [object Object]
return '' + value;
}
cache.push(value);
}
return value;
})
);
}
function errorJSON(err) {
var res = {};
Object.getOwnPropertyNames(err).forEach(function(key) {
res[key] = err[key];
}, err);
return res;
}
}
/**
* Inherit from `Base.prototype`.
*/
inherits(Matrix, Spec);
module.exports = Matrix;

@ -8,7 +8,12 @@ class PluginUI extends UI {
super(log);
this.flags = {
file: `Path (or glob) defining a subset of tests to run`,
testfiles: `Path (or glob) defining a subset of tests to run`,
testMatrix: `Generate a json object which maps which unit tests hit which lines of code.`,
abi: `Generate a json object which can be used to produce a unified diff of your ` +
`contracts public interface between two commits.`,
solcoverjs: `Relative path from working directory to config. ` +
`Useful for monorepo packages that share settings.`,
@ -16,7 +21,6 @@ class PluginUI extends UI {
temp: `Path to a disposable folder to store compilation artifacts in. ` +
`Useful when your test setup scripts include hard-coded paths to ` +
`a build directory.`,
}
}

@ -37,6 +37,7 @@ function normalizeConfig(config, args={}){
config.logger = config.logger ? config.logger : {log: null};
config.solcoverjs = args.solcoverjs
config.gasReporter = { enabled: false }
config.matrix = args.matrix;
try {
const hardhatPackage = require('hardhat/package.json');
@ -165,6 +166,52 @@ function configureHttpProvider(networkConfig, api, ui){
networkConfig.url = `http://${api.host}:${api.port}`;
}
/**
* Configures mocha to generate a json object which maps which tests
* hit which lines of code.
*/
function collectTestMatrixData(args, env, api){
if (args.matrix){
mochaConfig = env.config.mocha || {};
mochaConfig.reporter = api.matrixReporterPath;
mochaConfig.reporterOptions = {
collectTestMatrixData: api.collectTestMatrixData.bind(api),
saveMochaJsonOutput: api.saveMochaJsonOutput.bind(api),
cwd: api.cwd
}
env.config.mocha = mochaConfig;
}
}
/**
* Returns all Hardhat artifacts.
* @param {HRE} env
* @return {Artifact[]}
*/
async function getAllArtifacts(env){
const all = [];
const qualifiedNames = await env.artifacts.getArtifactPaths();
for (const name of qualifiedNames){
all.push(require(name));
}
return all;
}
/**
* Compiles project
* Collects all artifacts from Hardhat project,
* Converts them to a format that can be consumed by api.abiUtils.diff
* Saves them to `api.abiOutputPath`
* @param {HRE} env
* @param {SolidityCoverageAPI} api
*/
async function generateHumanReadableAbiList(env, api, TASK_COMPILE){
await env.run(TASK_COMPILE);
const _artifacts = await getAllArtifacts(env);
const list = api.abiUtils.generateHumanReadableAbiList(_artifacts)
api.saveHumanReadableAbis(list);
}
/**
* Sets the default `from` account field in the network that will be used.
* This needs to be done after accounts are fetched from the launched client.
@ -216,6 +263,9 @@ module.exports = {
setupBuidlerNetwork,
setupHardhatNetwork,
getTestFilePaths,
setNetworkFrom
setNetworkFrom,
collectTestMatrixData,
getAllArtifacts,
generateHumanReadableAbiList
}

@ -41,7 +41,6 @@ class PluginUI extends UI {
'lib-local': `\n${ct} ${c.grey('Using Truffle library from local node_modules.')}\n`,
'lib-global': `\n${ct} ${c.grey('Using Truffle library from global node_modules.')}\n`,
'help': `Usage: truffle run coverage [options]\n\n` +
`Options:\n` +
` --file: path (or glob) to subset of JS test files. (Quote your globs)\n` +

@ -35,6 +35,39 @@ async function getTestFilePaths(config){
return target.filter(f => f.match(testregex) != null);
}
/**
* Returns all Truffle artifacts.
* @param {TruffleConfig} config
* @return {Artifact[]}
*/
function getAllArtifacts(config){
const all = [];
const artifactsGlob = path.join(config.artifactsDir, '/**/*.json');
const files = globby.sync([artifactsGlob])
for (const file of files){
const candidate = require(file);
if (candidate.contractName && candidate.abi){
all.push(candidate);
}
}
return all;
}
/**
* Compiles project
* Collects all artifacts from Truffle project,
* Converts them to a format that can be consumed by api.abiUtils.diff
* Saves them to `api.abiOutputPath`
* @param {TruffleConfig} config
* @param {TruffleAPI} truffle
* @param {SolidityCoverageAPI} api
*/
async function generateHumanReadableAbiList(config, truffle, api){
await truffle.contracts.compile(config);
const _artifacts = getAllArtifacts(config);
const list = api.abiUtils.generateHumanReadableAbiList(_artifacts)
api.saveHumanReadableAbis(list);
}
/**
* Configures the network. Runs before the server is launched.
@ -196,10 +229,47 @@ function normalizeConfig(config){
return config;
}
/**
* Configures mocha to generate a json object which maps which tests
* hit which lines of code.
*/
function collectTestMatrixData(config, api){
if (config.matrix){
config.mocha = config.mocha || {};
config.mocha.reporter = api.matrixReporterPath;
config.mocha.reporterOptions = {
collectTestMatrixData: api.collectTestMatrixData.bind(api),
saveMochaJsonOutput: api.saveMochaJsonOutput.bind(api),
cwd: api.cwd
}
}
}
/**
* Replacement logger which filters out compilation warnings triggered by injected trace
* function definitions.
*
* @type {Object}
*/
const filteredLogger = {
log: (val) => {
const loggable = typeof val === 'string' &&
!val.includes('Warning:') && // solc msg grep
!process.env.SILENT; // unit tests
loggable && console.log(val);
},
warn: console.warn,
error: console.error
}
module.exports = {
getTestFilePaths,
setNetwork,
setNetworkFrom,
loadLibrary,
normalizeConfig,
filteredLogger,
collectTestMatrixData,
generateHumanReadableAbiList
}

@ -30,9 +30,18 @@ async function plugin(config){
truffle = truffleUtils.loadLibrary(config);
api = new API(utils.loadSolcoverJS(config));
truffleUtils.setNetwork(config, api);
// ===========================
// Generate abi diff component
// (This flag only useful within codecheck context)
// ===========================
if (config.abi){
await truffleUtils.generateHumanReadableAbiList(config, truffle, api);
return;
}
// Server launch
truffleUtils.setNetwork(config, api);
const client = api.client || truffle.ganache;
const address = await api.ganache(client);
const accountsRequest = await utils.getAccountsGanache(api.server.provider);
@ -90,7 +99,14 @@ async function plugin(config){
path.basename(config.contracts_build_directory)
);
// Filter compilation warnings
const defaultLogger = config.logger;
if (!config.verbose){
config.logger = truffleUtils.filteredLogger;
}
config.all = true;
config.strict = false;
config.compilers.solc.settings.optimizer.enabled = false;
// Run pre-compile hook;
@ -98,9 +114,12 @@ async function plugin(config){
// Compile Instrumented Contracts
await truffle.contracts.compile(config);
config.logger = defaultLogger;
await api.onCompileComplete(config);
config.test_files = await truffleUtils.getTestFilePaths(config);
truffleUtils.collectTestMatrixData(config, api);
// Run tests
try {
failures = await truffle.test.run(config)
@ -109,8 +128,13 @@ async function plugin(config){
}
await api.onTestsComplete(config);
// Run Istanbul
await api.report();
// =================================
// Output (Istanbul or Test Matrix)
// =================================
(config.matrix)
? await api.saveTestMatrix()
: await api.report();
await api.onIstanbulComplete(config);
} catch(e){

@ -53,3 +53,13 @@ if [ ! -d "coverage" ]; then
exit 1
fi
npx truffle run coverage --matrix
# Test that coverage/ was generated
if [ ! -f "testMatrix.json" ]; then
echo "ERROR: no matrix file was created."
exit 1
fi
cat testMatrix.json

@ -13,6 +13,13 @@ function verifyCoverageExists {
fi
}
function verifyMatrixExists {
if [ ! -f "testMatrix.json" ]; then
echo "ERROR: no matrix file was created."
exit 1
fi
}
# Get rid of any caches
sudo rm -rf node_modules
echo "NVM CURRENT >>>>>" && nvm current
@ -47,6 +54,12 @@ npx hardhat coverage
verifyCoverageExists
npx hardhat coverage --matrix
verifyMatrixExists
cat testMatrix.json
echo ""
echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
echo "wighawag/hardhat-deploy "
@ -68,22 +81,3 @@ yarn run coverage
yarn run gas
verifyCoverageExists
# Install buidler-ethers
echo ""
echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
echo "Simple buidler/buidler-ethers "
echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
echo ""
cd ..
git clone https://github.com/sc-forks/example-buidler-ethers.git
cd example-buidler-ethers
yarn
# Install and run solidity-coverage @ PR
yarn add $PR_PATH --dev
cat package.json
npx buidler coverage
verifyCoverageExists

@ -1,5 +1,5 @@
// This contract should throw a parse error in instrumentSolidity.js
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract SimpleError {
uint x = 0;

@ -1,4 +1,4 @@
pragma solidity >=0.4.22 <0.6.0;
pragma solidity >=0.4.22 <0.8.0;
contract Migrations {

@ -1,9 +1,8 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
import "./../B/ContractB2.sol";
contract ContractA is ContractB {
uint x;
constructor() public {
}

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractB {

@ -8,7 +8,7 @@ module.exports={
}
},
solidity: {
version: "0.5.15"
version: "0.7.3"
},
paths: {
sources: './contracts/A'

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractA {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractB {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractC {

@ -3,7 +3,7 @@ require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = {
solidity: {
version: "0.5.15"
version: "0.7.3"
},
networks: {
coverage: {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.5.5;
contract ContractA {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractA {

@ -4,7 +4,7 @@ require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = {
solidity: {
version: "0.5.15"
version: "0.7.3"
},
logger: process.env.SILENT ? { log: () => {} } : console,
};

@ -1,4 +1,4 @@
pragma solidity >=0.4.21 <0.6.0;
pragma solidity >=0.4.21 <0.8.0;
contract RelativePathImport {
uint r;

@ -1,23 +0,0 @@
pragma solidity >=0.4.21 <0.6.0;
contract Migrations {
address public owner;
uint public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

@ -1,3 +1,3 @@
pragma solidity >=0.4.21 <0.6.0;
pragma solidity >=0.4.21 <0.8.0;
import "package/AnotherImport.sol";

@ -1,4 +1,4 @@
pragma solidity >=0.4.21 <0.6.0;
pragma solidity >=0.4.21 <0.8.0;
import "../assets/RelativePathImport.sol";
import "package/NodeModulesImport.sol";

@ -3,7 +3,7 @@ require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = {
solidity: {
version: "0.5.15"
version: "0.7.3"
},
logger: process.env.SILENT ? { log: () => {} } : console,
};

@ -1,5 +0,0 @@
const Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};

@ -1,4 +1,4 @@
pragma solidity >=0.4.21 <0.6.0;
pragma solidity >=0.4.21 <0.8.0;
contract AnotherImport {
uint x;

@ -1,4 +1,4 @@
pragma solidity >=0.4.21 <0.6.0;
pragma solidity >=0.4.21 <0.8.0;
contract NodeModulesImport {
uint x;

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
library CLibrary {
uint constant x = 1;

@ -1,4 +1,4 @@
pragma solidity >=0.4.21 <0.6.0;
pragma solidity >=0.4.21 <0.8.0;
contract Migrations {
address public owner;

@ -1,14 +1,10 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract PureView {
// Make sure we aren't corrupting anything with the replace
uint notpureview = 5;
// Abstract functions to inherit from an uninstrumented, imported file.
function bePure(uint a, uint b) public pure returns (uint);
function beView() public view returns (uint);
function inheritedPure(uint a, uint b) public pure returns(uint){
return a + b;
}

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
import "./_Interface.sol";
import "./PureView.sol";
@ -45,11 +45,11 @@ contract UsesPure is PureView, _Interface {
return onehundred;
}
function stare(uint a, uint b) external {
function stare(uint a, uint b) external override {
uint z = a + b;
}
function cry() external {
function cry() external override {
}
}

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
interface _Interface {
function stare(uint a, uint b) external;

@ -3,7 +3,7 @@ require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = {
solidity: {
version: "0.5.15"
version: "0.7.3"
},
logger: process.env.SILENT ? { log: () => {} } : console,
};

@ -0,0 +1,17 @@
// Testing hooks
const fn = (msg, config) => config.logger.log(msg);
const reporterPath = (process.env.TRUFFLE_TEST)
? "./plugins/resources/matrix.js"
: "../plugins/resources/matrix.js";
module.exports = {
// This is loaded directly from `./plugins` during unit tests. The default val is
// "solidity-coverage/plugins/resources/matrix.js"
matrixReporterPath: reporterPath,
matrixOutputPath: "alternateTestMatrix.json",
mochaJsonOutputPath: "alternateMochaOutput.json",
skipFiles: ['Migrations.sol'],
silent: process.env.SILENT ? true : false,
istanbulReporter: ['json-summary', 'text'],
}

@ -0,0 +1,21 @@
pragma solidity ^0.7.0;
contract MatrixA {
uint x;
constructor() public {
}
function sendFn() public {
x = 5;
}
function callFn() public pure returns (uint){
uint y = 5;
return y;
}
function unhit() public {
uint z = 7;
}
}

@ -0,0 +1,17 @@
pragma solidity ^0.7.0;
contract MatrixB {
uint x;
constructor() public {
}
function sendFn() public {
x = 5;
}
function callFn() public pure returns (uint){
uint y = 5;
return y;
}
}

@ -0,0 +1,99 @@
{
"stats": {
"suites": 2,
"tests": 6,
"passes": 6,
"pending": 0,
"failures": 0
},
"tests": [
{
"title": "sends to A",
"fullTitle": "Contract: Matrix A and B sends to A",
"file": "test/matrix_a_b.js",
"currentRetry": 0,
"err": {}
},
{
"title": "sends to A",
"fullTitle": "Contract: Matrix A and B sends to A",
"file": "test/matrix_a_b.js",
"currentRetry": 0,
"err": {}
},
{
"title": "calls B",
"fullTitle": "Contract: Matrix A and B calls B",
"file": "test/matrix_a_b.js",
"currentRetry": 0,
"err": {}
},
{
"title": "sends to B",
"fullTitle": "Contract: Matrix A and B sends to B",
"file": "test/matrix_a_b.js",
"currentRetry": 0,
"err": {}
},
{
"title": "sends",
"fullTitle": "Contract: MatrixA sends",
"file": "test/matrix_a.js",
"currentRetry": 0,
"err": {}
},
{
"title": "calls",
"fullTitle": "Contract: MatrixA calls",
"file": "test/matrix_a.js",
"currentRetry": 0,
"err": {}
}
],
"failures": [],
"passes": [
{
"title": "sends to A",
"fullTitle": "Contract: Matrix A and B sends to A",
"file": "test/matrix_a_b.js",
"currentRetry": 0,
"err": {}
},
{
"title": "sends to A",
"fullTitle": "Contract: Matrix A and B sends to A",
"file": "test/matrix_a_b.js",
"currentRetry": 0,
"err": {}
},
{
"title": "calls B",
"fullTitle": "Contract: Matrix A and B calls B",
"file": "test/matrix_a_b.js",
"currentRetry": 0,
"err": {}
},
{
"title": "sends to B",
"fullTitle": "Contract: Matrix A and B sends to B",
"file": "test/matrix_a_b.js",
"currentRetry": 0,
"err": {}
},
{
"title": "sends",
"fullTitle": "Contract: MatrixA sends",
"file": "test/matrix_a.js",
"currentRetry": 0,
"err": {}
},
{
"title": "calls",
"fullTitle": "Contract: MatrixA calls",
"file": "test/matrix_a.js",
"currentRetry": 0,
"err": {}
}
]
}

@ -0,0 +1,48 @@
{
"contracts/MatrixA.sol": {
"10": [
{
"title": "sends to A",
"file": "test/matrix_a_b.js"
},
{
"title": "sends",
"file": "test/matrix_a.js"
}
],
"14": [
{
"title": "calls",
"file": "test/matrix_a.js"
}
],
"15": [
{
"title": "calls",
"file": "test/matrix_a.js"
}
],
"19": []
},
"contracts/MatrixB.sol": {
"10": [
{
"title": "sends to B",
"file": "test/matrix_a_b.js"
}
],
"14": [
{
"title": "calls B",
"file": "test/matrix_a_b.js"
}
],
"15": [
{
"title": "calls B",
"file": "test/matrix_a_b.js"
}
]
}
}

@ -0,0 +1,46 @@
{
"contracts/MatrixA.sol": {
"10": [
{
"title": "sends",
"file": "test/matrix_a.js"
},
{
"title": "sends to A",
"file": "test/matrix_a_b.js"
}
],
"14": [
{
"title": "calls",
"file": "test/matrix_a.js"
}
],
"15": [
{
"title": "calls",
"file": "test/matrix_a.js"
}
]
},
"contracts/MatrixB.sol": {
"10": [
{
"title": "sends to B",
"file": "test/matrix_a_b.js"
}
],
"14": [
{
"title": "calls B",
"file": "test/matrix_a_b.js"
}
],
"15": [
{
"title": "calls B",
"file": "test/matrix_a_b.js"
}
]
}
}

@ -0,0 +1,9 @@
require("@nomiclabs/hardhat-truffle5");
require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports={
solidity: {
version: "0.7.3"
},
logger: process.env.SILENT ? { log: () => {} } : console,
};

@ -0,0 +1,15 @@
const MatrixA = artifacts.require("MatrixA");
contract("MatrixA", function(accounts) {
let instance;
before(async () => instance = await MatrixA.new())
it('sends', async function(){
await instance.sendFn();
});
it('calls', async function(){
await instance.callFn();
})
});

@ -0,0 +1,30 @@
const MatrixA = artifacts.require("MatrixA");
const MatrixB = artifacts.require("MatrixB");
contract("Matrix A and B", function(accounts) {
let instanceA;
let instanceB;
before(async () => {
instanceA = await MatrixA.new();
instanceB = await MatrixB.new();
})
it('sends to A', async function(){
await instanceA.sendFn();
});
// Duplicate test title and file should *not* be duplicated in the output
it('sends to A', async function(){
await instanceA.sendFn();
})
it('calls B', async function(){
await instanceB.callFn();
})
it('sends to B', async function(){
await instanceB.sendFn();
});
});

@ -0,0 +1,10 @@
module.exports = {
networks: {},
mocha: {},
compilers: {
solc: {
version: "0.7.3"
}
},
logger: process.env.SILENT ? { log: () => {} } : console,
}

@ -0,0 +1,8 @@
// Testing hooks
const fn = (msg, config) => config.logger.log(msg);
module.exports = {
skipFiles: ['Migrations.sol'],
silent: process.env.SILENT ? true : false,
istanbulReporter: ['json-summary', 'text'],
}

@ -0,0 +1,45 @@
pragma solidity ^0.6.0;
import "./ModifiersB.sol";
/**
* New syntaxes in solc 0.6.x
*/
contract ModifiersA is ModifiersB {
uint counter;
bool flag = true;
modifier flippable {
require(flag);
_;
}
modifier overridden() override {
require(true);
_;
}
function flip() public {
flag = !flag;
}
function simpleSet(uint i)
public
override(ModifiersB)
{
counter = counter + i;
}
function simpleView(uint i)
view
overridden
external
returns (uint, bool)
{
return (counter + i, true);
}
function simpleSetFlip(uint i) flippable public {
counter = counter + i;
}
}

@ -0,0 +1,19 @@
pragma solidity ^0.6.0;
contract ModifiersB {
uint value;
uint b;
constructor() public {
}
modifier overridden() virtual {
require(true);
_;
}
function simpleSet(uint i) public virtual {
value = 5;
}
}

@ -0,0 +1,43 @@
pragma solidity ^0.6.0;
import "./ModifiersB.sol";
/**
* New syntaxes in solc 0.6.x
*/
contract ModifiersC {
uint counter;
address owner;
bool flag = true;
constructor() public {
owner = msg.sender;
}
modifier flippable {
require(flag);
_;
}
function flip() public {
flag = !flag;
}
function simpleSetFlip(uint i) flippable public {
counter = counter + i;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
function set(uint i)
onlyOwner
public
payable
virtual
{
counter = counter + i;
}
}

@ -0,0 +1,9 @@
require("@nomiclabs/hardhat-truffle5");
require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = {
solidity: {
version: "0.6.7"
},
logger: process.env.SILENT ? { log: () => {} } : console,
};

@ -0,0 +1,40 @@
const ModifiersA = artifacts.require("ModifiersA");
const ModifiersC = artifacts.require("ModifiersC");
contract("Modifiers", function(accounts) {
let instance;
before(async () => {
A = await ModifiersA.new();
C = await ModifiersC.new();
})
it('simpleSet (overridden method)', async function(){
await A.simpleSet(5);
});
it('simpleView (overridden modifier)', async function(){
await A.simpleView(5);
});
it('simpleSetFlip (both branches)', async function(){
await A.simpleSetFlip(5);
await A.flip();
try {
await A.simpleSetFlip(5);
} catch (e) {
/* ignore */
}
});
it('simpleSetFlip (false branch + other file)', async function(){
await C.flip();
try {
await C.simpleSetFlip(5);
} catch (e) {
/* ignore */
}
});
});

@ -0,0 +1,9 @@
module.exports = {
networks: {},
mocha: {},
compilers: {
solc: {
version: "0.6.2"
}
}
}

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractA {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractB {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractC {

@ -1,4 +1,4 @@
pragma solidity >=0.4.21 <0.6.0;
pragma solidity >=0.4.21 <0.8.0;
contract Migrations {
address public owner;

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractA {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractB {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractC {

@ -1,23 +0,0 @@
pragma solidity >=0.4.21 <0.6.0;
contract Migrations {
address public owner;
uint public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

@ -8,7 +8,7 @@ module.exports={
}
},
solidity: {
version: "0.5.15"
version: "0.7.3"
},
logger: process.env.SILENT ? { log: () => {} } : console,
};

@ -2,6 +2,8 @@ module.exports = {
networks: {},
mocha: {},
compilers: {
solc: {}
solc: {
version: "0.7.3"
}
}
}

@ -3,7 +3,7 @@ require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = {
solidity: {
version: "0.5.15"
version: "0.7.3"
},
logger: process.env.SILENT ? { log: () => {} } : console,
};

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractA {

@ -1,23 +0,0 @@
pragma solidity >=0.4.21 <0.6.0;
contract Migrations {
address public owner;
uint public last_completed_migration;
constructor() public {
owner = msg.sender;
}
modifier restricted() {
if (msg.sender == owner) _;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractB {

@ -3,7 +3,7 @@ require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = {
solidity: {
version: "0.5.15"
version: "0.7.3"
},
logger: process.env.SILENT ? { log: () => {} } : console,
};

@ -1,5 +0,0 @@
const Migrations = artifacts.require("Migrations");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};

@ -0,0 +1,8 @@
// Testing hooks
const fn = (msg, config) => config.logger.log(msg);
module.exports = {
skipFiles: ['Migrations.sol'],
silent: process.env.SILENT ? true : false,
istanbulReporter: ['json-summary', 'text', 'html'],
}

@ -0,0 +1,46 @@
pragma solidity ^0.7.0;
contract Contract_OR {
function _if(uint i) public pure {
if (i == 0 || i > 5){
/* ignore */
}
}
function _if_and(uint i) public pure {
if (i != 0 && (i < 2 || i > 5)){
/* ignore */
}
}
function _return(uint i) public pure returns (bool){
return (i != 0 && i != 1 ) ||
((i + 1) == 2);
}
function _while(uint i) public pure returns (bool){
uint counter;
while( (i == 1 || i == 2) && counter < 2 ){
counter++;
}
}
function _require(uint x) public {
require(x == 1 || x == 2);
}
function _require_multi_line(uint x) public {
require(
(x == 1 || x == 2) ||
x == 3
);
}
function _if_neither(uint i) public {
if (i == 1 || i == 2){
/* ignore */
}
}
}

@ -0,0 +1,54 @@
pragma solidity ^0.7.0;
contract Contract_ternary {
// Sameline consequent
function a() public {
bool x = true;
bool y = true;
x && y ? y = false : y = false;
}
// Multiline consequent
function b() public {
bool x = false;
bool y = false;
(x)
? y = false
: y = false;
}
// Sameline w/ logicalOR
function c() public {
bool x = false;
bool y = true;
(x || y) ? y = false : y = false;
}
// Multiline w/ logicalOR
function d() public {
bool x = false;
bool y = true;
(x || y)
? y = false
: y = false;
}
// Sameline alternate
function e() public {
bool x = false;
bool y = false;
(x) ? y = false : y = false;
}
// Multiline w/ logicalOR (both false)
function f() public {
bool x = false;
bool y = false;
(x || y)
? y = false
: y = false;
}
}

@ -0,0 +1,9 @@
require("@nomiclabs/hardhat-truffle5");
require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = {
solidity: {
version: "0.7.3"
},
logger: process.env.SILENT ? { log: () => {} } : console,
};

@ -0,0 +1,37 @@
const Contract_OR = artifacts.require("Contract_OR");
contract("contract_or", function(accounts) {
let instance;
before(async () => instance = await Contract_OR.new())
it('_if', async function(){
await instance._if(0);
await instance._if(7);
});
it('_if_and', async function(){
await instance._if_and(1);
});
it('_return', async function(){
await instance._return(4);
});
it('_while', async function(){
await instance._while(1);
});
it('_require', async function(){
await instance._require(2);
})
it('_require_multi_line', async function(){
await instance._require_multi_line(1);
await instance._require_multi_line(3);
})
it('_if_neither', async function(){
await instance._if_neither(3);
})
});

@ -0,0 +1,16 @@
const Contract_ternary = artifacts.require("Contract_ternary");
contract("contract_ternary", function(accounts) {
let instance;
before(async () => instance = await Contract_ternary.new())
it('misc ternary conditionals', async function(){
await instance.a();
await instance.b();
await instance.c();
await instance.d();
await instance.e();
await instance.f();
});
});

@ -0,0 +1,7 @@
module.exports = {
networks: {},
mocha: {},
compilers: {
solc: {}
}
}

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractA {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractB {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractC {

@ -1,4 +1,4 @@
pragma solidity >=0.4.21 <0.6.0;
pragma solidity >=0.4.21 <0.8.0;
contract Migrations {
address public owner;

@ -3,7 +3,7 @@ require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = {
solidity: {
version: "0.5.15"
version: "0.7.3"
},
logger: process.env.SILENT ? { log: () => {} } : console,
};

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractA {

@ -1,4 +1,4 @@
pragma solidity ^0.5.0;
pragma solidity ^0.7.0;
contract ContractB {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save