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 */ |
const Empty = artifacts.require('Empty'); |
||||||
/* global artifacts, contract */ |
|
||||||
|
|
||||||
const Empty = artifacts.require('./Empty.sol'); |
contract('Empty', function() { |
||||||
|
it('should deploy', async function (){ |
||||||
contract('Empty', () => { |
await Empty.new() |
||||||
it('should deploy', () => Empty.deployed()); |
}); |
||||||
}); |
}); |
||||||
|
@ -1,11 +1,11 @@ |
|||||||
const Owned = artifacts.require('./Owned.sol'); |
const Owned = artifacts.require('Owned'); |
||||||
const Proxy = artifacts.require('./Proxy.sol'); |
const Proxy = artifacts.require('Proxy'); |
||||||
|
|
||||||
contract('Proxy', accounts => { |
contract('Proxy', accounts => { |
||||||
it('Should compile and run when one contract inherits from another', () => Owned.deployed() |
it('when one contract inherits from another', async function(){ |
||||||
.then(() => Proxy.deployed()) |
const owned = await Owned.new(); |
||||||
.then(instance => instance.isOwner.call({ |
const proxy = await Proxy.new(); |
||||||
from: accounts[0], |
const val = await proxy.isOwner({from: accounts[0]}); |
||||||
})) |
assert.equal(val, true); |
||||||
.then(val => assert.equal(val, true))); |
}) |
||||||
}); |
}); |
||||||
|
@ -1,17 +1,9 @@ |
|||||||
/* eslint-env node, mocha */ |
const OnlyCall = artifacts.require('OnlyCall'); |
||||||
/* global artifacts, contract, assert */ |
|
||||||
|
|
||||||
const OnlyCall = artifacts.require('./OnlyCall.sol'); |
|
||||||
|
|
||||||
contract('OnlyCall', accounts => { |
contract('OnlyCall', accounts => { |
||||||
it('should return val + 2', done => { |
it('should return val + 2', async function(){ |
||||||
OnlyCall.deployed().then(instance => { |
const onlycall = await OnlyCall.new(); |
||||||
instance.addTwo.call(5, { |
const val = await onlycall.addTwo(5); |
||||||
from: accounts[0], |
assert.equal(val.toNumber(), 7); |
||||||
}).then(val => { |
}) |
||||||
assert.equal(val, 7); |
|
||||||
done(); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
|
||||||
}); |
}); |
||||||
|
@ -1,17 +1,17 @@ |
|||||||
/* eslint-env node, mocha */ |
/* eslint-env node, mocha */ |
||||||
/* global artifacts, contract, assert */ |
/* global artifacts, contract, assert */ |
||||||
|
|
||||||
const PureView = artifacts.require('./PureView.sol'); |
const PureView = artifacts.require('PureView'); |
||||||
|
|
||||||
contract('PureView', accounts => { |
contract('PureView', accounts => { |
||||||
|
|
||||||
it('calls a pure function', async function(){ |
it('calls a pure function', async function(){ |
||||||
const instance = await PureView.deployed(); |
const instance = await PureView.new(); |
||||||
const value = await instance.isPure(4,5); |
const value = await instance.isPure(4,5); |
||||||
}); |
}); |
||||||
|
|
||||||
it('calls a view function', async function(){ |
it('calls a view function', async function(){ |
||||||
const instance = await PureView.deployed(); |
const instance = await PureView.new(); |
||||||
const value = await instance.isView(); |
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 */ |
/* eslint-env node, mocha */ |
||||||
/* global artifacts, contract */ |
/* 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.
|
// This test should break truffle because it has a syntax error.
|
||||||
contract('Simple', () => { |
contract('Simple', () => { |
||||||
it('should crash', function(){ |
it('should crash', function(){ |
||||||
return Simple.deployed().then.why. |
return Simple.new().then.why. |
||||||
}) |
}) |
||||||
}) |
}) |
@ -1,16 +1,20 @@ |
|||||||
/* eslint-env node, mocha */ |
/* eslint-env node, mocha */ |
||||||
/* global artifacts, contract, assert */ |
/* global artifacts, contract, assert */ |
||||||
|
|
||||||
const Simple = artifacts.require('./Simple.sol'); |
const Simple = artifacts.require('Simple'); |
||||||
|
|
||||||
contract('Simple', () => { |
contract('Simple', () => { |
||||||
it('should set x to 5', () => { |
it('should set x to 5', async function() { |
||||||
let simple; |
let simple = await Simple.new(); |
||||||
return Simple.deployed().then(instance => { |
await simple.test(5); |
||||||
simple = instance; |
const val = await simple.getX(); |
||||||
return simple.test(5); |
assert.equal(val.toNumber(), 4) // <-- Wrong result: test fails
|
||||||
}) |
}); |
||||||
.then(() => simple.getX.call()) |
|
||||||
.then(val => 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