Merge pull request #421 from sc-forks/buidler-plugin
Add Buidler plugin / Finalize APIpull/456/head v0.7.0-beta.3
commit
212c88fc4d
@ -0,0 +1,4 @@ |
||||
// For require('solidity-coverage/api');
|
||||
const api = require('./lib/api'); |
||||
|
||||
module.exports = api; |
@ -1,18 +0,0 @@ |
||||
|
||||
// Mute compiler warnings - this will need to be addressed properly in
|
||||
// the Buidler plugin by overloading TASK_COMPILE_COMPILE.
|
||||
const originalLog = console.log; |
||||
console.warn = () => {}; |
||||
console.log = val => val === '\n' ? null : originalLog(val); |
||||
|
||||
module.exports = { |
||||
solc: { |
||||
version: "0.5.8" |
||||
}, |
||||
paths: { |
||||
artifacts: "./test/artifacts", |
||||
cache: "./test/cache", |
||||
test: "./test/units", |
||||
sources: "./test/sources/contracts", |
||||
} |
||||
} |
@ -1,460 +0,0 @@ |
||||
/** |
||||
* A collection of utilities for common tasks plugins will need in the course |
||||
* of composing a workflow using the solidity-coverage API |
||||
* |
||||
* TODO: Sweep back through here and make all `config.truffle_variable` plugin |
||||
* platform neutral... |
||||
*/ |
||||
|
||||
const PluginUI = require('./truffle.ui'); |
||||
|
||||
const path = require('path'); |
||||
const fs = require('fs-extra'); |
||||
const dir = require('node-dir'); |
||||
const globby = require('globby'); |
||||
const shell = require('shelljs'); |
||||
const globalModules = require('global-modules'); |
||||
const TruffleProvider = require('@truffle/provider'); |
||||
|
||||
// ===
|
||||
// UI
|
||||
// ===
|
||||
|
||||
/** |
||||
* Displays a list of skipped contracts |
||||
* @param {TruffleConfig} config |
||||
* @return {Object[]} skipped array of objects generated by `assembleTargets` method |
||||
*/ |
||||
function reportSkipped(config, skipped=[]){ |
||||
let started = false; |
||||
const ui = new PluginUI(config.logger.log); |
||||
|
||||
for (let item of skipped){ |
||||
if (!started) { |
||||
ui.report('instr-skip', []); |
||||
started = true; |
||||
} |
||||
ui.report('instr-skipped', [item.relativePath]); |
||||
} |
||||
} |
||||
|
||||
// ========
|
||||
// File I/O
|
||||
// ========
|
||||
|
||||
/** |
||||
* Loads source |
||||
* @param {String} _path absolute path |
||||
* @return {String} source file |
||||
*/ |
||||
function loadSource(_path){ |
||||
return fs.readFileSync(_path).toString(); |
||||
} |
||||
|
||||
/** |
||||
* Save a set of instrumented files to a temporary directory. |
||||
* @param {Object[]} targets array of targets generated by `assembleTargets` |
||||
* @param {[type]} originalDir absolute path to parent directory of original source |
||||
* @param {[type]} tempDir absolute path to temp parent directory |
||||
*/ |
||||
function save(targets, originalDir, tempDir){ |
||||
let _path; |
||||
for (target of targets) { |
||||
_path = target.canonicalPath.replace(originalDir, tempDir); |
||||
fs.outputFileSync(_path, target.source); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Relativizes an absolute file path, given an absolute parent path |
||||
* @param {String} pathToFile |
||||
* @param {String} pathToParent |
||||
* @return {String} relative path |
||||
*/ |
||||
function toRelativePath(pathToFile, pathToParent){ |
||||
return pathToFile.replace(`${pathToParent}${path.sep}`, ''); |
||||
} |
||||
|
||||
/** |
||||
* Returns a pair of canonically named temporary directory paths for contracts |
||||
* and artifacts. Instrumented assets can be written & compiled to these. |
||||
* Then the unit tests can be run, consuming them as sources. |
||||
* @param {TruffleConfig} config |
||||
* @return {Object} temp paths |
||||
*/ |
||||
function getTempLocations(config){ |
||||
const cwd = config.working_directory; |
||||
const contractsDirName = '.coverage_contracts'; |
||||
const artifactsDirName = config.temp || '.coverage_artifacts'; |
||||
|
||||
return { |
||||
tempContractsDir: path.join(cwd, contractsDirName), |
||||
tempArtifactsDir: path.join(cwd, artifactsDirName) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Checks for existence of contract sources, and sweeps away debris |
||||
* left over from an uncontrolled crash. |
||||
*/ |
||||
function checkContext(config, tempContractsDir, tempArtifactsDir){ |
||||
const ui = new PluginUI(config.logger.log); |
||||
|
||||
if (!shell.test('-e', config.contracts_directory)){ |
||||
|
||||
const msg = ui.generate('sources-fail', [config.contracts_directory]) |
||||
throw new Error(msg); |
||||
} |
||||
|
||||
if (shell.test('-e', tempContractsDir)){ |
||||
shell.rm('-Rf', tempContractsDir); |
||||
} |
||||
|
||||
if (shell.test('-e', tempArtifactsDir)){ |
||||
shell.rm('-Rf', tempArtifactsDir); |
||||
} |
||||
} |
||||
|
||||
|
||||
// =============================
|
||||
// Instrumentation Set Assembly
|
||||
// =============================
|
||||
|
||||
function assembleFiles(config, skipFiles=[]){ |
||||
let targets; |
||||
let skipFolders; |
||||
let skipped = []; |
||||
|
||||
const { |
||||
tempContractsDir, |
||||
tempArtifactsDir |
||||
} = getTempLocations(config); |
||||
|
||||
checkContext(config, tempContractsDir, tempArtifactsDir); |
||||
|
||||
shell.mkdir(tempContractsDir); |
||||
shell.mkdir(tempArtifactsDir); |
||||
|
||||
targets = shell.ls(`${config.contracts_directory}/**/*.sol`); |
||||
|
||||
skipFiles = assembleSkipped(config, targets, skipFiles); |
||||
|
||||
return assembleTargets(config, targets, skipFiles) |
||||
} |
||||
|
||||
function assembleTargets(config, targets=[], skipFiles=[]){ |
||||
const skipped = []; |
||||
const filtered = []; |
||||
const cd = config.contracts_directory; |
||||
|
||||
for (let target of targets){ |
||||
if (skipFiles.includes(target)){ |
||||
|
||||
skipped.push({ |
||||
canonicalPath: target, |
||||
relativePath: toRelativePath(target, cd), |
||||
source: loadSource(target) |
||||
}) |
||||
|
||||
} else { |
||||
|
||||
filtered.push({ |
||||
canonicalPath: target, |
||||
relativePath: toRelativePath(target, cd), |
||||
source: loadSource(target) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
return { |
||||
skipped: skipped, |
||||
targets: filtered |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Parses the skipFiles option (which also accepts folders) |
||||
*/ |
||||
function assembleSkipped(config, targets, skipFiles=[]){ |
||||
const cd = config.contracts_directory; |
||||
|
||||
// Make paths absolute
|
||||
skipFiles = skipFiles.map(contract => `${cd}/${contract}`); |
||||
skipFiles.push(`${cd}/Migrations.sol`); |
||||
|
||||
// Enumerate files in skipped folders
|
||||
const skipFolders = skipFiles.filter(item => path.extname(item) !== '.sol') |
||||
|
||||
for (let folder of skipFolders){ |
||||
for (let target of targets ) { |
||||
if (target.indexOf(folder) === 0) |
||||
skipFiles.push(target); |
||||
} |
||||
}; |
||||
|
||||
return skipFiles; |
||||
} |
||||
|
||||
// ========
|
||||
// Truffle
|
||||
// ========
|
||||
|
||||
/** |
||||
* Returns a list of test files to pass to mocha. |
||||
* @param {Object} config truffleConfig |
||||
* @return {String[]} list of files to pass to mocha |
||||
*/ |
||||
function getTestFilePaths(config){ |
||||
let target; |
||||
let ui = new PluginUI(config.logger.log); |
||||
|
||||
|
||||
// Handle --file <path|glob> cli option (subset of tests)
|
||||
(typeof config.file === 'string') |
||||
? target = globby.sync([config.file]) |
||||
: target = dir.files(config.test_directory, { sync: true }) || []; |
||||
|
||||
// Filter native solidity tests and warn that they're skipped
|
||||
const solregex = /.*\.(sol)$/; |
||||
const hasSols = target.filter(f => f.match(solregex) != null); |
||||
|
||||
if (hasSols.length > 0) ui.report('sol-tests', [hasSols.length]); |
||||
|
||||
// Return list of test files
|
||||
const testregex = /.*\.(js|ts|es|es6|jsx)$/; |
||||
return target.filter(f => f.match(testregex) != null); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Configures the network. Runs before the server is launched. |
||||
* User can request a network from truffle-config with "--network <name>". |
||||
* There are overlapiing options in solcoverjs (like port and providerOptions.network_id). |
||||
* Where there are mismatches user is warned & the truffle network settings are preferred. |
||||
* |
||||
* Also generates a default config & sets the default gas high / gas price low. |
||||
* |
||||
* @param {TruffleConfig} config |
||||
* @param {SolidityCoverage} api |
||||
*/ |
||||
function setNetwork(config, api){ |
||||
const ui = new PluginUI(config.logger.log); |
||||
|
||||
// --network <network-name>
|
||||
if (config.network){ |
||||
const network = config.networks[config.network]; |
||||
|
||||
// Check network:
|
||||
if (!network){ |
||||
throw new Error(ui.generate('no-network', [config.network])); |
||||
} |
||||
|
||||
// Check network id
|
||||
if (!isNaN(parseInt(network.network_id))){ |
||||
|
||||
// Warn: non-matching provider options id and network id
|
||||
if (api.providerOptions.network_id && |
||||
api.providerOptions.network_id !== parseInt(network.network_id)){ |
||||
|
||||
ui.report('id-clash', [ parseInt(network.network_id) ]); |
||||
} |
||||
|
||||
// Prefer network defined id.
|
||||
api.providerOptions.network_id = parseInt(network.network_id); |
||||
|
||||
} else { |
||||
network.network_id = "*"; |
||||
} |
||||
|
||||
// Check port: use solcoverjs || default if undefined
|
||||
if (!network.port) { |
||||
ui.report('no-port', [api.port]); |
||||
network.port = api.port; |
||||
} |
||||
|
||||
// Warn: port conflicts
|
||||
if (api.port !== api.defaultPort && api.port !== network.port){ |
||||
ui.report('port-clash', [ network.port ]) |
||||
} |
||||
|
||||
// Prefer network port if defined;
|
||||
api.port = network.port; |
||||
|
||||
network.gas = api.gasLimit; |
||||
network.gasPrice = api.gasPrice; |
||||
|
||||
setOuterConfigKeys(config, api, network.network_id); |
||||
return; |
||||
} |
||||
|
||||
// Default Network Configuration
|
||||
config.network = 'soliditycoverage'; |
||||
setOuterConfigKeys(config, api, "*"); |
||||
|
||||
config.networks[config.network] = { |
||||
network_id: "*", |
||||
port: api.port, |
||||
host: api.host, |
||||
gas: api.gasLimit, |
||||
gasPrice: api.gasPrice |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the default `from` account field in the truffle network that will be used. |
||||
* This needs to be done after accounts are fetched from the launched client. |
||||
* @param {TruffleConfig} config |
||||
* @param {Array} accounts |
||||
*/ |
||||
function setNetworkFrom(config, accounts){ |
||||
if (!config.networks[config.network].from){ |
||||
config.networks[config.network].from = accounts[0]; |
||||
} |
||||
} |
||||
|
||||
// Truffle complains that these outer keys *are not* set when running plugin fn directly.
|
||||
// But throws saying they *cannot* be manually set when running as truffle command.
|
||||
function setOuterConfigKeys(config, api, id){ |
||||
try { |
||||
config.network_id = id; |
||||
config.port = api.port; |
||||
config.host = api.host; |
||||
config.provider = TruffleProvider.create(config); |
||||
} catch (err){} |
||||
} |
||||
|
||||
/** |
||||
* Tries to load truffle module library and reports source. User can force use of |
||||
* a non-local version using cli flags (see option). It's necessary to maintain |
||||
* a fail-safe lib because feature was only introduced in 5.0.30. Load order is: |
||||
* |
||||
* 1. local node_modules |
||||
* 2. global node_modules |
||||
* 3. fail-safe (truffle lib v 5.0.31 at ./plugin-assets/truffle.library) |
||||
* |
||||
* @param {Object} truffleConfig config |
||||
* @return {Module} |
||||
*/ |
||||
function loadTruffleLibrary(config){ |
||||
const ui = new PluginUI(config.logger.log); |
||||
|
||||
// Local
|
||||
try { |
||||
if (config.useGlobalTruffle || config.usePluginTruffle) throw null; |
||||
|
||||
const lib = require("truffle"); |
||||
ui.report('lib-local'); |
||||
return lib; |
||||
|
||||
} catch(err) {}; |
||||
|
||||
// Global
|
||||
try { |
||||
if (config.usePluginTruffle) throw null; |
||||
|
||||
const globalTruffle = path.join(globalModules, 'truffle'); |
||||
const lib = require(globalTruffle); |
||||
ui.report('lib-global'); |
||||
return lib; |
||||
|
||||
} catch(err) {}; |
||||
|
||||
// Plugin Copy @ v 5.0.31
|
||||
try { |
||||
if (config.forceLibFailure) throw null; // For err unit testing
|
||||
|
||||
ui.report('lib-warn'); |
||||
return require("./truffle.library") |
||||
|
||||
} catch(err) { |
||||
throw new Error(ui.generate('lib-fail', [err])); |
||||
}; |
||||
|
||||
} |
||||
|
||||
function loadSolcoverJS(config){ |
||||
let solcoverjs; |
||||
let coverageConfig; |
||||
let ui = new PluginUI(config.logger.log); |
||||
|
||||
|
||||
// Handle --solcoverjs flag
|
||||
(config.solcoverjs) |
||||
? solcoverjs = path.join(config.working_directory, config.solcoverjs) |
||||
: solcoverjs = path.join(config.working_directory, '.solcover.js'); |
||||
|
||||
// Catch solcoverjs syntax errors
|
||||
if (shell.test('-e', solcoverjs)){ |
||||
|
||||
try { |
||||
coverageConfig = require(solcoverjs); |
||||
} catch(error){ |
||||
error.message = ui.generate('solcoverjs-fail') + error.message; |
||||
throw new Error(error) |
||||
} |
||||
|
||||
// Config is optional
|
||||
} else { |
||||
coverageConfig = {}; |
||||
} |
||||
|
||||
// Truffle writes to coverage config
|
||||
coverageConfig.log = config.logger.log; |
||||
coverageConfig.cwd = config.working_directory; |
||||
coverageConfig.originalContractsDir = config.contracts_directory; |
||||
|
||||
// Solidity-Coverage writes to Truffle config
|
||||
config.mocha = config.mocha || {}; |
||||
|
||||
if (coverageConfig.mocha && typeof coverageConfig.mocha === 'object'){ |
||||
config.mocha = Object.assign( |
||||
config.mocha, |
||||
coverageConfig.mocha |
||||
); |
||||
} |
||||
|
||||
return coverageConfig; |
||||
} |
||||
|
||||
// ==========================
|
||||
// Finishing / Cleanup
|
||||
// ==========================
|
||||
|
||||
/** |
||||
* Silently removes temporary folders and calls api.finish to shut server down |
||||
* @param {TruffleConfig} config |
||||
* @param {SolidityCoverage} api |
||||
* @return {Promise} |
||||
*/ |
||||
async function finish(config, api){ |
||||
const { |
||||
tempContractsDir, |
||||
tempArtifactsDir |
||||
} = getTempLocations(config); |
||||
|
||||
shell.config.silent = true; |
||||
shell.rm('-Rf', tempContractsDir); |
||||
shell.rm('-Rf', tempArtifactsDir); |
||||
|
||||
if (api) await api.finish(); |
||||
} |
||||
|
||||
module.exports = { |
||||
assembleFiles: assembleFiles, |
||||
assembleSkipped: assembleSkipped, |
||||
assembleTargets: assembleTargets, |
||||
checkContext: checkContext, |
||||
finish: finish, |
||||
getTempLocations: getTempLocations, |
||||
getTestFilePaths: getTestFilePaths, |
||||
loadSource: loadSource, |
||||
loadSolcoverJS: loadSolcoverJS, |
||||
loadTruffleLibrary: loadTruffleLibrary, |
||||
reportSkipped: reportSkipped, |
||||
save: save, |
||||
setNetwork: setNetwork, |
||||
setNetworkFrom: setNetworkFrom, |
||||
setOuterConfigKeys: setOuterConfigKeys, |
||||
checkContext: checkContext, |
||||
toRelativePath: toRelativePath |
||||
} |
@ -0,0 +1,367 @@ |
||||
# Solidity-Coverage API |
||||
|
||||
Solidity-coverage tracks which lines are hit as your tests run by instrumenting the contracts with special solidity statements and detecting their execution in a coverage-enabled EVM. |
||||
|
||||
As such, the API spans the full set of tasks typically required to run a solidity test suite. The |
||||
table below shows how its core methods relate to the stages of a test run: |
||||
|
||||
| Test Stage <img width=200/> | API Method <img width=200/> | Description <img width=800/> | |
||||
|---------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| |
||||
| compilation | `instrument` | A **pre-compilation** step: Rewrites contracts and generates an instrumentation data map. | |
||||
| client launch | `ganache` | A **substitute** step: Launches a ganache client with coverage collection enabled in its VM. As the client runs it will mark line/branch hits on the instrumentation data map. | |
||||
| test | `report` | A **post-test** step: Generates a coverage report from the data collected by the VM after tests complete. | |
||||
| exit | `finish` | A **substitute** step: Shuts client down | |
||||
|
||||
[3]: https://github.com/gotwarlost/istanbul |
||||
|
||||
**Additional Resources:** |
||||
|
||||
+ the library includes [file system utilities](#Utils) for managing the |
||||
disposable set of contracts/artifacts which coverage must use in lieu of the 'real' (uninstrumented) |
||||
contracts. |
||||
|
||||
+ there are two complete [coverage tool/plugin implementations][5] (for Buidler and Truffle) |
||||
which can be used as sources if you're building something similar. |
||||
|
||||
[5]: https://github.com/sc-forks/solidity-coverage/tree/beta/plugins |
||||
|
||||
|
||||
# Table of Contents |
||||
|
||||
- [API Methods](#api) |
||||
* [constructor](#constructor) |
||||
* [instrument](#instrument) |
||||
* [ganache](#ganache) |
||||
* [report](#report) |
||||
* [finish](#finish) |
||||
* [getInstrumentationData](#getinstrumentationdata) |
||||
* [setInstrumentationData](#setinstrumentationdata) |
||||
- [Utils Methods](#utils) |
||||
* [loadSolcoverJS](#loadsolcoverjs) |
||||
* [assembleFiles](#assemblefiles) |
||||
* [getTempLocations](#gettemplocations) |
||||
* [setupTempFolders](#setuptempfolders) |
||||
* [save](#save) |
||||
* [finish](#finish-1) |
||||
|
||||
# API |
||||
|
||||
**Example** |
||||
```javascript |
||||
const CoverageAPI = require("solidity-coverage/api"); |
||||
const api = new CoverageAPI(options); |
||||
``` |
||||
|
||||
## constructor |
||||
|
||||
Creates a coverage API instance. Configurable. |
||||
|
||||
**Parameters** |
||||
|
||||
- `options` **Object** : API options |
||||
|
||||
| Option <img width=200/>| Type <img width=200/> | Default <img width=1300/> | Description <img width=800/> | |
||||
| ------ | ---- | ------- | ----------- | |
||||
| port | *Number* | 8555 | Port to launch client on | |
||||
| silent | *Boolean* | false | Suppress logging output | |
||||
| client | *Object* | `require("ganache-core")` | JS Ethereum client | |
||||
| providerOptions | *Object* | `{ }` | [ganache-core options][1] | |
||||
| skipFiles | *Array* | `[]` | 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] | |
||||
|
||||
[1]: https://github.com/trufflesuite/ganache-core#options |
||||
[2]: https://istanbul.js.org/docs/advanced/alternative-reporters/ |
||||
|
||||
-------------- |
||||
|
||||
## instrument |
||||
|
||||
Instruments a set of sources to prepare them for compilation. |
||||
|
||||
:warning: **Important:** Instrumented sources must be compiled with **solc optimization OFF** :warning: |
||||
|
||||
**Parameters** |
||||
|
||||
- `contracts` **Object[]**: Array of solidity sources and their paths |
||||
|
||||
Returns **Object[]** in the same format as the `contracts` param, but with sources instrumented. |
||||
|
||||
**Example** |
||||
```javascript |
||||
const contracts = [{ |
||||
source: "contract Simple { uint x = 5; }", |
||||
canonicalPath: "/Users/user/project/contracts/Simple.sol", |
||||
relativePath: "Simple.sol" // Optional, used for pretty printing. |
||||
},...] |
||||
|
||||
const instrumented = api.instrument(contracts) |
||||
``` |
||||
|
||||
-------------- |
||||
|
||||
## ganache |
||||
|
||||
Enables coverage data collection on an in-process ganache server. By default, this method launches |
||||
the server, begins listening on the port specified in the [config](#constructor) (or 8555 if unspecified), and |
||||
returns a url string. When `autoLaunchServer` is false, method returns `ganache.server` so you can control |
||||
the `server.listen` invocation yourself. |
||||
|
||||
**Parameters** |
||||
|
||||
- `client` **Object**: (*Optional*) ganache module |
||||
- `autoLaunchServer` **Boolean**: (*Optional*) |
||||
|
||||
Returns **Promise** Address of server to connect to, or initialized, unlaunched server |
||||
|
||||
**Example** |
||||
```javascript |
||||
const client = require('ganache-cli'); |
||||
|
||||
const api = new CoverageAPI( { client: client } ); |
||||
const address = await api.ganache(); |
||||
|
||||
> http://127.0.0.1:8555 |
||||
|
||||
// Alternatively... |
||||
|
||||
const server = await api.ganache(client, false); |
||||
await pify(server.listen()(8545)); |
||||
``` |
||||
|
||||
-------------- |
||||
|
||||
## report |
||||
|
||||
Generates coverage report using IstanbulJS |
||||
|
||||
**Parameters** |
||||
|
||||
- `istanbulFolder` **String**: (*Optional*) path to folder Istanbul will deposit coverage reports in. |
||||
|
||||
Returns **Promise** |
||||
|
||||
**Example** |
||||
```javascript |
||||
await api.report('./coverage_4A3cd2b'); // Default folder name is 'coverage' |
||||
``` |
||||
|
||||
------------- |
||||
|
||||
## finish |
||||
|
||||
Shuts down coverage-enabled ganache server instance |
||||
|
||||
Returns **Promise** |
||||
|
||||
**Example** |
||||
```javascript |
||||
const client = require('ganache-cli'); |
||||
|
||||
await api.ganache(client); // Server listening... |
||||
await api.finish(); // Server shut down. |
||||
``` |
||||
|
||||
------------- |
||||
|
||||
## getInstrumentationData |
||||
|
||||
Returns a copy of the hit map created during instrumentation. Useful if you'd like to delegate |
||||
coverage collection to multiple processes. |
||||
|
||||
Returns **Object** instrumentation data; |
||||
|
||||
|
||||
**Example** |
||||
```javascript |
||||
const contracts = api.instrument(contracts); |
||||
const data = api.getInstrumentationData(); |
||||
save(data); // Pseudo-code |
||||
``` |
||||
|
||||
------------- |
||||
|
||||
## setInstrumentationData |
||||
|
||||
Sets the hit map object generated during instrumentation. Useful if you'd like |
||||
to collect or convert data to coverage for an instrumentation which was generated |
||||
in a different process. |
||||
|
||||
**Example** |
||||
```javascript |
||||
const data = load(data); // Pseudo-code |
||||
api.setIntrumentationData(data); |
||||
|
||||
// Client will collect data for the loaded map |
||||
const address = await api.ganache(client); |
||||
|
||||
// Or to `report` instrumentation data which was collected in a different process. |
||||
const data = load(data); // Pseudo-code |
||||
api.setInstrumentationData(data); |
||||
|
||||
api.report(); |
||||
``` |
||||
|
||||
---------------------------------------------------------------------------------------------------- |
||||
|
||||
# Utils |
||||
|
||||
```javascript |
||||
const utils = require('solidity-coverage/utils'); |
||||
``` |
||||
|
||||
Many of the utils methods take a `config` object param which |
||||
defines the absolute paths to your project root and contracts directory. |
||||
|
||||
**Example** |
||||
```javascript |
||||
const config = { |
||||
workingDir: process.cwd(), |
||||
contractsDir: path.join(process.cwd(), 'contracts'), |
||||
} |
||||
``` |
||||
------------- |
||||
|
||||
## loadSolcoverJS |
||||
|
||||
Loads `.solcoverjs`. Users may specify [options][7] in a `.solcover.js` config file which your |
||||
application needs to consume. |
||||
|
||||
**Parameters** |
||||
|
||||
- `config` **Object**: [See *config* above](#Utils) |
||||
|
||||
Returns **Object** Normalized coverage config |
||||
|
||||
|
||||
**Example** |
||||
```javascript |
||||
const solcoverJS = utils.loadSolcoverJS(config); |
||||
const api = new CoverageAPI(solcoverJS); |
||||
``` |
||||
|
||||
[7]: https://github.com/sc-forks/solidity-coverage/tree/beta#config-options |
||||
|
||||
------------- |
||||
|
||||
## assembleFiles |
||||
|
||||
Loads contracts from the filesystem in a format that can be passed directly to the |
||||
[api.instrument](#instrument) method. Filters by an optional `skipFiles` parameter. |
||||
|
||||
**Parameters** |
||||
|
||||
- `config` **Object**: [See *config* above](#Utils) |
||||
- `skipFiles` **String[]**: (*Optional*) Array of files or folders to skip |
||||
[See API *constructor*](#constructor) |
||||
|
||||
Returns **Object** with `targets` and `skipped` keys. These are Object arrays of contract sources |
||||
and paths. |
||||
|
||||
**Example** |
||||
```javascript |
||||
const { |
||||
targets, |
||||
skipped |
||||
} = utils.assembleFiles(config, ['Migrations.sol']) |
||||
|
||||
const instrumented = api.instrument(targets); |
||||
``` |
||||
|
||||
-------------- |
||||
|
||||
## getTempLocations |
||||
|
||||
Returns a pair of canonically named temporary directory paths for contracts |
||||
and artifacts. Instrumented assets can be compiled from and written to these so the unit tests can |
||||
use them as sources. |
||||
|
||||
**Parameters** |
||||
|
||||
- `config` **Object**: [See *config* above](#Utils) |
||||
|
||||
Returns **Object** with two absolute paths to disposable folders, `tempContractsDir`, `tempArtifactsDir`. |
||||
These directories are named `.coverage_contracts` and `.coverage_artifacts`. |
||||
|
||||
**Example** |
||||
```javascript |
||||
const { |
||||
tempContractsDir, |
||||
tempArtifactsDir |
||||
} = utils.getTempLocations(config) |
||||
|
||||
utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir) |
||||
|
||||
// Later, you can call `utils.finish` to delete these... |
||||
utils.finish(config, api) |
||||
``` |
||||
|
||||
---------- |
||||
|
||||
## setupTempFolders |
||||
|
||||
Creates temporary directories to store instrumented contracts and their compilation artifacts in. |
||||
|
||||
**Parameters** |
||||
|
||||
- `config` **Object**: [See *config* above](#Utils) |
||||
- `tempContractsDir` **String**: absolute path to temporary contracts directory |
||||
- `tempArtifactsDir` **String**: absolute path to temporary artifacts directory |
||||
|
||||
**Example** |
||||
```javascript |
||||
const { |
||||
tempContractsDir, |
||||
tempArtifactsDir |
||||
} = utils.getTempLocations(config) |
||||
|
||||
utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir); |
||||
``` |
||||
------------- |
||||
|
||||
## save |
||||
|
||||
Writes an array of instrumented sources in the object format returned by |
||||
[api.instrument](#instrument) to a temporary directory. |
||||
|
||||
**Parameters** |
||||
|
||||
- `contracts` **Object[]**: array of contracts & paths generated by [api.instrument](#instrument) |
||||
- `originalDir` **String**: absolute path to original contracts directory |
||||
- `tempDir` **String**: absolute path to temp contracts directory (the destination of the save) |
||||
|
||||
**Example** |
||||
```javascript |
||||
const { |
||||
tempContractsDir, |
||||
tempArtifactsDir |
||||
} = utils.getTempLocations(config) |
||||
|
||||
utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir); |
||||
|
||||
const instrumented = api.instrument(targets); |
||||
|
||||
utils.save(instrumented, config.contractsDir, tempContractsDir); |
||||
``` |
||||
|
||||
------------- |
||||
|
||||
## finish |
||||
|
||||
Deletes temporary folders and shuts the ganache server down. Is tolerant - if folders or ganache |
||||
server don't exist it will return silently. |
||||
|
||||
**Parameters** |
||||
|
||||
- `config` **Object**: [See *config* above](#Utils) |
||||
- `api` **Object**: (*Optional*) coverage api instance whose own `finish` method will be called |
||||
|
||||
Returns **Promise** |
||||
|
||||
**Example** |
||||
```javascript |
||||
await utils.finish(config, api); |
||||
``` |
||||
|
||||
|
||||
|
@ -1,2 +0,0 @@ |
||||
// stub: this is package.json's "main"
|
||||
// truffle require.resolve's this in order to check plugin is installed
|
@ -0,0 +1,142 @@ |
||||
const API = require('./../lib/api'); |
||||
const utils = require('./resources/plugin.utils'); |
||||
const buidlerUtils = require('./resources/buidler.utils'); |
||||
const PluginUI = require('./resources/buidler.ui'); |
||||
|
||||
const pkg = require('./../package.json'); |
||||
const death = require('death'); |
||||
const path = require('path'); |
||||
const Web3 = require('web3'); |
||||
const ganache = require('ganache-cli'); |
||||
|
||||
const { task, types } = require("@nomiclabs/buidler/config"); |
||||
const { ensurePluginLoadedWithUsePlugin } = require("@nomiclabs/buidler/plugins"); |
||||
|
||||
const { |
||||
TASK_TEST, |
||||
TASK_COMPILE, |
||||
} = require("@nomiclabs/buidler/builtin-tasks/task-names"); |
||||
|
||||
ensurePluginLoadedWithUsePlugin(); |
||||
|
||||
function plugin() { |
||||
|
||||
// UI for the task flags...
|
||||
const ui = new PluginUI(); |
||||
|
||||
task("coverage", "Generates a code coverage report for tests") |
||||
|
||||
.addOptionalParam("testfiles", ui.flags.file, null, types.string) |
||||
.addOptionalParam("solcoverjs", ui.flags.solcoverjs, null, types.string) |
||||
.addOptionalParam('temp', ui.flags.temp, null, 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.setupNetwork(env, api, ui); |
||||
|
||||
const client = api.client || ganache; |
||||
const address = await api.ganache(client); |
||||
const web3 = new Web3(address); |
||||
const accounts = await web3.eth.getAccounts(); |
||||
const nodeInfo = await web3.eth.getNodeInfo(); |
||||
const ganacheVersion = nodeInfo.split('/')[1]; |
||||
|
||||
// Set default account
|
||||
network.from = accounts[0]; |
||||
|
||||
// Version Info
|
||||
ui.report('versions', [ |
||||
ganacheVersion, |
||||
pkg.version |
||||
]); |
||||
|
||||
ui.report('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 ? [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; |
||||
|
@ -0,0 +1,85 @@ |
||||
const UI = require('./../../lib/ui').UI; |
||||
|
||||
/** |
||||
* Buidler Plugin logging |
||||
*/ |
||||
class PluginUI extends UI { |
||||
constructor(log){ |
||||
super(log); |
||||
|
||||
this.flags = { |
||||
file: `Path (or glob) defining a subset of tests to run`, |
||||
|
||||
solcoverjs: `Relative path from working directory to config. ` + |
||||
`Useful for monorepo packages that share settings.`, |
||||
|
||||
temp: `Path to a disposable folder to store compilation artifacts in. ` + |
||||
`Useful when your test setup scripts include hard-coded paths to ` + |
||||
`a build directory.`, |
||||
|
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Writes a formatted message via log |
||||
* @param {String} kind message selector |
||||
* @param {String[]} args info to inject into template |
||||
*/ |
||||
report(kind, args=[]){ |
||||
const c = this.chalk; |
||||
const ct = c.bold.green('>'); |
||||
const ds = c.bold.yellow('>'); |
||||
const w = ":warning:"; |
||||
|
||||
const kinds = { |
||||
|
||||
'instr-skip': `\n${c.bold('Coverage skipped for:')}` + |
||||
`\n${c.bold('=====================')}\n`, |
||||
|
||||
'instr-skipped': `${ds} ${c.grey(args[0])}`, |
||||
|
||||
'versions': `${ct} ${c.bold('ganache-core')}: ${args[0]}\n` + |
||||
`${ct} ${c.bold('solidity-coverage')}: v${args[1]}`, |
||||
|
||||
'network': `\n${c.bold('Network Info')}` + |
||||
`\n${c.bold('============')}\n` + |
||||
`${ct} ${c.bold('port')}: ${args[1]}\n` + |
||||
`${ct} ${c.bold('network')}: ${args[0]}\n`, |
||||
|
||||
'port-clash': `${w} ${c.red("The 'port' values in your Buidler url ")}` + |
||||
`${c.red("and .solcover.js are different. Using Buidler's: ")} ${c.bold(args[0])}.\n`, |
||||
|
||||
} |
||||
|
||||
this._write(kinds[kind]); |
||||
} |
||||
|
||||
/** |
||||
* Returns a formatted message. Useful for error message. |
||||
* @param {String} kind message selector |
||||
* @param {String[]} args info to inject into template |
||||
* @return {String} message |
||||
*/ |
||||
generate(kind, args=[]){ |
||||
const c = this.chalk; |
||||
const x = ":x:"; |
||||
|
||||
const kinds = { |
||||
|
||||
'sources-fail': `${c.red('Cannot locate expected contract sources folder: ')} ${args[0]}`, |
||||
|
||||
'solcoverjs-fail': `${c.red('Could not load .solcover.js config file. ')}` + |
||||
`${c.red('This can happen if it has a syntax error or ')}` + |
||||
`${c.red('the path you specified for it is wrong.')}`, |
||||
|
||||
'tests-fail': `${x} ${c.bold(args[0])} ${c.red('test(s) failed under coverage.')}`, |
||||
|
||||
|
||||
} |
||||
|
||||
|
||||
return this._format(kinds[kind]) |
||||
} |
||||
} |
||||
|
||||
module.exports = PluginUI; |
@ -0,0 +1,108 @@ |
||||
const shell = require('shelljs'); |
||||
const globby = require('globby'); |
||||
const pluginUtils = require("./plugin.utils"); |
||||
const path = require('path'); |
||||
const util = require('util'); |
||||
const { createProvider } = require("@nomiclabs/buidler/internal/core/providers/construction"); |
||||
|
||||
|
||||
// =============================
|
||||
// Buidler Plugin Utils
|
||||
// =============================
|
||||
|
||||
/** |
||||
* Normalizes buidler paths / logging for use by the plugin utilities and |
||||
* attaches them to the config |
||||
* @param {BuidlerConfig} config |
||||
* @return {BuidlerConfig} updated config |
||||
*/ |
||||
function normalizeConfig(config, args={}){ |
||||
config.workingDir = config.paths.root; |
||||
config.contractsDir = config.paths.sources; |
||||
config.testDir = config.paths.tests; |
||||
config.artifactsDir = config.paths.artifacts; |
||||
config.logger = config.logger ? config.logger : {log: null}; |
||||
config.solcoverjs = args.solcoverjs |
||||
|
||||
return config; |
||||
} |
||||
|
||||
function setupNetwork(env, api, ui){ |
||||
let networkConfig = {}; |
||||
|
||||
let networkName = (env.buidlerArguments.network !== 'buidlerevm') |
||||
? env.buidlerArguments.network |
||||
: api.defaultNetworkName; |
||||
|
||||
if (networkName !== api.defaultNetworkName){ |
||||
networkConfig = env.config.networks[networkName]; |
||||
|
||||
const configPort = networkConfig.url.split(':')[2]; |
||||
|
||||
// Warn: port conflicts
|
||||
if (api.port !== api.defaultPort && api.port !== configPort){ |
||||
ui.report('port-clash', [ configPort ]) |
||||
} |
||||
|
||||
// Prefer network port
|
||||
api.port = parseInt(configPort); |
||||
} |
||||
|
||||
networkConfig.url = `http://${api.host}:${api.port}`; |
||||
networkConfig.gas = api.gasLimit; |
||||
networkConfig.gasPrice = api.gasPrice; |
||||
|
||||
const provider = createProvider(networkName, networkConfig); |
||||
|
||||
env.config.networks[networkName] = networkConfig; |
||||
env.config.defaultNetwork = networkName; |
||||
|
||||
env.network = { |
||||
name: networkName, |
||||
config: networkConfig, |
||||
provider: provider, |
||||
} |
||||
|
||||
env.ethereum = provider; |
||||
|
||||
// Return a reference so we can set the from account
|
||||
return env.network; |
||||
} |
||||
|
||||
/** |
||||
* Generates a path to a temporary compilation cache directory |
||||
* @param {BuidlerConfig} config |
||||
* @return {String} .../.coverage_cache |
||||
*/ |
||||
function tempCacheDir(config){ |
||||
return path.join(config.paths.root, '.coverage_cache'); |
||||
} |
||||
|
||||
/** |
||||
* Silently removes temporary folders and calls api.finish to shut server down |
||||
* @param {BuidlerConfig} config |
||||
* @param {SolidityCoverage} api |
||||
* @return {Promise} |
||||
*/ |
||||
async function finish(config, api){ |
||||
const { |
||||
tempContractsDir, |
||||
tempArtifactsDir |
||||
} = pluginUtils.getTempLocations(config); |
||||
|
||||
shell.config.silent = true; |
||||
shell.rm('-Rf', tempContractsDir); |
||||
shell.rm('-Rf', tempArtifactsDir); |
||||
shell.rm('-Rf', path.join(config.paths.root, '.coverage_cache')); |
||||
shell.config.silent = false; |
||||
|
||||
if (api) await api.finish(); |
||||
} |
||||
|
||||
module.exports = { |
||||
normalizeConfig: normalizeConfig, |
||||
finish: finish, |
||||
tempCacheDir: tempCacheDir, |
||||
setupNetwork: setupNetwork |
||||
} |
||||
|
@ -0,0 +1,273 @@ |
||||
/** |
||||
* A collection of utilities for common tasks plugins will need in the course |
||||
* of composing a workflow using the solidity-coverage API |
||||
*/ |
||||
|
||||
const PluginUI = require('./truffle.ui'); |
||||
|
||||
const path = require('path'); |
||||
const fs = require('fs-extra'); |
||||
const shell = require('shelljs'); |
||||
const util = require('util') |
||||
|
||||
// ===
|
||||
// UI
|
||||
// ===
|
||||
|
||||
/** |
||||
* Displays a list of skipped contracts |
||||
* @param {TruffleConfig} config |
||||
* @return {Object[]} skipped array of objects generated by `assembleTargets` method |
||||
*/ |
||||
function reportSkipped(config, skipped=[]){ |
||||
let started = false; |
||||
const ui = new PluginUI(config.logger.log); |
||||
|
||||
for (let item of skipped){ |
||||
if (!started) { |
||||
ui.report('instr-skip', []); |
||||
started = true; |
||||
} |
||||
ui.report('instr-skipped', [item.relativePath]); |
||||
} |
||||
} |
||||
|
||||
// ========
|
||||
// File I/O
|
||||
// ========
|
||||
|
||||
/** |
||||
* Loads source |
||||
* @param {String} _path absolute path |
||||
* @return {String} source file |
||||
*/ |
||||
function loadSource(_path){ |
||||
return fs.readFileSync(_path).toString(); |
||||
} |
||||
|
||||
/** |
||||
* Sets up temporary folders for instrumented contracts and their compilation artifacts |
||||
* @param {PlatformConfig} config |
||||
* @param {String} tempContractsDir |
||||
* @param {String} tempArtifactsDir |
||||
*/ |
||||
function setupTempFolders(config, tempContractsDir, tempArtifactsDir){ |
||||
checkContext(config, tempContractsDir, tempArtifactsDir); |
||||
|
||||
shell.mkdir(tempContractsDir); |
||||
shell.mkdir(tempArtifactsDir); |
||||
} |
||||
|
||||
/** |
||||
* Save a set of instrumented files to a temporary directory. |
||||
* @param {Object[]} targets array of targets generated by `assembleTargets` |
||||
* @param {[type]} originalDir absolute path to original contracts directory |
||||
* @param {[type]} tempDir absolute path to temp contracts directory |
||||
*/ |
||||
function save(targets, originalDir, tempDir){ |
||||
let _path; |
||||
for (target of targets) { |
||||
_path = target.canonicalPath.replace(originalDir, tempDir); |
||||
fs.outputFileSync(_path, target.source); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Relativizes an absolute file path, given an absolute parent path |
||||
* @param {String} pathToFile |
||||
* @param {String} pathToParent |
||||
* @return {String} relative path |
||||
*/ |
||||
function toRelativePath(pathToFile, pathToParent){ |
||||
return pathToFile.replace(`${pathToParent}${path.sep}`, ''); |
||||
} |
||||
|
||||
/** |
||||
* Returns a pair of canonically named temporary directory paths for contracts |
||||
* and artifacts. Instrumented assets can be written & compiled to these. |
||||
* Then the unit tests can be run, consuming them as sources. |
||||
* @param {TruffleConfig} config |
||||
* @return {Object} temp paths |
||||
*/ |
||||
function getTempLocations(config){ |
||||
const cwd = config.workingDir; |
||||
const contractsDirName = '.coverage_contracts'; |
||||
const artifactsDirName = config.temp || '.coverage_artifacts'; |
||||
|
||||
return { |
||||
tempContractsDir: path.join(cwd, contractsDirName), |
||||
tempArtifactsDir: path.join(cwd, artifactsDirName) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Checks for existence of contract sources, and sweeps away debris |
||||
* left over from an uncontrolled crash. |
||||
*/ |
||||
function checkContext(config, tempContractsDir, tempArtifactsDir){ |
||||
const ui = new PluginUI(config.logger.log); |
||||
|
||||
if (!shell.test('-e', config.contractsDir)){ |
||||
|
||||
const msg = ui.generate('sources-fail', [config.contractsDir]) |
||||
throw new Error(msg); |
||||
} |
||||
|
||||
if (shell.test('-e', tempContractsDir)){ |
||||
shell.rm('-Rf', tempContractsDir); |
||||
} |
||||
|
||||
if (shell.test('-e', tempArtifactsDir)){ |
||||
shell.rm('-Rf', tempArtifactsDir); |
||||
} |
||||
} |
||||
|
||||
|
||||
// =============================
|
||||
// Instrumentation Set Assembly
|
||||
// =============================
|
||||
|
||||
function assembleFiles(config, skipFiles=[]){ |
||||
let targets; |
||||
let skipFolders; |
||||
let skipped = []; |
||||
|
||||
targets = shell.ls(`${config.contractsDir}/**/*.sol`); |
||||
|
||||
skipFiles = assembleSkipped(config, targets, skipFiles); |
||||
|
||||
return assembleTargets(config, targets, skipFiles) |
||||
} |
||||
|
||||
function assembleTargets(config, targets=[], skipFiles=[]){ |
||||
const skipped = []; |
||||
const filtered = []; |
||||
const cd = config.contractsDir; |
||||
|
||||
for (let target of targets){ |
||||
if (skipFiles.includes(target)){ |
||||
|
||||
skipped.push({ |
||||
canonicalPath: target, |
||||
relativePath: toRelativePath(target, cd), |
||||
source: loadSource(target) |
||||
}) |
||||
|
||||
} else { |
||||
|
||||
filtered.push({ |
||||
canonicalPath: target, |
||||
relativePath: toRelativePath(target, cd), |
||||
source: loadSource(target) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
return { |
||||
skipped: skipped, |
||||
targets: filtered |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Parses the skipFiles option (which also accepts folders) |
||||
*/ |
||||
function assembleSkipped(config, targets, skipFiles=[]){ |
||||
// Make paths absolute
|
||||
skipFiles = skipFiles.map(contract => `${config.contractsDir}/${contract}`); |
||||
|
||||
// Enumerate files in skipped folders
|
||||
const skipFolders = skipFiles.filter(item => path.extname(item) !== '.sol') |
||||
|
||||
for (let folder of skipFolders){ |
||||
for (let target of targets ) { |
||||
if (target.indexOf(folder) === 0) |
||||
skipFiles.push(target); |
||||
} |
||||
}; |
||||
|
||||
return skipFiles; |
||||
} |
||||
|
||||
function loadSolcoverJS(config={}){ |
||||
let solcoverjs; |
||||
let coverageConfig; |
||||
let log = config.logger ? config.logger.log : console.log; |
||||
let ui = new PluginUI(log); |
||||
|
||||
// Handle --solcoverjs flag
|
||||
(config.solcoverjs) |
||||
? solcoverjs = path.join(config.workingDir, config.solcoverjs) |
||||
: solcoverjs = path.join(config.workingDir, '.solcover.js'); |
||||
|
||||
// Catch solcoverjs syntax errors
|
||||
if (shell.test('-e', solcoverjs)){ |
||||
|
||||
try { |
||||
coverageConfig = require(solcoverjs); |
||||
} catch(error){ |
||||
error.message = ui.generate('solcoverjs-fail') + error.message; |
||||
throw new Error(error) |
||||
} |
||||
|
||||
// Config is optional
|
||||
} else { |
||||
coverageConfig = {}; |
||||
} |
||||
|
||||
// Truffle writes to coverage config
|
||||
coverageConfig.log = log; |
||||
coverageConfig.cwd = config.workingDir; |
||||
coverageConfig.originalContractsDir = config.contractsDir; |
||||
|
||||
// Solidity-Coverage writes to Truffle config
|
||||
config.mocha = config.mocha || {}; |
||||
|
||||
if (coverageConfig.mocha && typeof coverageConfig.mocha === 'object'){ |
||||
config.mocha = Object.assign( |
||||
config.mocha, |
||||
coverageConfig.mocha |
||||
); |
||||
} |
||||
|
||||
return coverageConfig; |
||||
} |
||||
|
||||
// ==========================
|
||||
// Finishing / Cleanup
|
||||
// ==========================
|
||||
|
||||
/** |
||||
* Silently removes temporary folders and calls api.finish to shut server down |
||||
* @param {TruffleConfig} config |
||||
* @param {SolidityCoverage} api |
||||
* @return {Promise} |
||||
*/ |
||||
async function finish(config, api){ |
||||
const { |
||||
tempContractsDir, |
||||
tempArtifactsDir |
||||
} = getTempLocations(config); |
||||
|
||||
shell.config.silent = true; |
||||
shell.rm('-Rf', tempContractsDir); |
||||
shell.rm('-Rf', tempArtifactsDir); |
||||
shell.config.silent = false; |
||||
|
||||
if (api) await api.finish(); |
||||
} |
||||
|
||||
module.exports = { |
||||
assembleFiles: assembleFiles, |
||||
assembleSkipped: assembleSkipped, |
||||
assembleTargets: assembleTargets, |
||||
checkContext: checkContext, |
||||
finish: finish, |
||||
getTempLocations: getTempLocations, |
||||
loadSource: loadSource, |
||||
loadSolcoverJS: loadSolcoverJS, |
||||
reportSkipped: reportSkipped, |
||||
save: save, |
||||
toRelativePath: toRelativePath, |
||||
setupTempFolders: setupTempFolders |
||||
} |
@ -0,0 +1,217 @@ |
||||
const PluginUI = require('./truffle.ui'); |
||||
const globalModules = require('global-modules'); |
||||
const TruffleProvider = require('@truffle/provider'); |
||||
const recursive = require('recursive-readdir'); |
||||
const globby = require('globby'); |
||||
const path = require('path'); |
||||
|
||||
// =============================
|
||||
// Truffle Specific Plugin Utils
|
||||
// ==============================
|
||||
|
||||
/** |
||||
* Returns a list of test files to pass to mocha. |
||||
* @param {Object} config truffleConfig |
||||
* @return {String[]} list of files to pass to mocha |
||||
*/ |
||||
async function getTestFilePaths(config){ |
||||
let target; |
||||
let ui = new PluginUI(config.logger.log); |
||||
|
||||
|
||||
// Handle --file <path|glob> cli option (subset of tests)
|
||||
(typeof config.file === 'string') |
||||
? target = globby.sync([config.file]) |
||||
: target = await recursive(config.testDir); |
||||
|
||||
// Filter native solidity tests and warn that they're skipped
|
||||
const solregex = /.*\.(sol)$/; |
||||
const hasSols = target.filter(f => f.match(solregex) != null); |
||||
|
||||
if (hasSols.length > 0) ui.report('sol-tests', [hasSols.length]); |
||||
|
||||
// Return list of test files
|
||||
const testregex = /.*\.(js|ts|es|es6|jsx)$/; |
||||
return target.filter(f => f.match(testregex) != null); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Configures the network. Runs before the server is launched. |
||||
* User can request a network from truffle-config with "--network <name>". |
||||
* There are overlapiing options in solcoverjs (like port and providerOptions.network_id). |
||||
* Where there are mismatches user is warned & the truffle network settings are preferred. |
||||
* |
||||
* Also generates a default config & sets the default gas high / gas price low. |
||||
* |
||||
* @param {TruffleConfig} config |
||||
* @param {SolidityCoverage} api |
||||
*/ |
||||
function setNetwork(config, api){ |
||||
const ui = new PluginUI(config.logger.log); |
||||
|
||||
// --network <network-name>
|
||||
if (config.network){ |
||||
const network = config.networks[config.network]; |
||||
|
||||
// Check network:
|
||||
if (!network){ |
||||
throw new Error(ui.generate('no-network', [config.network])); |
||||
} |
||||
|
||||
// Check network id
|
||||
if (!isNaN(parseInt(network.network_id))){ |
||||
|
||||
// Warn: non-matching provider options id and network id
|
||||
if (api.providerOptions.network_id && |
||||
api.providerOptions.network_id !== parseInt(network.network_id)){ |
||||
|
||||
ui.report('id-clash', [ parseInt(network.network_id) ]); |
||||
} |
||||
|
||||
// Prefer network defined id.
|
||||
api.providerOptions.network_id = parseInt(network.network_id); |
||||
|
||||
} else { |
||||
network.network_id = "*"; |
||||
} |
||||
|
||||
// Check port: use solcoverjs || default if undefined
|
||||
if (!network.port) { |
||||
ui.report('no-port', [api.port]); |
||||
network.port = api.port; |
||||
} |
||||
|
||||
// Warn: port conflicts
|
||||
if (api.port !== api.defaultPort && api.port !== network.port){ |
||||
ui.report('port-clash', [ network.port ]) |
||||
} |
||||
|
||||
// Prefer network port if defined;
|
||||
api.port = network.port; |
||||
|
||||
network.gas = api.gasLimit; |
||||
network.gasPrice = api.gasPrice; |
||||
|
||||
setOuterConfigKeys(config, api, network.network_id); |
||||
return; |
||||
} |
||||
|
||||
// Default Network Configuration
|
||||
config.network = 'soliditycoverage'; |
||||
setOuterConfigKeys(config, api, "*"); |
||||
|
||||
config.networks[config.network] = { |
||||
network_id: "*", |
||||
port: api.port, |
||||
host: api.host, |
||||
gas: api.gasLimit, |
||||
gasPrice: api.gasPrice |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sets the default `from` account field in the truffle network that will be used. |
||||
* This needs to be done after accounts are fetched from the launched client. |
||||
* @param {TruffleConfig} config |
||||
* @param {Array} accounts |
||||
*/ |
||||
function setNetworkFrom(config, accounts){ |
||||
if (!config.networks[config.network].from){ |
||||
config.networks[config.network].from = accounts[0]; |
||||
} |
||||
} |
||||
|
||||
// Truffle complains that these outer keys *are not* set when running plugin fn directly.
|
||||
// But throws saying they *cannot* be manually set when running as truffle command.
|
||||
function setOuterConfigKeys(config, api, id){ |
||||
try { |
||||
config.network_id = id; |
||||
config.port = api.port; |
||||
config.host = api.host; |
||||
config.provider = TruffleProvider.create(config); |
||||
} catch (err){} |
||||
} |
||||
|
||||
/** |
||||
* Tries to load truffle module library and reports source. User can force use of |
||||
* a non-local version using cli flags (see option). It's necessary to maintain |
||||
* a fail-safe lib because feature was only introduced in 5.0.30. Load order is: |
||||
* |
||||
* 1. local node_modules |
||||
* 2. global node_modules |
||||
* 3. fail-safe (truffle lib v 5.0.31 at ./plugin-assets/truffle.library) |
||||
* |
||||
* @param {Object} truffleConfig config |
||||
* @return {Module} |
||||
*/ |
||||
function loadLibrary(config){ |
||||
const ui = new PluginUI(config.logger.log); |
||||
|
||||
// Local
|
||||
try { |
||||
if (config.useGlobalTruffle || config.usePluginTruffle) throw null; |
||||
|
||||
const lib = require("truffle"); |
||||
ui.report('lib-local'); |
||||
return lib; |
||||
|
||||
} catch(err) {}; |
||||
|
||||
// Global
|
||||
try { |
||||
if (config.usePluginTruffle) throw null; |
||||
|
||||
const globalTruffle = path.join(globalModules, 'truffle'); |
||||
const lib = require(globalTruffle); |
||||
ui.report('lib-global'); |
||||
return lib; |
||||
|
||||
} catch(err) {}; |
||||
|
||||
// Plugin Copy @ v 5.0.31
|
||||
try { |
||||
if (config.forceLibFailure) throw null; // For err unit testing
|
||||
|
||||
ui.report('lib-warn'); |
||||
return require("./truffle.library") |
||||
|
||||
} catch(err) { |
||||
throw new Error(ui.generate('lib-fail', [err])); |
||||
}; |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Maps truffle specific keys for the paths to things like sources to the generic |
||||
* keys required by the plugin utils |
||||
* @return {Object} truffle-config.js |
||||
*/ |
||||
function normalizeConfig(config){ |
||||
config.workingDir = config.working_directory; |
||||
config.contractsDir = config.contracts_directory; |
||||
config.testDir = config.test_directory; |
||||
config.artifactsDir = config.build_directory; |
||||
|
||||
// eth-gas-reporter freezes the in-process client because it uses sync calls
|
||||
if (typeof config.mocha === "object" && config.mocha.reporter === 'eth-gas-reporter'){ |
||||
config.mocha.reporter = 'spec'; |
||||
delete config.mocha.reporterOptions; |
||||
} |
||||
|
||||
// Truffle V4 style solc settings are honored over V5 settings. Apparently it's common
|
||||
// for both to be present in the same config (as an error).
|
||||
if (typeof config.solc === "object" ){ |
||||
config.solc.optimizer = { enabled: false }; |
||||
} |
||||
|
||||
return config; |
||||
} |
||||
|
||||
module.exports = { |
||||
getTestFilePaths: getTestFilePaths, |
||||
setNetwork: setNetwork, |
||||
setNetworkFrom: setNetworkFrom, |
||||
loadLibrary: loadLibrary, |
||||
normalizeConfig: normalizeConfig, |
||||
} |
@ -0,0 +1,67 @@ |
||||
#!/usr/bin/env bash |
||||
# |
||||
# E2E CI: installs PR candidate on sc-forks/buidler-e2e (a simple example, |
||||
# similar to Metacoin) and runs coverage |
||||
# |
||||
|
||||
set -o errexit |
||||
|
||||
# Get rid of any caches |
||||
sudo rm -rf node_modules |
||||
echo "NVM CURRENT >>>>>" && nvm current |
||||
|
||||
# Use PR env variables (for forks) or fallback on local if PR not available |
||||
SED_REGEX="s/git@github.com:/https:\/\/github.com\//" |
||||
|
||||
if [[ -v CIRCLE_PR_REPONAME ]]; then |
||||
PR_PATH="https://github.com/$CIRCLE_PR_USERNAME/$CIRCLE_PR_REPONAME#$CIRCLE_SHA1" |
||||
else |
||||
PR_PATH=$(echo "$CIRCLE_REPOSITORY_URL#$CIRCLE_SHA1" | sudo sed "$SED_REGEX") |
||||
fi |
||||
|
||||
echo "PR_PATH >>>>> $PR_PATH" |
||||
|
||||
echo "" |
||||
echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" |
||||
echo "Simple buidler/buidler-trufflev5 " |
||||
echo ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" |
||||
echo "" |
||||
|
||||
# Install buidler e2e test |
||||
git clone https://github.com/sc-forks/buidler-e2e.git |
||||
cd buidler-e2e |
||||
npm install |
||||
|
||||
# Install and run solidity-coverage @ PR |
||||
npm install --save-dev $PR_PATH |
||||
cat package.json |
||||
|
||||
npx buidler coverage |
||||
|
||||
# Test that coverage/ was generated |
||||
if [ ! -d "coverage" ]; then |
||||
echo "ERROR: no coverage folder was created for buidler-trufflev5." |
||||
exit 1 |
||||
fi |
||||
|
||||
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 |
||||
npm install |
||||
|
||||
# Install and run solidity-coverage @ PR |
||||
npm install --save-dev $PR_PATH |
||||
cat package.json |
||||
|
||||
npx buidler coverage |
||||
|
||||
# Test that coverage/ was generated |
||||
if [ ! -d "coverage" ]; then |
||||
echo "ERROR: no coverage folder was created for buidler-ethers." |
||||
exit 1 |
||||
fi |
@ -0,0 +1,39 @@ |
||||
#!/usr/bin/env bash |
||||
# |
||||
# E2E CI: installs PR candidate on sc-forks/buidler-e2e (a simple example, |
||||
# similar to Metacoin) and runs coverage |
||||
# |
||||
|
||||
set -o errexit |
||||
|
||||
# Get rid of any caches |
||||
sudo rm -rf node_modules |
||||
echo "NVM CURRENT >>>>>" && nvm current |
||||
|
||||
# Use PR env variables (for forks) or fallback on local if PR not available |
||||
SED_REGEX="s/git@github.com:/https:\/\/github.com\//" |
||||
|
||||
if [[ -v CIRCLE_PR_REPONAME ]]; then |
||||
PR_PATH="https://github.com/$CIRCLE_PR_USERNAME/$CIRCLE_PR_REPONAME#$CIRCLE_SHA1" |
||||
else |
||||
PR_PATH=$(echo "$CIRCLE_REPOSITORY_URL#$CIRCLE_SHA1" | sudo sed "$SED_REGEX") |
||||
fi |
||||
|
||||
echo "PR_PATH >>>>> $PR_PATH" |
||||
|
||||
# Install buidler e2e test |
||||
git clone https://github.com/sc-forks/moloch.git |
||||
cd moloch |
||||
npm install |
||||
npm uninstall --save-dev solidity-coverage |
||||
|
||||
# Install and run solidity-coverage @ PR |
||||
# Should run on network 'localhost' |
||||
npm install --save-dev $PR_PATH |
||||
npm run coverage |
||||
|
||||
# Test that coverage/ was generated |
||||
if [ ! -d "coverage" ]; then |
||||
echo "ERROR: no coverage folder was created." |
||||
exit 1 |
||||
fi |
@ -0,0 +1,8 @@ |
||||
const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); |
||||
loadPluginFile(__dirname + "/../plugins/buidler.plugin"); |
||||
usePlugin("@nomiclabs/buidler-truffle5"); |
||||
|
||||
module.exports={ |
||||
defaultNetwork: "buidlerevm", |
||||
logger: process.env.SILENT ? { log: () => {} } : console, |
||||
}; |
@ -0,0 +1 @@ |
||||
!node_modules |
@ -0,0 +1,5 @@ |
||||
module.exports = { |
||||
client: require('ganache-cli'), |
||||
silent: process.env.SILENT ? true : false, |
||||
istanbulReporter: ['json-summary', 'text'], |
||||
} |
@ -0,0 +1,8 @@ |
||||
const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); |
||||
loadPluginFile(__dirname + "/../plugins/buidler.plugin"); |
||||
usePlugin("@nomiclabs/buidler-truffle5"); |
||||
|
||||
module.exports={ |
||||
defaultNetwork: "buidlerevm", |
||||
logger: process.env.SILENT ? { log: () => {} } : console, |
||||
}; |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
|
||||
contract ContractA { |
||||
uint x; |
||||
constructor() public { |
||||
} |
||||
|
||||
function sendFn() public { |
||||
x = 5; |
||||
} |
||||
|
||||
function callFn() public pure returns (uint){ |
||||
uint y = 5; |
||||
return y; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
|
||||
contract ContractB { |
||||
uint x; |
||||
constructor() public { |
||||
} |
||||
|
||||
function sendFn() public { |
||||
x = 5; |
||||
} |
||||
|
||||
function callFn() public pure returns (uint){ |
||||
uint y = 5; |
||||
return y; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
|
||||
contract ContractC { |
||||
uint x; |
||||
constructor() public { |
||||
} |
||||
|
||||
function sendFn() public { |
||||
x = 5; |
||||
} |
||||
|
||||
function callFn() public pure returns (uint){ |
||||
uint y = 5; |
||||
return y; |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
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); |
||||
} |
||||
} |
@ -0,0 +1,15 @@ |
||||
const ContractA = artifacts.require("ContractA"); |
||||
|
||||
contract("contracta", function(accounts) { |
||||
let instance; |
||||
|
||||
before(async () => instance = await ContractA.new()) |
||||
|
||||
it('sends [ @skipForCoverage ]', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
it('calls [ @skipForCoverage ]', async function(){ |
||||
await instance.callFn(); |
||||
}) |
||||
}); |
@ -0,0 +1,15 @@ |
||||
const ContractB = artifacts.require("ContractB"); |
||||
|
||||
contract("contractB [ @skipForCoverage ]", function(accounts) { |
||||
let instance; |
||||
|
||||
before(async () => instance = await ContractB.new()) |
||||
|
||||
it('sends', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
it('calls', async function(){ |
||||
await instance.callFn(); |
||||
}) |
||||
}); |
@ -0,0 +1,20 @@ |
||||
const ContractC = artifacts.require("ContractC"); |
||||
|
||||
contract("contractc", function(accounts) { |
||||
let instance; |
||||
|
||||
before(async () => instance = await ContractC.new()) |
||||
|
||||
it('sends', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
it('calls', async function(){ |
||||
await instance.callFn(); |
||||
}) |
||||
|
||||
it('sends', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
}); |
@ -0,0 +1,7 @@ |
||||
module.exports = { |
||||
networks: {}, |
||||
mocha: {}, |
||||
compilers: { |
||||
solc: {} |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); |
||||
loadPluginFile(__dirname + "/../plugins/buidler.plugin"); |
||||
usePlugin("@nomiclabs/buidler-truffle5"); |
||||
|
||||
module.exports={ |
||||
defaultNetwork: "buidlerevm", |
||||
logger: process.env.SILENT ? { log: () => {} } : console, |
||||
}; |
@ -0,0 +1,5 @@ |
||||
pragma solidity >=0.4.21 <0.6.0; |
||||
|
||||
import "package/AnotherImport.sol"; |
||||
|
||||
interface Void {} |
@ -0,0 +1,10 @@ |
||||
pragma solidity >=0.4.21 <0.6.0; |
||||
|
||||
contract AnotherImport { |
||||
uint x; |
||||
constructor() public {} |
||||
|
||||
function isNodeModulesMethod() public { |
||||
x = 5; |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
{} |
@ -0,0 +1,7 @@ |
||||
const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); |
||||
loadPluginFile(__dirname + "/../plugins/buidler.plugin"); |
||||
usePlugin("@nomiclabs/buidler-truffle5"); |
||||
|
||||
module.exports={ |
||||
logger: process.env.SILENT ? { log: () => {} } : console, |
||||
}; |
@ -0,0 +1 @@ |
||||
module.exports={}; |
@ -0,0 +1,4 @@ |
||||
module.exports = { |
||||
"silent": false, |
||||
"istanbulReporter": [ "json-summary", "text"] |
||||
} |
@ -0,0 +1,8 @@ |
||||
const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); |
||||
loadPluginFile(__dirname + "/../plugins/buidler.plugin"); |
||||
usePlugin("@nomiclabs/buidler-truffle5"); |
||||
|
||||
module.exports={ |
||||
defaultNetwork: "buidlerevm", |
||||
logger: process.env.SILENT ? { log: () => {} } : console, |
||||
}; |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
|
||||
contract ContractA { |
||||
uint x; |
||||
constructor() public { |
||||
} |
||||
|
||||
function sendFn() public { |
||||
x = 5; |
||||
} |
||||
|
||||
function callFn() public pure returns (uint){ |
||||
uint y = 5; |
||||
return y; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
|
||||
contract ContractB { |
||||
uint x; |
||||
constructor() public { |
||||
} |
||||
|
||||
function sendFn() public { |
||||
x = 5; |
||||
} |
||||
|
||||
function callFn() public pure returns (uint){ |
||||
uint y = 5; |
||||
return y; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
|
||||
contract ContractC { |
||||
uint x; |
||||
constructor() public { |
||||
} |
||||
|
||||
function sendFn() public { |
||||
x = 5; |
||||
} |
||||
|
||||
function callFn() public pure returns (uint){ |
||||
uint y = 5; |
||||
return y; |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
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); |
||||
} |
||||
} |
@ -0,0 +1,15 @@ |
||||
const ContractA = artifacts.require("ContractA"); |
||||
|
||||
contract("contracta", function(accounts) { |
||||
let instance; |
||||
|
||||
before(async () => instance = await ContractA.new()) |
||||
|
||||
it('sends [ @skipForCoverage ]', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
it('calls [ @skipForCoverage ]', async function(){ |
||||
await instance.callFn(); |
||||
}) |
||||
}); |
@ -0,0 +1,15 @@ |
||||
const ContractB = artifacts.require("ContractB"); |
||||
|
||||
contract("contractB [ @skipForCoverage ]", function(accounts) { |
||||
let instance; |
||||
|
||||
before(async () => instance = await ContractB.new()) |
||||
|
||||
it('sends', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
it('calls', async function(){ |
||||
await instance.callFn(); |
||||
}) |
||||
}); |
@ -0,0 +1,20 @@ |
||||
const ContractC = artifacts.require("ContractC"); |
||||
|
||||
contract("contractc", function(accounts) { |
||||
let instance; |
||||
|
||||
before(async () => instance = await ContractC.new()) |
||||
|
||||
it('sends', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
it('calls', async function(){ |
||||
await instance.callFn(); |
||||
}) |
||||
|
||||
it('sends', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
}); |
@ -0,0 +1,7 @@ |
||||
module.exports = { |
||||
networks: {}, |
||||
mocha: {}, |
||||
compilers: { |
||||
solc: {} |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); |
||||
loadPluginFile(__dirname + "/../plugins/buidler.plugin"); |
||||
usePlugin("@nomiclabs/buidler-truffle5"); |
||||
|
||||
module.exports={ |
||||
logger: process.env.SILENT ? { log: () => {} } : console, |
||||
}; |
@ -0,0 +1,8 @@ |
||||
const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); |
||||
loadPluginFile(__dirname + "/../plugins/buidler.plugin"); |
||||
usePlugin("@nomiclabs/buidler-truffle5"); |
||||
|
||||
module.exports={ |
||||
defaultNetwork: "buidlerevm", |
||||
logger: process.env.SILENT ? { log: () => {} } : console, |
||||
}; |
@ -0,0 +1,7 @@ |
||||
const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); |
||||
loadPluginFile(__dirname + "/../plugins/buidler.plugin"); |
||||
usePlugin("@nomiclabs/buidler-truffle5"); |
||||
|
||||
module.exports={ |
||||
defaultNetwork: "buidlerevm", |
||||
}; |
@ -0,0 +1,4 @@ |
||||
module.exports = { |
||||
"silent": false, |
||||
"istanbulReporter": [ "json-summary", "text"] |
||||
} |
@ -0,0 +1,8 @@ |
||||
const { loadPluginFile } = require("@nomiclabs/buidler/plugins-testing"); |
||||
loadPluginFile(__dirname + "/../plugins/buidler.plugin"); |
||||
usePlugin("@nomiclabs/buidler-truffle5"); |
||||
|
||||
module.exports={ |
||||
defaultNetwork: "buidlerevm", |
||||
logger: process.env.SILENT ? { log: () => {} } : console, |
||||
}; |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
|
||||
contract ContractA { |
||||
uint x; |
||||
constructor() public { |
||||
} |
||||
|
||||
function sendFn() public { |
||||
x = 5; |
||||
} |
||||
|
||||
function callFn() public pure returns (uint){ |
||||
uint y = 5; |
||||
return y; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
|
||||
contract ContractB { |
||||
uint x; |
||||
constructor() public { |
||||
} |
||||
|
||||
function sendFn() public { |
||||
x = 5; |
||||
} |
||||
|
||||
function callFn() public pure returns (uint){ |
||||
uint y = 5; |
||||
return y; |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
|
||||
contract ContractC { |
||||
uint x; |
||||
constructor() public { |
||||
} |
||||
|
||||
function sendFn() public { |
||||
x = 5; |
||||
} |
||||
|
||||
function callFn() public pure returns (uint){ |
||||
uint y = 5; |
||||
return y; |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
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); |
||||
} |
||||
} |
@ -0,0 +1,15 @@ |
||||
const ContractA = artifacts.require("ContractA"); |
||||
|
||||
contract("contracta", function(accounts) { |
||||
let instance; |
||||
|
||||
before(async () => instance = await ContractA.new()) |
||||
|
||||
it('sends [ @skipForCoverage ]', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
it('calls [ @skipForCoverage ]', async function(){ |
||||
await instance.callFn(); |
||||
}) |
||||
}); |
@ -0,0 +1,15 @@ |
||||
const ContractB = artifacts.require("ContractB"); |
||||
|
||||
contract("contractB [ @skipForCoverage ]", function(accounts) { |
||||
let instance; |
||||
|
||||
before(async () => instance = await ContractB.new()) |
||||
|
||||
it('sends', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
it('calls', async function(){ |
||||
await instance.callFn(); |
||||
}) |
||||
}); |
@ -0,0 +1,20 @@ |
||||
const ContractC = artifacts.require("ContractC"); |
||||
|
||||
contract("contractc", function(accounts) { |
||||
let instance; |
||||
|
||||
before(async () => instance = await ContractC.new()) |
||||
|
||||
it('sends', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
it('calls', async function(){ |
||||
await instance.callFn(); |
||||
}) |
||||
|
||||
it('sends', async function(){ |
||||
await instance.sendFn(); |
||||
}); |
||||
|
||||
}); |
@ -0,0 +1,7 @@ |
||||
module.exports = { |
||||
networks: {}, |
||||
mocha: {}, |
||||
compilers: { |
||||
solc: {} |
||||
} |
||||
} |
@ -1,8 +1,7 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract */ |
||||
const Empty = artifacts.require('Empty'); |
||||
|
||||
const Empty = artifacts.require('./Empty.sol'); |
||||
|
||||
contract('Empty', () => { |
||||
it('should deploy', () => Empty.deployed()); |
||||
contract('Empty', function() { |
||||
it('should deploy', async function (){ |
||||
await Empty.new() |
||||
}); |
||||
}); |
||||
|
@ -1,11 +1,11 @@ |
||||
const Owned = artifacts.require('./Owned.sol'); |
||||
const Proxy = artifacts.require('./Proxy.sol'); |
||||
const Owned = artifacts.require('Owned'); |
||||
const Proxy = artifacts.require('Proxy'); |
||||
|
||||
contract('Proxy', accounts => { |
||||
it('Should compile and run when one contract inherits from another', () => Owned.deployed() |
||||
.then(() => Proxy.deployed()) |
||||
.then(instance => instance.isOwner.call({ |
||||
from: accounts[0], |
||||
})) |
||||
.then(val => assert.equal(val, true))); |
||||
it('when one contract inherits from another', async function(){ |
||||
const owned = await Owned.new(); |
||||
const proxy = await Proxy.new(); |
||||
const val = await proxy.isOwner({from: accounts[0]}); |
||||
assert.equal(val, true); |
||||
}) |
||||
}); |
||||
|
@ -1,17 +1,9 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const OnlyCall = artifacts.require('./OnlyCall.sol'); |
||||
const OnlyCall = artifacts.require('OnlyCall'); |
||||
|
||||
contract('OnlyCall', accounts => { |
||||
it('should return val + 2', done => { |
||||
OnlyCall.deployed().then(instance => { |
||||
instance.addTwo.call(5, { |
||||
from: accounts[0], |
||||
}).then(val => { |
||||
assert.equal(val, 7); |
||||
done(); |
||||
}); |
||||
}); |
||||
}); |
||||
it('should return val + 2', async function(){ |
||||
const onlycall = await OnlyCall.new(); |
||||
const val = await onlycall.addTwo(5); |
||||
assert.equal(val.toNumber(), 7); |
||||
}) |
||||
}); |
||||
|
@ -1,17 +1,17 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const PureView = artifacts.require('./PureView.sol'); |
||||
const PureView = artifacts.require('PureView'); |
||||
|
||||
contract('PureView', accounts => { |
||||
|
||||
it('calls a pure function', async function(){ |
||||
const instance = await PureView.deployed(); |
||||
const instance = await PureView.new(); |
||||
const value = await instance.isPure(4,5); |
||||
}); |
||||
|
||||
it('calls a view function', async function(){ |
||||
const instance = await PureView.deployed(); |
||||
const instance = await PureView.new(); |
||||
const value = await instance.isView(); |
||||
}) |
||||
}); |
@ -1,17 +0,0 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const Simple = artifacts.require('./Simple.sol'); |
||||
|
||||
// This test is constructed correctly but the SimpleError.sol has a syntax error
|
||||
contract('SimpleError', () => { |
||||
it('should set x to 5', () => { |
||||
let simple; |
||||
return Simple.deployed().then(instance => { |
||||
simple = instance; |
||||
return simple.test(5); |
||||
}) |
||||
.then(() => simple.getX.call()) |
||||
.then(val => assert.equal(val, 5)); |
||||
}); |
||||
}); |
@ -1,11 +1,11 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract */ |
||||
|
||||
var Simple = artifacts.require('./Simple.sol'); |
||||
var Simple = artifacts.require('Simple'); |
||||
|
||||
// This test should break truffle because it has a syntax error.
|
||||
contract('Simple', () => { |
||||
it('should crash', function(){ |
||||
return Simple.deployed().then.why. |
||||
return Simple.new().then.why. |
||||
}) |
||||
}) |
@ -1,16 +1,20 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const Simple = artifacts.require('./Simple.sol'); |
||||
const Simple = artifacts.require('Simple'); |
||||
|
||||
contract('Simple', () => { |
||||
it('should set x to 5', () => { |
||||
let simple; |
||||
return Simple.deployed().then(instance => { |
||||
simple = instance; |
||||
return simple.test(5); |
||||
}) |
||||
.then(() => simple.getX.call()) |
||||
.then(val => assert.equal(val.toNumber(), 4)); // <-- Wrong result: test fails
|
||||
it('should set x to 5', async function() { |
||||
let simple = await Simple.new(); |
||||
await simple.test(5); |
||||
const val = await simple.getX(); |
||||
assert.equal(val.toNumber(), 4) // <-- Wrong result: test fails
|
||||
}); |
||||
|
||||
it('should set x to 2', async function() { |
||||
let simple = await Simple.new(); |
||||
await simple.test(5); |
||||
const val = await simple.getX(); |
||||
assert.equal(val.toNumber(), 2) // <-- Wrong result: test fails
|
||||
}); |
||||
}); |
||||
|
@ -0,0 +1,101 @@ |
||||
const assert = require('assert'); |
||||
const detect = require('detect-port'); |
||||
const Ganache = require('ganache-cli'); |
||||
|
||||
const util = require('./../util/util.js'); |
||||
const API = require('./../../api.js'); |
||||
const utils = require('./../../utils.js'); |
||||
|
||||
describe('api', () => { |
||||
let opts; |
||||
|
||||
beforeEach(() => opts = {silent: true}) |
||||
|
||||
it('getInstrumentationData', function(){ |
||||
const api = new API(opts); |
||||
const canonicalPath = 'statements/single.sol' |
||||
const source = util.getCode(canonicalPath); |
||||
|
||||
api.instrument([{ |
||||
source: source, |
||||
canonicalPath: canonicalPath |
||||
}]); |
||||
|
||||
const data = api.getInstrumentationData(); |
||||
|
||||
const hash = Object.keys(data)[0]; |
||||
assert(data[hash].hits === 0); |
||||
}); |
||||
|
||||
it('setInstrumentationData', function(){ |
||||
let api = new API(opts); |
||||
|
||||
const canonicalPath = 'statements/single.sol' |
||||
const source = util.getCode(canonicalPath); |
||||
|
||||
api.instrument([{ |
||||
source: source, |
||||
canonicalPath: canonicalPath |
||||
}]); |
||||
|
||||
const cloneA = api.getInstrumentationData(); |
||||
const hash = Object.keys(cloneA)[0]; |
||||
|
||||
// Verify cloning
|
||||
cloneA[hash].hits = 5; |
||||
const cloneB = api.getInstrumentationData(); |
||||
assert(cloneB[hash].hits === 0); |
||||
|
||||
// Verify setting
|
||||
api = new API(opts); |
||||
api.instrument([{ |
||||
source: source, |
||||
canonicalPath: canonicalPath |
||||
}]); |
||||
|
||||
api.setInstrumentationData(cloneA); |
||||
const cloneC = api.getInstrumentationData(); |
||||
assert(cloneC[hash].hits === 5); |
||||
}); |
||||
|
||||
it('ganache: autoLaunchServer === false', async function(){ |
||||
const api = new API(opts); |
||||
const port = api.port; |
||||
const server = await api.ganache(Ganache, false); |
||||
|
||||
assert(typeof port === 'number') |
||||
assert(typeof server === 'object'); |
||||
assert(typeof server.listen === 'function'); |
||||
|
||||
const freePort = await detect(port); |
||||
|
||||
assert(freePort === port); |
||||
}); |
||||
|
||||
it('config: autoLaunchServer: false', async function(){ |
||||
opts.autoLaunchServer = false; |
||||
|
||||
const api = new API(opts); |
||||
const port = api.port; |
||||
const server = await api.ganache(Ganache); |
||||
|
||||
assert(typeof port === 'number') |
||||
assert(typeof server === 'object'); |
||||
assert(typeof server.listen === 'function'); |
||||
|
||||
const freePort = await detect(port); |
||||
|
||||
assert(freePort === port); |
||||
}) |
||||
|
||||
it('utils', async function(){ |
||||
assert(utils.assembleFiles !== undefined) |
||||
assert(utils.checkContext !== undefined) |
||||
assert(utils.finish !== undefined) |
||||
assert(utils.getTempLocations !== undefined) |
||||
assert(utils.setupTempFolders !== undefined) |
||||
assert(utils.loadSource !== undefined) |
||||
assert(utils.loadSolcoverJS !== undefined) |
||||
assert(utils.save !== undefined) |
||||
}); |
||||
}) |
@ -0,0 +1,180 @@ |
||||
const assert = require('assert'); |
||||
const fs = require('fs'); |
||||
const path = require('path') |
||||
const pify = require('pify') |
||||
const shell = require('shelljs'); |
||||
const ganache = require('ganache-cli') |
||||
|
||||
const verify = require('../../util/verifiers') |
||||
const mock = require('../../util/integration'); |
||||
const plugin = require('../../../plugins/buidler.plugin'); |
||||
|
||||
// =======
|
||||
// Errors
|
||||
// =======
|
||||
|
||||
describe('Buidler Plugin: error cases', function() { |
||||
let buidlerConfig; |
||||
let solcoverConfig; |
||||
|
||||
beforeEach(() => { |
||||
mock.clean(); |
||||
|
||||
mock.loggerOutput.val = ''; |
||||
solcoverConfig = { skipFiles: ['Migrations.sol']}; |
||||
buidlerConfig = mock.getDefaultBuidlerConfig(); |
||||
verify.cleanInitialState(); |
||||
}) |
||||
|
||||
afterEach(() => { |
||||
mock.buidlerTearDownEnv(); |
||||
mock.clean(); |
||||
}); |
||||
|
||||
it('project contains no contract sources folder', async function() { |
||||
mock.installFullProject('no-sources'); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
try { |
||||
await this.env.run("coverage"); |
||||
assert.fail() |
||||
} catch(err){ |
||||
assert( |
||||
err.message.includes('Cannot locate expected contract sources folder'), |
||||
`Should error when contract sources cannot be found:: ${err.message}` |
||||
); |
||||
|
||||
assert( |
||||
err.message.includes('sc_temp/contracts'), |
||||
`Error message should contain path:: ${err.message}` |
||||
); |
||||
} |
||||
|
||||
verify.coverageNotGenerated(buidlerConfig); |
||||
}); |
||||
|
||||
it('.solcover.js has syntax error', async function(){ |
||||
mock.installFullProject('bad-solcoverjs'); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
try { |
||||
await this.env.run("coverage"); |
||||
assert.fail() |
||||
} catch(err){ |
||||
assert( |
||||
err.message.includes('Could not load .solcover.js config file.'), |
||||
`Should notify when solcoverjs has syntax error:: ${err.message}` |
||||
); |
||||
} |
||||
|
||||
verify.coverageNotGenerated(buidlerConfig); |
||||
}) |
||||
|
||||
it('.solcover.js has incorrectly formatted option', async function(){ |
||||
solcoverConfig.port = "Antwerpen"; |
||||
|
||||
mock.install('Simple', 'simple.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
try { |
||||
await this.env.run("coverage"); |
||||
assert.fail() |
||||
} catch (err) { |
||||
assert( |
||||
err.message.includes('config option'), |
||||
`Should error on incorrect config options: ${err.message}` |
||||
); |
||||
} |
||||
}); |
||||
|
||||
it('tries to launch with a port already in use', async function(){ |
||||
const server = ganache.server(); |
||||
|
||||
mock.install('Simple', 'simple.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await pify(server.listen)(8555); |
||||
|
||||
try { |
||||
await this.env.run("coverage"); |
||||
assert.fail(); |
||||
} catch(err){ |
||||
assert( |
||||
err.message.includes('is already in use') && |
||||
err.message.includes('lsof'), |
||||
`Should error on port-in-use with advice: ${err.message}` |
||||
) |
||||
} |
||||
|
||||
await pify(server.close)(); |
||||
}); |
||||
|
||||
it('uses an invalid istanbul reporter', async function() { |
||||
solcoverConfig = { |
||||
silent: process.env.SILENT ? true : false, |
||||
istanbulReporter: ['does-not-exist'] |
||||
}; |
||||
|
||||
mock.install('Simple', 'simple.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
try { |
||||
await this.env.run("coverage"); |
||||
assert.fail(); |
||||
} catch(err){ |
||||
assert( |
||||
err.message.includes('does-not-exist') && |
||||
err.message.includes('coverage reports could not be generated'), |
||||
`Should error on invalid reporter: ${err.message}` |
||||
) |
||||
} |
||||
|
||||
}); |
||||
|
||||
// Truffle test contains syntax error
|
||||
it('truffle crashes', async function() { |
||||
mock.install('Simple', 'truffle-crash.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
try { |
||||
await this.env.run("coverage"); |
||||
assert.fail() |
||||
} catch(err){ |
||||
assert(err.toString().includes('SyntaxError')); |
||||
} |
||||
}); |
||||
|
||||
// Solidity syntax errors
|
||||
it('compilation failure', async function(){ |
||||
mock.install('SimpleError', 'simple.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
try { |
||||
await this.env.run("coverage"); |
||||
assert.fail() |
||||
} catch(err){ |
||||
assert(err.message.includes('Compilation failed')); |
||||
} |
||||
|
||||
verify.coverageNotGenerated(buidlerConfig); |
||||
}); |
||||
|
||||
it('instrumentation failure', async function(){ |
||||
mock.install('Unparseable', 'simple.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
try { |
||||
await this.env.run("coverage"); |
||||
assert.fail() |
||||
} catch(err){ |
||||
assert( |
||||
err.message.includes('Unparseable.sol.'), |
||||
`Should throw instrumentation errors with file name: ${err.toString()}` |
||||
); |
||||
|
||||
assert(err.stack !== undefined, 'Should have error trace') |
||||
} |
||||
|
||||
verify.coverageNotGenerated(buidlerConfig); |
||||
}); |
||||
}) |
@ -0,0 +1,146 @@ |
||||
const assert = require('assert'); |
||||
const fs = require('fs'); |
||||
const path = require('path') |
||||
const shell = require('shelljs'); |
||||
|
||||
const verify = require('../../util/verifiers') |
||||
const mock = require('../../util/integration'); |
||||
const plugin = require('../../../plugins/buidler.plugin'); |
||||
|
||||
// =======================
|
||||
// CLI Options / Flags
|
||||
// =======================
|
||||
|
||||
describe('Buidler Plugin: command line options', function() { |
||||
let buidlerConfig; |
||||
let solcoverConfig; |
||||
|
||||
beforeEach(function(){ |
||||
mock.clean(); |
||||
|
||||
mock.loggerOutput.val = ''; |
||||
solcoverConfig = { |
||||
skipFiles: ['Migrations.sol'], |
||||
silent: process.env.SILENT ? true : false, |
||||
istanbulReporter: ['json-summary', 'text'] |
||||
}; |
||||
buidlerConfig = mock.getDefaultBuidlerConfig(); |
||||
verify.cleanInitialState(); |
||||
}) |
||||
|
||||
afterEach(async function (){ |
||||
mock.buidlerTearDownEnv(); |
||||
mock.clean(); |
||||
}); |
||||
|
||||
|
||||
it('--temp', async function(){ |
||||
const taskArgs = { |
||||
temp: 'special_folder' |
||||
} |
||||
|
||||
mock.install('Simple', 'simple.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage", taskArgs); |
||||
|
||||
const expected = [{ |
||||
file: mock.pathToContract(buidlerConfig, 'Simple.sol'), |
||||
pct: 100 |
||||
}]; |
||||
|
||||
verify.lineCoverage(expected); |
||||
}); |
||||
|
||||
it('--network (declared port mismatches)', async function(){ |
||||
solcoverConfig.port = 8222; |
||||
|
||||
mock.install('Simple', 'simple.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
this.env.buidlerArguments.network = "development"; |
||||
|
||||
await this.env.run("coverage"); |
||||
|
||||
assert( |
||||
mock.loggerOutput.val.includes("The 'port' values"), |
||||
`Should notify about mismatched port values: ${mock.loggerOutput.val}` |
||||
); |
||||
|
||||
assert( |
||||
mock.loggerOutput.val.includes("8545"), |
||||
`Should have used default coverage port 8545: ${mock.loggerOutput.val}` |
||||
); |
||||
|
||||
assert( |
||||
mock.loggerOutput.val.includes("development"), |
||||
`Should have used specified network name: ${mock.loggerOutput.val}` |
||||
); |
||||
|
||||
const expected = [{ |
||||
file: mock.pathToContract(buidlerConfig, 'Simple.sol'), |
||||
pct: 100 |
||||
}]; |
||||
|
||||
verify.lineCoverage(expected); |
||||
}); |
||||
|
||||
it('--testfiles test/<fileName>', async function() { |
||||
const taskArgs = { |
||||
testfiles: path.join( |
||||
buidlerConfig.paths.root, |
||||
'test/specific_a.js' |
||||
) |
||||
}; |
||||
|
||||
mock.installFullProject('test-files'); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage", taskArgs); |
||||
|
||||
const expected = [ |
||||
{ |
||||
file: mock.pathToContract(buidlerConfig, 'ContractA.sol'), |
||||
pct: 100 |
||||
}, |
||||
{ |
||||
file: mock.pathToContract(buidlerConfig, 'ContractB.sol'), |
||||
pct: 0, |
||||
}, |
||||
{ |
||||
file: mock.pathToContract(buidlerConfig, 'ContractC.sol'), |
||||
pct: 0, |
||||
}, |
||||
]; |
||||
|
||||
verify.lineCoverage(expected); |
||||
}); |
||||
|
||||
it('--config ../.solcover.js', async function() { |
||||
// Write solcoverjs to parent dir of sc_temp (where the test project is installed)
|
||||
fs.writeFileSync( |
||||
'.solcover.js', |
||||
`module.exports=${JSON.stringify(solcoverConfig)}` |
||||
); |
||||
|
||||
// This relative path has to be ./ prefixed (it's path.joined to buidler's paths.root)
|
||||
const taskArgs = { |
||||
solcoverjs: './../.solcover.js' |
||||
}; |
||||
|
||||
mock.install('Simple', 'simple.js'); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage", taskArgs); |
||||
|
||||
// The relative solcoverjs uses the json-summary reporter
|
||||
const expected = [{ |
||||
file: mock.pathToContract(buidlerConfig, 'Simple.sol'), |
||||
pct: 100 |
||||
}]; |
||||
|
||||
verify.lineCoverage(expected); |
||||
shell.rm('.solcover.js'); |
||||
}); |
||||
}); |
||||
|
@ -0,0 +1,277 @@ |
||||
const assert = require('assert'); |
||||
const fs = require('fs'); |
||||
const path = require('path') |
||||
const shell = require('shelljs'); |
||||
|
||||
const verify = require('../../util/verifiers') |
||||
const mock = require('../../util/integration'); |
||||
const plugin = require('../../../plugins/buidler.plugin'); |
||||
|
||||
// =======================
|
||||
// Standard Use-case Tests
|
||||
// =======================
|
||||
|
||||
describe('Buidler Plugin: standard use cases', function() { |
||||
let buidlerConfig; |
||||
let solcoverConfig; |
||||
|
||||
beforeEach(() => { |
||||
mock.clean(); |
||||
|
||||
mock.loggerOutput.val = ''; |
||||
solcoverConfig = { skipFiles: ['Migrations.sol']}; |
||||
buidlerConfig = mock.getDefaultBuidlerConfig(); |
||||
verify.cleanInitialState(); |
||||
}) |
||||
|
||||
afterEach(() => { |
||||
mock.buidlerTearDownEnv(); |
||||
mock.clean(); |
||||
}); |
||||
|
||||
it('simple contract', async function(){ |
||||
mock.install('Simple', 'simple.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
|
||||
verify.coverageGenerated(buidlerConfig); |
||||
|
||||
const output = mock.getOutput(buidlerConfig); |
||||
const path = Object.keys(output)[0]; |
||||
|
||||
assert( |
||||
output[path].fnMap['1'].name === 'test', |
||||
'coverage.json missing "test"' |
||||
); |
||||
|
||||
assert( |
||||
output[path].fnMap['2'].name === 'getX', |
||||
'coverage.json missing "getX"' |
||||
); |
||||
}); |
||||
|
||||
it('default network ("buidlerevm")', async function(){ |
||||
mock.install('Simple', 'simple.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
this.env.buidlerArguments.network = "buidlerevm" |
||||
|
||||
await this.env.run("coverage"); |
||||
|
||||
assert( |
||||
mock.loggerOutput.val.includes("8555"), |
||||
`Should have used default coverage port 8555: ${mock.loggerOutput.val}` |
||||
); |
||||
|
||||
assert( |
||||
mock.loggerOutput.val.includes("soliditycoverage"), |
||||
`Should have used specified network name: ${mock.loggerOutput.val}` |
||||
); |
||||
}); |
||||
|
||||
it('with relative path solidity imports', async function() { |
||||
mock.installFullProject('import-paths'); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
}); |
||||
|
||||
it('uses inheritance', async function() { |
||||
mock.installDouble( |
||||
['Proxy', 'Owned'], |
||||
'inheritance.js', |
||||
solcoverConfig |
||||
); |
||||
|
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
|
||||
verify.coverageGenerated(buidlerConfig); |
||||
|
||||
const output = mock.getOutput(buidlerConfig); |
||||
const ownedPath = Object.keys(output)[0]; |
||||
const proxyPath = Object.keys(output)[1]; |
||||
|
||||
assert( |
||||
output[ownedPath].fnMap['1'].name === 'constructor', |
||||
'"constructor" not covered' |
||||
); |
||||
|
||||
assert( |
||||
output[proxyPath].fnMap['1'].name === 'isOwner', |
||||
'"isOwner" not covered' |
||||
); |
||||
}); |
||||
|
||||
it('only uses ".call"', async function(){ |
||||
mock.install('OnlyCall', 'only-call.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
|
||||
verify.coverageGenerated(buidlerConfig); |
||||
|
||||
const output = mock.getOutput(buidlerConfig); |
||||
const path = Object.keys(output)[0]; |
||||
assert( |
||||
output[path].fnMap['1'].name === 'addTwo', |
||||
'cov should map "addTwo"' |
||||
); |
||||
}); |
||||
|
||||
it('sends / transfers to instrumented fallback', async function(){ |
||||
mock.install('Wallet', 'wallet.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
|
||||
verify.coverageGenerated(buidlerConfig); |
||||
|
||||
const output = mock.getOutput(buidlerConfig); |
||||
const path = Object.keys(output)[0]; |
||||
assert( |
||||
output[path].fnMap['1'].name === 'transferPayment', |
||||
'cov should map "transferPayment"' |
||||
); |
||||
}); |
||||
|
||||
// Truffle test asserts deployment cost is greater than 20,000,000 gas
|
||||
it('deployment cost > block gasLimit', async function() { |
||||
mock.install('Expensive', 'block-gas-limit.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
}); |
||||
|
||||
// Simple.sol with a failing assertion in a truffle test
|
||||
it('unit tests failing', async function() { |
||||
mock.install('Simple', 'truffle-test-fail.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
try { |
||||
await this.env.run("coverage"); |
||||
assert.fail() |
||||
} catch(err){ |
||||
assert(err.message.includes('failed under coverage')); |
||||
} |
||||
|
||||
verify.coverageGenerated(buidlerConfig); |
||||
|
||||
const output = mock.getOutput(buidlerConfig); |
||||
const path = Object.keys(output)[0]; |
||||
|
||||
assert(output[path].fnMap['1'].name === 'test', 'cov missing "test"'); |
||||
assert(output[path].fnMap['2'].name === 'getX', 'cov missing "getX"'); |
||||
}); |
||||
|
||||
// This project has [ @skipForCoverage ] tags in the test descriptions
|
||||
// at selected 'contract' and 'it' blocks.
|
||||
it('config: mocha options', async function() { |
||||
solcoverConfig.mocha = { |
||||
grep: '@skipForCoverage', |
||||
invert: true, |
||||
}; |
||||
|
||||
solcoverConfig.silent = process.env.SILENT ? true : false, |
||||
solcoverConfig.istanbulReporter = ['json-summary', 'text'] |
||||
|
||||
mock.installFullProject('multiple-suites', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
|
||||
const expected = [ |
||||
{ |
||||
file: mock.pathToContract(buidlerConfig, 'ContractA.sol'), |
||||
pct: 0 |
||||
}, |
||||
{ |
||||
file: mock.pathToContract(buidlerConfig, 'ContractB.sol'), |
||||
pct: 0, |
||||
}, |
||||
{ |
||||
file: mock.pathToContract(buidlerConfig, 'ContractC.sol'), |
||||
pct: 100, |
||||
}, |
||||
]; |
||||
|
||||
verify.lineCoverage(expected); |
||||
}); |
||||
|
||||
// Truffle test asserts balance is 777 ether
|
||||
it('config: providerOptions', async function() { |
||||
solcoverConfig.providerOptions = { default_balance_ether: 777 } |
||||
|
||||
mock.install('Simple', 'testrpc-options.js', solcoverConfig); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
}); |
||||
|
||||
it('config: skipped file', async function() { |
||||
solcoverConfig.skipFiles = ['Migrations.sol', 'Owned.sol']; |
||||
|
||||
mock.installDouble( |
||||
['Proxy', 'Owned'], |
||||
'inheritance.js', |
||||
solcoverConfig |
||||
); |
||||
|
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
|
||||
verify.coverageGenerated(buidlerConfig); |
||||
|
||||
const output = mock.getOutput(buidlerConfig); |
||||
const firstKey = Object.keys(output)[0]; |
||||
|
||||
assert( |
||||
Object.keys(output).length === 1, |
||||
'Wrong # of contracts covered' |
||||
); |
||||
|
||||
assert( |
||||
firstKey.substr(firstKey.length - 9) === 'Proxy.sol', |
||||
'Wrong contract covered' |
||||
); |
||||
}); |
||||
|
||||
it('config: skipped folder', async function() { |
||||
mock.installFullProject('skipping'); |
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
|
||||
const expected = [{ |
||||
file: mock.pathToContract(buidlerConfig, 'ContractA.sol'), |
||||
pct: 100 |
||||
}]; |
||||
|
||||
const missing = [{ |
||||
file: mock.pathToContract(buidlerConfig, 'skipped-folder/ContractB.sol'), |
||||
}]; |
||||
|
||||
verify.lineCoverage(expected); |
||||
verify.coverageMissing(missing); |
||||
}); |
||||
|
||||
it('config: "onServerReady", "onTestsComplete", ...', async function() { |
||||
mock.installFullProject('test-files'); |
||||
|
||||
mock.buidlerSetupEnv(this); |
||||
|
||||
await this.env.run("coverage"); |
||||
|
||||
assert( |
||||
mock.loggerOutput.val.includes('running onServerReady') && |
||||
mock.loggerOutput.val.includes('running onTestsComplete') && |
||||
mock.loggerOutput.val.includes('running onCompileComplete') && |
||||
mock.loggerOutput.val.includes('running onIstanbulComplete'), |
||||
|
||||
`Should run "on" hooks : ${mock.loggerOutput.val}` |
||||
); |
||||
}); |
||||
}) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue