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/> | | 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])| | 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) | | 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] | | 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] [<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. | | 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] | | measureStatementCoverage | *boolean* | `true` | Computes statement (in addition to line) coverage. [More...][34] |
| measureFunctionCoverage | *boolean* | `true` | Computes function 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. | | istanbulFolder | *String* | `./coverage` | Folder location for Istanbul coverage reports. |
| istanbulReporter | *Array* | `['html', 'lcov', 'text', 'json']` | [Istanbul coverage reporters][2] | | 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.| | 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/ [36]: https://hardhat.org/
[37]: https://github.com/sc-forks/solidity-coverage/blob/master/HARDHAT_README.md [37]: https://github.com/sc-forks/solidity-coverage/blob/master/HARDHAT_README.md
[38]: https://github.com/sindresorhus/globby#globbing-patterns [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 [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 [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 improve performance, lower the cost of execution and minimize complications that arise from `solc`'s
limits on how large the compilation payload can be. 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 Coverage = require('./coverage');
const DataCollector = require('./collector'); const DataCollector = require('./collector');
const AppUI = require('./ui').AppUI; const AppUI = require('./ui').AppUI;
const AbiUtils = require('./abi');
/** /**
* Coverage Runner * Coverage Runner
*/ */
class API { class API {
constructor(config={}) { constructor(config={}) {
this.validator = new ConfigValidator() this.validator = new ConfigValidator();
this.abiUtils = new AbiUtils();
this.config = config || {}; this.config = config || {};
this.testMatrix = {};
// Validate // Validate
this.validator.validate(this.config); this.validator.validate(this.config);
@ -30,6 +33,10 @@ class API {
this.testsErrored = false; this.testsErrored = false;
this.cwd = config.cwd || process.cwd(); 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.defaultHook = () => {};
this.onServerReady = config.onServerReady || 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 // File I/O
// ======== // ========
@ -305,6 +360,22 @@ class API {
fs.writeFileSync(covPath, JSON.stringify(data)); 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 // 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 * 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. * @param {String} hash data hash from evm stack.
* @return {String} 0x prefixed hash of length 66. * @return {String} 0x prefixed hash of length 66.
*/ */
_normalizeHash(hash){ _normalizeHash(hash){
if (hash.length < 66 && hash.length > 59){ if (hash.length < 18 && hash.length > 11){
hash = hash.slice(2); hash = hash.slice(2);
while(hash.length < 64) hash = '0' + hash; while(hash.length < 16) hash = '0' + hash;
hash = '0x' + hash hash = '0x' + hash
} }
return hash; return hash;

@ -69,6 +69,9 @@ class Coverage {
const data = collectedData[hash]; const data = collectedData[hash];
const contractPath = collectedData[hash].contractPath; const contractPath = collectedData[hash].contractPath;
const id = collectedData[hash].id; 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; const hits = collectedData[hash].hits;
switch(collectedData[hash].type){ switch(collectedData[hash].type){
@ -76,8 +79,10 @@ class Coverage {
case 'function': this.data[contractPath].f[id] = hits; break; case 'function': this.data[contractPath].f[id] = hits; break;
case 'statement': this.data[contractPath].s[id] = hits; break; case 'statement': this.data[contractPath].s[id] = hits; break;
case 'branch': this.data[contractPath].b[id][data.locationIdx] = hits; break; case 'branch': this.data[contractPath].b[id][data.locationIdx] = hits; break;
case 'requirePre': this.requireData[contractPath][id].preEvents = hits; break; case 'and-true': this.data[contractPath].b[id][data.locationIdx] = hits; break;
case 'requirePost': this.requireData[contractPath][id].postEvents = 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 { class Injector {
constructor(){ constructor(){
this.hashCounter = 0; this.hashCounter = 0;
this.modifierCounter = 0;
this.modifiers = {};
} }
_split(contract, injectionPoint){ _split(contract, injectionPoint){
@ -13,16 +15,40 @@ class Injector {
} }
_getInjectable(id, hash, type){ _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) { _getHash(id) {
this.hashCounter++; 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){ _getModifierIdentifier(id){
return `c_${web3Utils.keccak256(id).slice(0,10)}` return `c_mod${web3Utils.keccak256(id).slice(2,10)}`
} }
_getInjectionComponents(contract, injectionPoint, id, type){ _getInjectionComponents(contract, injectionPoint, id, type){
@ -44,10 +70,10 @@ class Injector {
* @param {String} id * @param {String} id
* @return {String} * @return {String}
*/ */
_getHashMethodDefinition(id, contract){ _getDefaultMethodDefinition(id){
const hash = web3Utils.keccak256(id).slice(0,10); const hash = web3Utils.keccak256(id).slice(2,10);
const method = this._getMethodIdentifier(id); const method = this._getDefaultMethodIdentifier(id);
return `\nfunction ${method}(bytes32 c__${hash}) internal pure {}\n`; return `\nfunction ${method}(bytes8 c__${hash}) internal pure {}\n`;
} }
/** /**
@ -57,9 +83,100 @@ class Injector {
* @return {String} * @return {String}
*/ */
_getFileScopedHashMethodDefinition(id, contract){ _getFileScopedHashMethodDefinition(id, contract){
const hash = web3Utils.keccak256(id).slice(0,10); const hash = web3Utils.keccak256(id).slice(2,10);
const method = this._getMethodIdentifier(id); const method = this._getDefaultMethodIdentifier(id);
return `\nfunction ${method}(bytes32 c__${hash}) pure {}\n`; 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){ injectLine(contract, fileName, injectionPoint, injection, instrumentation){
@ -217,9 +334,104 @@ class Injector {
const end = contract.instrumented.slice(injectionPoint); const end = contract.instrumented.slice(injectionPoint);
const id = `${fileName}:${injection.contractName}`; const id = `${fileName}:${injection.contractName}`;
contract.instrumented = (injection.isFileScoped) const methodDefinition = (injection.isFileScoped)
? `${start}${this._getFileScopedHashMethodDefinition(id)}${end}` ? this._getFileScopedHashMethodDefinition(id)
: `${start}${this._getHashMethodDefinition(id)}${end}`; : 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={}){ constructor(config={}){
this.instrumentationData = {}; this.instrumentationData = {};
this.injector = new Injector(); this.injector = new Injector();
this.measureStatementCoverage = (config.measureStatementCoverage === false) ? false : true; this.modifierWhitelist = config.modifierWhitelist || [];
this.measureFunctionCoverage = (config.measureFunctionCoverage === false) ? false: true; 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){ _isRootNode(node){
@ -56,16 +62,17 @@ class Instrumenter {
instrument(contractSource, fileName) { instrument(contractSource, fileName) {
const contract = {}; const contract = {};
this.injector.resetModifierMapping();
parse.configure(this.enabled, this.modifierWhitelist);
contract.source = contractSource; contract.source = contractSource;
contract.instrumented = contractSource; contract.instrumented = contractSource;
this._initializeCoverageFields(contract); this._initializeCoverageFields(contract);
parse.configureStatementCoverage(this.measureStatementCoverage)
parse.configureFunctionCoverage(this.measureFunctionCoverage)
// First, we run over the original contract to get the source mapping. // First, we run over the original contract to get the source mapping.
let ast = SolidityParser.parse(contract.source, {loc: true, range: true}); let ast = SolidityParser.parse(contract.source, {loc: true, range: true});
//console.log(JSON.stringify(ast, null, ' '))
parse[ast.type](contract, ast); parse[ast.type](contract, ast);
const retValue = JSON.parse(JSON.stringify(contract)); // Possibly apotropaic. const retValue = JSON.parse(JSON.stringify(contract)); // Possibly apotropaic.
@ -88,7 +95,12 @@ class Instrumenter {
// Line instrumentation has to happen first // Line instrumentation has to happen first
contract.injectionPoints[injectionPoint].sort((a, b) => { 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); return injections.indexOf(b.type) - injections.indexOf(a.type);
}); });

@ -11,12 +11,9 @@ const FILE_SCOPED_ID = "fileScopedId";
const parse = {}; const parse = {};
// Utilities // Utilities
parse.configureStatementCoverage = function(val){ parse.configure = function(_enabled, _whitelist){
register.measureStatementCoverage = val; register.enabled = Object.assign(register.enabled, _enabled);
} register.modifierWhitelist = _whitelist;
parse.configureFunctionCoverage = function(val){
register.measureFunctionCoverage = val;
} }
// Nodes // Nodes
@ -32,17 +29,55 @@ parse.Block = function(contract, expression) {
} }
}; };
parse.BinaryOperation = function(contract, expression) { parse.BinaryOperation = function(contract, expression, skipStatementRegistry) {
register.statement(contract, expression); // 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. // 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. // This makes sure we don't instrument a chain of expressions multiple times.
if (expression.expression.type !== 'FunctionCall') { 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') { if (expression.expression.name === 'require') {
register.requireBranch(contract, expression); 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] &&
parse[expression.expression.type](contract, expression.expression); parse[expression.expression.type](contract, expression.expression);
@ -52,10 +87,11 @@ parse.FunctionCall = function(contract, expression) {
} }
}; };
parse.Conditional = function(contract, expression) { parse.Conditional = function(contract, expression, skipStatementRegistry) {
register.statement(contract, expression); parse[expression.condition.type] &&
// TODO: Investigate node structure parse[expression.condition.type](contract, expression.condition, true);
// There are potential substatements here we aren't measuring
register.conditional(contract, expression);
}; };
parse.ContractDefinition = function(contract, expression) { parse.ContractDefinition = function(contract, expression) {
@ -145,6 +181,10 @@ parse.FunctionDefinition = function(contract, expression) {
parse.IfStatement = function(contract, expression) { parse.IfStatement = function(contract, expression) {
register.statement(contract, expression); register.statement(contract, expression);
register.ifStatement(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] &&
parse[expression.trueBody.type](contract, expression.trueBody); parse[expression.trueBody.type](contract, expression.trueBody);
@ -220,6 +260,10 @@ parse.SourceUnit = function(contract, expression) {
parse.ReturnStatement = function(contract, expression) { parse.ReturnStatement = function(contract, expression) {
register.statement(contract, expression); register.statement(contract, expression);
expression.expression &&
parse[expression.expression.type] &&
parse[expression.expression.type](contract, expression.expression, true);
}; };
// TODO:Investigate node structure // TODO:Investigate node structure
@ -250,11 +294,18 @@ parse.UsingStatement = function (contract, expression) {
}; };
parse.VariableDeclarationStatement = 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); register.statement(contract, expression);
}; };
parse.WhileStatement = function (contract, expression) { parse.WhileStatement = function (contract, expression) {
register.statement(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] &&
parse[expression.body.type](contract, expression.body); parse[expression.body.type](contract, expression.body);
}; };

@ -12,8 +12,15 @@ class Registrar {
this.trackStatements = true; this.trackStatements = true;
// These are set by user option and enable/disable the measurement completely // These are set by user option and enable/disable the measurement completely
this.measureStatementCoverage = true; this.enabled = {
this.measureFunctionCoverage = true; statements: true,
functions: true,
modifiers: true,
branches: true,
lines: true
}
this.modifierWhitelist = [];
} }
/** /**
@ -36,7 +43,7 @@ class Registrar {
* @param {Object} expression AST node * @param {Object} expression AST node
*/ */
statement(contract, expression) { 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 startContract = contract.instrumented.slice(0, expression.range[0]);
const startline = ( startContract.match(/\n/g) || [] ).length + 1; const startline = ( startContract.match(/\n/g) || [] ).length + 1;
@ -76,6 +83,8 @@ class Registrar {
* @param {Object} expression AST node * @param {Object} expression AST node
*/ */
line(contract, expression) { line(contract, expression) {
if (!this.enabled.lines) return;
const startchar = expression.range[0]; const startchar = expression.range[0];
const endchar = expression.range[1] + 1; const endchar = expression.range[1] + 1;
const lastNewLine = contract.instrumented.slice(0, startchar).lastIndexOf('\n'); const lastNewLine = contract.instrumented.slice(0, startchar).lastIndexOf('\n');
@ -107,17 +116,52 @@ class Registrar {
* @param {Object} expression AST node * @param {Object} expression AST node
*/ */
functionDeclaration(contract, expression) { functionDeclaration(contract, expression) {
if (!this.measureFunctionCoverage) return; if (!this.enabled.functions) return;
let start = 0; 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){ if (expression.modifiers && expression.modifiers.length){
for (let modifier of expression.modifiers ){ 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){ if (modifier.range[1] > start){
start = modifier.range[1]; 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 { } else {
start = expression.range[0]; start = expression.range[0];
@ -133,7 +177,6 @@ class Registrar {
start + endlineDelta start + endlineDelta
); );
contract.fnId += 1;
contract.fnMap[contract.fnId] = { contract.fnMap[contract.fnId] = {
name: expression.isConstructor ? 'constructor' : expression.name, name: expression.isConstructor ? 'constructor' : expression.name,
line: startline, 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) * Registers injections for require statement measurements (branches)
* @param {Object} contract instrumentation target * @param {Object} contract instrumentation target
* @param {Object} expression AST node * @param {Object} expression AST node
*/ */
requireBranch(contract, expression) { requireBranch(contract, expression) {
if (!this.enabled.branches) return;
this.addNewBranch(contract, expression); this.addNewBranch(contract, expression);
this._createInjectionPoint( this._createInjectionPoint(
contract, contract,
@ -217,6 +455,8 @@ class Registrar {
* @param {Object} expression AST node * @param {Object} expression AST node
*/ */
ifStatement(contract, expression) { ifStatement(contract, expression) {
if (!this.enabled.branches) return;
this.addNewBranch(contract, expression); this.addNewBranch(contract, expression);
if (expression.trueBody.type === 'Block') { 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"}, client: {type: "object"},
cwd: {type: "string"}, cwd: {type: "string"},
host: {type: "string"}, host: {type: "string"},
abiOutputPath: {type: "string"},
matrixOutputPath: {type: "string"},
matrixReporterPath: {type: "string"},
port: {type: "number"}, port: {type: "number"},
providerOptions: {type: "object"}, providerOptions: {type: "object"},
silent: {type: "boolean"}, silent: {type: "boolean"},
autoLaunchServer: {type: "boolean"}, autoLaunchServer: {type: "boolean"},
istanbulFolder: {type: "string"}, istanbulFolder: {type: "string"},
measureStatementCoverage: {type: "boolean"}, measureStatementCoverage: {type: "boolean"},
measureFunctionCoverage: {type: "boolean"}, measureFunctionCoverage: {type: "boolean"},
measureModifierCoverage: {type: "boolean"},
measureLineCoverage: {type: "boolean"},
measureBranchCoverage: {type: "boolean"},
// Hooks: // Hooks:
onServerReady: {type: "function", format: "isFunction"}, onServerReady: {type: "function", format: "isFunction"},
@ -39,6 +44,11 @@ const configSchema = {
type: "array", type: "array",
items: {type: "string"} items: {type: "string"}
}, },
modifierWhitelist: {
type: "array",
items: {type: "string"}
}
}, },
}; };

@ -12,7 +12,7 @@
"scripts": { "scripts": {
"nyc": "SILENT=true nyc --exclude '**/sc_temp/**' --exclude '**/test/**'", "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": "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", "test:debug": "node --max-old-space-size=4096 ./node_modules/.bin/mocha test/units/* --timeout 100000 --no-warnings --exit",
"netlify": "./scripts/run-netlify.sh" "netlify": "./scripts/run-netlify.sh"
}, },
@ -24,17 +24,20 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@solidity-parser/parser": "^0.14.0", "@ethersproject/abi": "^5.0.9",
"@solidity-parser/parser": "^0.14.1",
"@truffle/provider": "^0.2.24", "@truffle/provider": "^0.2.24",
"chalk": "^2.4.2", "chalk": "^2.4.2",
"death": "^1.1.0", "death": "^1.1.0",
"detect-port": "^1.3.0", "detect-port": "^1.3.0",
"difflib": "^0.2.4",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"ghost-testrpc": "^0.0.2", "ghost-testrpc": "^0.0.2",
"global-modules": "^2.0.0", "global-modules": "^2.0.0",
"globby": "^10.0.1", "globby": "^10.0.1",
"jsonschema": "^1.2.4", "jsonschema": "^1.2.4",
"lodash": "^4.17.15", "lodash": "^4.17.15",
"mocha": "7.1.2",
"node-emoji": "^1.10.0", "node-emoji": "^1.10.0",
"pify": "^4.0.1", "pify": "^4.0.1",
"recursive-readdir": "^2.2.2", "recursive-readdir": "^2.2.2",
@ -44,9 +47,6 @@
"web3-utils": "^1.3.0" "web3-utils": "^1.3.0"
}, },
"devDependencies": { "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-ethers": "^2.0.4",
"@nomiclabs/hardhat-truffle5": "^2.0.0", "@nomiclabs/hardhat-truffle5": "^2.0.0",
"@nomiclabs/hardhat-waffle": "^2.0.1", "@nomiclabs/hardhat-waffle": "^2.0.1",
@ -60,10 +60,9 @@
"ganache-cli": "6.12.2", "ganache-cli": "6.12.2",
"hardhat": "^2.9.3", "hardhat": "^2.9.3",
"hardhat-gas-reporter": "^1.0.1", "hardhat-gas-reporter": "^1.0.1",
"mocha": "5.2.0",
"nyc": "^14.1.1", "nyc": "^14.1.1",
"solc": "^0.5.10", "solc": "^0.7.5",
"truffle": "5.0.31", "truffle": "5.1.43",
"truffle-config": "^1.1.18" "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,
TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT, TASK_COMPILE_SOLIDITY_GET_COMPILER_INPUT,
TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE, TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE,
TASK_COMPILE_SOLIDITY_LOG_COMPILATION_ERRORS
} = require("hardhat/builtin-tasks/task-names"); } = require("hardhat/builtin-tasks/task-names");
// Toggled true for `coverage` task only. // Toggled true for `coverage` task only.
@ -75,6 +76,18 @@ subtask(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE).setAction(async (_,
return compilationJob; 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 * Coverage task implementation
* @param {HardhatUserArgs} args * @param {HardhatUserArgs} args
@ -84,6 +97,8 @@ task("coverage", "Generates a code coverage report for tests")
.addOptionalParam("testfiles", ui.flags.file, "", types.string) .addOptionalParam("testfiles", ui.flags.file, "", types.string)
.addOptionalParam("solcoverjs", ui.flags.solcoverjs, "", types.string) .addOptionalParam("solcoverjs", ui.flags.solcoverjs, "", types.string)
.addOptionalParam('temp', ui.flags.temp, "", types.string) .addOptionalParam('temp', ui.flags.temp, "", types.string)
.addFlag('matrix', ui.flags.testMatrix)
.addFlag('abi', ui.flags.abi)
.setAction(async function(args, env){ .setAction(async function(args, env){
const API = require('./../lib/api'); 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) 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 // Instrumentation
// ================ // ================
@ -219,6 +244,9 @@ task("coverage", "Generates a code coverage report for tests")
? nomiclabsUtils.getTestFilePaths(args.testfiles) ? nomiclabsUtils.getTestFilePaths(args.testfiles)
: []; : [];
// Optionally collect tests-per-line-of-code data
nomiclabsUtils.collectTestMatrixData(args, env, api);
try { try {
failedTests = await env.run(TASK_TEST, {testFiles: testfiles}) failedTests = await env.run(TASK_TEST, {testFiles: testfiles})
} catch (e) { } catch (e) {
@ -226,10 +254,13 @@ task("coverage", "Generates a code coverage report for tests")
} }
await api.onTestsComplete(config); await api.onTestsComplete(config);
// ======== // =================================
// Istanbul // Output (Istanbul or Test Matrix)
// ======== // =================================
await api.report(); (args.matrix)
? await api.saveTestMatrix()
: await api.report();
await api.onIstanbulComplete(config); await api.onIstanbulComplete(config);
} catch(e) { } catch(e) {

@ -1,6 +1 @@
if (global && global.__hardhatContext){ require("./hardhat.plugin");
require("./hardhat.plugin");
return;
}
module.exports = require('./buidler.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); super(log);
this.flags = { 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. ` + solcoverjs: `Relative path from working directory to config. ` +
`Useful for monorepo packages that share settings.`, `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. ` + temp: `Path to a disposable folder to store compilation artifacts in. ` +
`Useful when your test setup scripts include hard-coded paths to ` + `Useful when your test setup scripts include hard-coded paths to ` +
`a build directory.`, `a build directory.`,
} }
} }

@ -37,6 +37,7 @@ function normalizeConfig(config, args={}){
config.logger = config.logger ? config.logger : {log: null}; config.logger = config.logger ? config.logger : {log: null};
config.solcoverjs = args.solcoverjs config.solcoverjs = args.solcoverjs
config.gasReporter = { enabled: false } config.gasReporter = { enabled: false }
config.matrix = args.matrix;
try { try {
const hardhatPackage = require('hardhat/package.json'); const hardhatPackage = require('hardhat/package.json');
@ -165,6 +166,52 @@ function configureHttpProvider(networkConfig, api, ui){
networkConfig.url = `http://${api.host}:${api.port}`; 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. * 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. * This needs to be done after accounts are fetched from the launched client.
@ -216,6 +263,9 @@ module.exports = {
setupBuidlerNetwork, setupBuidlerNetwork,
setupHardhatNetwork, setupHardhatNetwork,
getTestFilePaths, 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-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`, 'lib-global': `\n${ct} ${c.grey('Using Truffle library from global node_modules.')}\n`,
'help': `Usage: truffle run coverage [options]\n\n` + 'help': `Usage: truffle run coverage [options]\n\n` +
`Options:\n` + `Options:\n` +
` --file: path (or glob) to subset of JS test files. (Quote your globs)\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); 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. * Configures the network. Runs before the server is launched.
@ -196,10 +229,47 @@ function normalizeConfig(config){
return 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 = { module.exports = {
getTestFilePaths, getTestFilePaths,
setNetwork, setNetwork,
setNetworkFrom, setNetworkFrom,
loadLibrary, loadLibrary,
normalizeConfig, normalizeConfig,
filteredLogger,
collectTestMatrixData,
generateHumanReadableAbiList
} }

@ -30,9 +30,18 @@ async function plugin(config){
truffle = truffleUtils.loadLibrary(config); truffle = truffleUtils.loadLibrary(config);
api = new API(utils.loadSolcoverJS(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 // Server launch
truffleUtils.setNetwork(config, api);
const client = api.client || truffle.ganache; const client = api.client || truffle.ganache;
const address = await api.ganache(client); const address = await api.ganache(client);
const accountsRequest = await utils.getAccountsGanache(api.server.provider); const accountsRequest = await utils.getAccountsGanache(api.server.provider);
@ -90,7 +99,14 @@ async function plugin(config){
path.basename(config.contracts_build_directory) path.basename(config.contracts_build_directory)
); );
// Filter compilation warnings
const defaultLogger = config.logger;
if (!config.verbose){
config.logger = truffleUtils.filteredLogger;
}
config.all = true; config.all = true;
config.strict = false;
config.compilers.solc.settings.optimizer.enabled = false; config.compilers.solc.settings.optimizer.enabled = false;
// Run pre-compile hook; // Run pre-compile hook;
@ -98,9 +114,12 @@ async function plugin(config){
// Compile Instrumented Contracts // Compile Instrumented Contracts
await truffle.contracts.compile(config); await truffle.contracts.compile(config);
config.logger = defaultLogger;
await api.onCompileComplete(config); await api.onCompileComplete(config);
config.test_files = await truffleUtils.getTestFilePaths(config); config.test_files = await truffleUtils.getTestFilePaths(config);
truffleUtils.collectTestMatrixData(config, api);
// Run tests // Run tests
try { try {
failures = await truffle.test.run(config) failures = await truffle.test.run(config)
@ -109,8 +128,13 @@ async function plugin(config){
} }
await api.onTestsComplete(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); await api.onIstanbulComplete(config);
} catch(e){ } catch(e){

@ -53,3 +53,13 @@ if [ ! -d "coverage" ]; then
exit 1 exit 1
fi 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 fi
} }
function verifyMatrixExists {
if [ ! -f "testMatrix.json" ]; then
echo "ERROR: no matrix file was created."
exit 1
fi
}
# Get rid of any caches # Get rid of any caches
sudo rm -rf node_modules sudo rm -rf node_modules
echo "NVM CURRENT >>>>>" && nvm current echo "NVM CURRENT >>>>>" && nvm current
@ -47,6 +54,12 @@ npx hardhat coverage
verifyCoverageExists verifyCoverageExists
npx hardhat coverage --matrix
verifyMatrixExists
cat testMatrix.json
echo "" echo ""
echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
echo "wighawag/hardhat-deploy " echo "wighawag/hardhat-deploy "
@ -68,22 +81,3 @@ yarn run coverage
yarn run gas yarn run gas
verifyCoverageExists 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 // This contract should throw a parse error in instrumentSolidity.js
pragma solidity ^0.5.0; pragma solidity ^0.7.0;
contract SimpleError { contract SimpleError {
uint x = 0; 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 { contract Migrations {

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

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

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

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

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

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

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

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

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

@ -4,7 +4,7 @@ require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = { module.exports = {
solidity: { solidity: {
version: "0.5.15" version: "0.7.3"
}, },
logger: process.env.SILENT ? { log: () => {} } : console, 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 { contract RelativePathImport {
uint r; 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"; 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 "../assets/RelativePathImport.sol";
import "package/NodeModulesImport.sol"; import "package/NodeModulesImport.sol";

@ -3,7 +3,7 @@ require(__dirname + "/../plugins/nomiclabs.plugin");
module.exports = { module.exports = {
solidity: { solidity: {
version: "0.5.15" version: "0.7.3"
}, },
logger: process.env.SILENT ? { log: () => {} } : console, 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 { contract AnotherImport {
uint x; uint x;

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

@ -1,4 +1,4 @@
pragma solidity ^0.5.0; pragma solidity ^0.7.0;
library CLibrary { library CLibrary {
uint constant x = 1; 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 { contract Migrations {
address public owner; address public owner;

@ -1,14 +1,10 @@
pragma solidity ^0.5.0; pragma solidity ^0.7.0;
contract PureView { contract PureView {
// Make sure we aren't corrupting anything with the replace // Make sure we aren't corrupting anything with the replace
uint notpureview = 5; 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){ function inheritedPure(uint a, uint b) public pure returns(uint){
return a + b; return a + b;
} }

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

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

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

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

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

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

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

@ -1,4 +1,4 @@
pragma solidity ^0.5.0; pragma solidity ^0.7.0;
contract ContractC { 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: { solidity: {
version: "0.5.15" version: "0.7.3"
}, },
logger: process.env.SILENT ? { log: () => {} } : console, logger: process.env.SILENT ? { log: () => {} } : console,
}; };

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

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

@ -1,4 +1,4 @@
pragma solidity ^0.5.0; pragma solidity ^0.7.0;
contract ContractA { 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 { contract ContractB {

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

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

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

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

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

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

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

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

Loading…
Cancel
Save