Merge pull request #1 from sc-forks/truffle3
Update to Truffle3, refactor CLI, add CLI unit tests, fix misc bugspull/2/head
commit
4c2c9e5e99
@ -0,0 +1 @@ |
||||
test/cli/truffle-crash.js |
@ -0,0 +1,211 @@ |
||||
#!/usr/bin/env node
|
||||
|
||||
const shell = require('shelljs'); |
||||
const fs = require('fs'); |
||||
const reqCwd = require('req-cwd'); |
||||
const path = require('path'); |
||||
const childprocess = require('child_process'); |
||||
const getInstrumentedVersion = require('./instrumentSolidity.js'); |
||||
const CoverageMap = require('./coverageMap.js'); |
||||
const istanbul = require('istanbul'); |
||||
|
||||
const istanbulCollector = new istanbul.Collector(); |
||||
const istanbulReporter = new istanbul.Reporter(); |
||||
|
||||
// Very high gas block limits / contract deployment limits
|
||||
const gasLimitString = '0xfffffffffff'; |
||||
const gasLimitHex = 0xfffffffffff; |
||||
const gasPriceHex = 0x01; |
||||
|
||||
const coverage = new CoverageMap(); |
||||
|
||||
// Paths
|
||||
const coverageDir = './coverageEnv'; // Env that instrumented .sols are tested in
|
||||
|
||||
// Options
|
||||
let coverageOption = '--network coverage'; // Default truffle network execution flag
|
||||
let silence = ''; // Default log level: configurable by --silence
|
||||
let log = console.log; // Default log level: configurable by --silence
|
||||
|
||||
let testrpcProcess; // ref to testrpc server we need to close on exit
|
||||
let events; // ref to string loaded from 'allFiredEvents'
|
||||
|
||||
// --------------------------------------- Utilities -----------------------------------------------
|
||||
/** |
||||
* Removes coverage build artifacts, kills testrpc. |
||||
* Exits (1) and prints msg on error, exits (0) otherwise. |
||||
* @param {String} err error message |
||||
*/ |
||||
function cleanUp(err) { |
||||
log('Cleaning up...'); |
||||
shell.config.silent = true; |
||||
shell.rm('-Rf', `${coverageDir}`); |
||||
shell.rm('./allFiredEvents'); |
||||
if (testrpcProcess) { testrpcProcess.kill(); } |
||||
|
||||
if (err) { |
||||
log(`${err}\nExiting without generating coverage...`); |
||||
process.exit(1); |
||||
} else { |
||||
process.exit(0); |
||||
} |
||||
} |
||||
// --------------------------------------- Script --------------------------------------------------
|
||||
const config = reqCwd.silent('./.solcover.js') || {}; |
||||
|
||||
const workingDir = config.dir || '.'; // Relative path to contracts folder
|
||||
const port = config.port || 8555; // Port testrpc listens on
|
||||
const accounts = config.accounts || 35; // Number of accounts to testrpc launches with
|
||||
|
||||
// Set testrpc options
|
||||
const defaultRpcOptions = `--gasLimit ${gasLimitString} --accounts ${accounts} --port ${port}`; |
||||
const testrpcOptions = config.testrpcOptions || defaultRpcOptions; |
||||
|
||||
// Silence shell and script logging (for solcover's unit tests / CI)
|
||||
if (config.silent) { |
||||
silence = '> /dev/null 2>&1'; |
||||
log = () => {}; |
||||
} |
||||
|
||||
// Run modified testrpc with large block limit, on (hopefully) unused port.
|
||||
// (Changes here should be also be added to the before() block of test/run.js).
|
||||
if (!config.norpc) { |
||||
try { |
||||
// NPM installs the testrpc-sc fork at different locations in node_modules based on
|
||||
// whether testrpc is a pre-existing dependency of the project solcover is being added to
|
||||
// using (--save-dev).
|
||||
let command; |
||||
if (shell.test('-e', './node_modules/ethereumjs-testrpc-sc')) { |
||||
command = './node_modules/ethereumjs-testrpc-sc/bin/testrpc '; |
||||
} else { |
||||
command = './node_modules/solcover/node_modules/ethereumjs-testrpc-sc/bin/testrpc '; |
||||
} |
||||
testrpcProcess = childprocess.exec(command + testrpcOptions); |
||||
log(`Testrpc launched on port ${port}`); |
||||
} catch (err) { |
||||
const msg = `There was a problem launching testrpc: ${err}`; |
||||
cleanUp(msg); |
||||
} |
||||
} |
||||
|
||||
// Generate a copy of the target truffle project configured for solcover and save to the coverage
|
||||
// environment folder.
|
||||
log('Generating coverage environment'); |
||||
try { |
||||
shell.mkdir(`${coverageDir}`); |
||||
shell.cp('-R', `${workingDir}/contracts`, `${coverageDir}`); |
||||
shell.cp('-R', `${workingDir}/migrations`, `${coverageDir}`); |
||||
shell.cp('-R', `${workingDir}/test`, `${coverageDir}`); |
||||
|
||||
const truffleConfig = reqCwd(`${workingDir}/truffle.js`); |
||||
|
||||
// Coverage network opts specified: copy truffle.js whole to coverage environment
|
||||
if (truffleConfig.networks.coverage) { |
||||
shell.cp(`${workingDir}/truffle.js`, `${coverageDir}/truffle.js`); |
||||
|
||||
// Coverage network opts NOT specified: default to the development network w/ modified
|
||||
// port, gasLimit, gasPrice. Export the config object only.
|
||||
} else { |
||||
const trufflejs = ` |
||||
module.exports = { |
||||
networks: { |
||||
development: { |
||||
host: "localhost",
|
||||
network_id: "*", |
||||
port: ${port}, |
||||
gas: ${gasLimitHex}, |
||||
gasPrice: ${gasPriceHex} |
||||
} |
||||
} |
||||
};`;
|
||||
|
||||
coverageOption = ''; |
||||
fs.writeFileSync(`${coverageDir}/truffle.js`, trufflejs); |
||||
} |
||||
} catch (err) { |
||||
const msg = ('There was a problem generating the coverage environment: '); |
||||
cleanUp(msg + err); |
||||
} |
||||
|
||||
// For each contract except migrations.sol:
|
||||
// 1. Generate file path reference for coverage report
|
||||
// 2. Load contract as string
|
||||
// 3. Instrument contract
|
||||
// 4. Save instrumented contract in the coverage environment folder where covered tests will run
|
||||
// 5. Add instrumentation info to the coverage map
|
||||
let currentFile; |
||||
try { |
||||
shell.ls(`${coverageDir}/contracts/**/*.sol`).forEach(file => { |
||||
const migrations = `${coverageDir}/contracts/Migrations.sol`; |
||||
|
||||
if (file !== migrations) { |
||||
log('Instrumenting ', file); |
||||
currentFile = file; |
||||
|
||||
const contractPath = path.resolve(file); |
||||
const canonicalPath = contractPath.split('/coverageEnv').join(''); |
||||
const contract = fs.readFileSync(contractPath).toString(); |
||||
const instrumentedContractInfo = getInstrumentedVersion(contract, canonicalPath); |
||||
fs.writeFileSync(contractPath, instrumentedContractInfo.contract); |
||||
coverage.addContract(instrumentedContractInfo, canonicalPath); |
||||
} |
||||
}); |
||||
} catch (err) { |
||||
const msg = (`There was a problem instrumenting ${currentFile}: `); |
||||
cleanUp(msg + err); |
||||
} |
||||
|
||||
// Run solcover's fork of truffle over instrumented contracts in the
|
||||
// coverage environment folder. Shell cd command needs to be invoked
|
||||
// as its own statement for command line options to work, apparently.
|
||||
try { |
||||
log('Launching test command (this can take a few seconds)...'); |
||||
const defaultCommand = `truffle test ${coverageOption} ${silence}`; |
||||
const command = config.testCommand || defaultCommand; |
||||
shell.cd('./coverageEnv'); |
||||
shell.exec(command); |
||||
shell.cd('./..'); |
||||
} catch (err) { |
||||
cleanUp(err); |
||||
} |
||||
|
||||
// Get events fired during instrumented contracts execution.
|
||||
try { |
||||
events = fs.readFileSync('./allFiredEvents').toString().split('\n'); |
||||
events.pop(); |
||||
} catch (err) { |
||||
const msg = |
||||
` |
||||
There was an error generating coverage. Possible reasons include: |
||||
1. Another application is using port ${port}
|
||||
2. Truffle crashed because your tests errored |
||||
|
||||
`;
|
||||
cleanUp(msg + err); |
||||
} |
||||
|
||||
// Generate coverage / write coverage report / run istanbul
|
||||
try { |
||||
coverage.generate(events, './contracts'); |
||||
|
||||
const json = JSON.stringify(coverage.coverage); |
||||
fs.writeFileSync('./coverage.json', json); |
||||
|
||||
istanbulCollector.add(coverage.coverage); |
||||
istanbulReporter.add('html'); |
||||
istanbulReporter.add('lcov'); |
||||
istanbulReporter.add('text'); |
||||
istanbulReporter.write(istanbulCollector, true, () => { |
||||
log('Istanbul coverage reports generated'); |
||||
}); |
||||
} catch (err) { |
||||
if (config.testing) { |
||||
cleanUp(); |
||||
} else { |
||||
const msg = 'There was a problem generating producing the coverage map / running Istanbul.\n'; |
||||
cleanUp(msg + err); |
||||
} |
||||
} |
||||
|
||||
// Finish
|
||||
cleanUp(); |
@ -1,31 +0,0 @@ |
||||
--- opFns.js.orig 2016-12-19 15:10:12.000000000 -0800
|
||||
+++ opFns.js 2016-12-19 15:15:51.000000000 -0800
|
||||
@@ -6,6 +6,7 @@
|
||||
const logTable = require('./logTable.js')
|
||||
const ERROR = constants.ERROR
|
||||
const MAX_INT = 9007199254740991
|
||||
+const fs = require('fs');
|
||||
|
||||
// the opcode functions
|
||||
module.exports = {
|
||||
@@ -489,7 +490,7 @@
|
||||
memLength = utils.bufferToInt(memLength)
|
||||
const numOfTopics = runState.opCode - 0xa0
|
||||
const mem = memLoad(runState, memOffset, memLength)
|
||||
- subGas(runState, new BN(numOfTopics * fees.logTopicGas.v + memLength * fees.logDataGas.v))
|
||||
+ //subGas(runState, new BN(numOfTopics * fees.logTopicGas.v + memLength * fees.logDataGas.v))
|
||||
|
||||
// add address
|
||||
var log = [runState.address]
|
||||
@@ -497,6 +498,11 @@
|
||||
|
||||
// add data
|
||||
log.push(mem)
|
||||
+ var toWrite = {};
|
||||
+ toWrite.address= log[0].toString('hex')
|
||||
+ toWrite.topics = log[1].map(function(x){return x.toString('hex')})
|
||||
+ toWrite.data = log[2].toString('hex')
|
||||
+ fs.appendFileSync('./allFiredEvents', JSON.stringify(toWrite) + '\n')
|
||||
runState.logs.push(log)
|
||||
},
|
||||
|
@ -1,61 +0,0 @@ |
||||
const Web3 = require('web3'); |
||||
const shell = require('shelljs'); |
||||
const SolidityCoder = require('web3/lib/solidity/coder.js'); |
||||
const getInstrumentedVersion = require('./instrumentSolidity.js'); |
||||
const fs = require('fs'); |
||||
const path = require('path'); |
||||
const CoverageMap = require('./coverageMap.js'); |
||||
const mkdirp = require('mkdirp'); |
||||
const childprocess = require('child_process'); |
||||
|
||||
const coverage = new CoverageMap(); |
||||
const web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:8545')); |
||||
|
||||
// Patch our local testrpc if necessary
|
||||
if (!shell.test('-e', './node_modules/ethereumjs-vm/lib/opFns.js.orig')) { |
||||
console.log('patch local testrpc...'); |
||||
shell.exec('patch -b ./node_modules/ethereumjs-vm/lib/opFns.js ./hookIntoEvents.patch'); |
||||
} |
||||
// Run the modified testrpc with large block limit
|
||||
const testrpcProcess = childprocess.exec('./node_modules/ethereumjs-testrpc/bin/testrpc --gasLimit 0xfffffffffffff --gasPrice 0x1'); |
||||
|
||||
if (shell.test('-d', '../originalContracts')) { |
||||
console.log('There is already an "originalContracts" directory in your truffle directory.\n' + |
||||
'This is probably due to a previous solcover failure.\n' + |
||||
'Please make sure the ./contracts/ directory contains your contracts (perhaps by copying them from originalContracts), ' + |
||||
'and then delete the originalContracts directory.'); |
||||
process.exit(1); |
||||
} |
||||
|
||||
shell.mv('./../contracts/', './../originalContracts/'); |
||||
shell.mkdir('./../contracts/'); |
||||
// For each contract in originalContracts, get the instrumented version
|
||||
shell.ls('./../originalContracts/**/*.sol').forEach(file => { |
||||
if (file !== 'originalContracts/Migrations.sol') { |
||||
const canonicalContractPath = path.resolve(file); |
||||
|
||||
console.log('instrumenting ', canonicalContractPath); |
||||
const contract = fs.readFileSync(canonicalContractPath).toString(); |
||||
const instrumentedContractInfo = getInstrumentedVersion(contract, canonicalContractPath); |
||||
mkdirp.sync(path.dirname(canonicalContractPath.replace('originalContracts', 'contracts'))); |
||||
fs.writeFileSync(canonicalContractPath.replace('originalContracts', 'contracts'), instrumentedContractInfo.contract); |
||||
coverage.addContract(instrumentedContractInfo, canonicalContractPath); |
||||
} |
||||
}); |
||||
shell.cp('./../originalContracts/Migrations.sol', './../contracts/Migrations.sol'); |
||||
|
||||
shell.rm('./allFiredEvents'); // Delete previous results
|
||||
shell.exec('truffle test --network test'); |
||||
|
||||
const events = fs.readFileSync('./allFiredEvents').toString().split('\n'); |
||||
events.pop(); |
||||
// The pop here isn't a bug - there is an empty line at the end of this file, so we
|
||||
// don't want to include it as an event.
|
||||
coverage.generate(events, './../originalContracts/'); |
||||
|
||||
fs.writeFileSync('./coverage.json', JSON.stringify(coverage.coverage)); |
||||
|
||||
shell.exec('./node_modules/istanbul/lib/cli.js report lcov'); |
||||
testrpcProcess.kill(); |
||||
shell.rm('-rf', './../contracts'); |
||||
shell.mv('./../originalContracts', './../contracts'); |
@ -0,0 +1,212 @@ |
||||
/* eslint-env node, mocha */ |
||||
|
||||
const assert = require('assert'); |
||||
const shell = require('shelljs'); |
||||
const fs = require('fs'); |
||||
const childprocess = require('child_process'); |
||||
const mock = require('./util/mockTruffle.js'); |
||||
|
||||
// shell.test alias for legibility
|
||||
function pathExists(path) { return shell.test('-e', path); } |
||||
|
||||
// tests run out of memory in CI without this
|
||||
function collectGarbage() { |
||||
if (global.gc) { global.gc(); } |
||||
} |
||||
|
||||
describe('cli', () => { |
||||
let testrpcProcess = null; |
||||
const script = 'node ./exec.js'; |
||||
const port = 8555; |
||||
|
||||
const config = { |
||||
dir: './mock', |
||||
port, |
||||
testing: true, |
||||
silent: true, // <-- Set to false to debug tests
|
||||
norpc: true, |
||||
}; |
||||
|
||||
before(() => { |
||||
const command = `./node_modules/ethereumjs-testrpc-sc/bin/testrpc --gasLimit 0xfffffffffff --port ${port}`; |
||||
testrpcProcess = childprocess.exec(command); |
||||
}); |
||||
|
||||
afterEach(() => { |
||||
mock.remove(); |
||||
}); |
||||
|
||||
after(() => { |
||||
testrpcProcess.kill(); |
||||
}); |
||||
|
||||
// #1: The 'config' tests ask exec.js to run testrpc on special ports, the subsequent tests use
|
||||
// the testrpc launched in the before() block. For some reason config tests fail randomly
|
||||
// unless they are at the top of the suite. Hard to debug since they pass if logging is turned
|
||||
// on - there might be a timing issue around resource cleanup or something.
|
||||
//
|
||||
// #2: Creating repeated instances of testrpc hits the container memory limit on
|
||||
// CI so these tests are disabled for that context
|
||||
it('config with testrpc options string: should generate coverage, cleanup & exit(0)', () => { |
||||
if (!process.env.CI) { |
||||
const privateKey = '0x3af46c9ac38ee1f01b05f9915080133f644bf57443f504d339082cb5285ccae4'; |
||||
const balance = '0xfffffffffffffff'; |
||||
const testConfig = Object.assign({}, config); |
||||
|
||||
testConfig.testrpcOptions = `--account="${privateKey},${balance}" --port 8777`; |
||||
testConfig.norpc = false; |
||||
testConfig.port = 8777; |
||||
|
||||
// Installed test will process.exit(1) and crash truffle if the test isn't
|
||||
// loaded with the account specified above
|
||||
mock.install('Simple.sol', 'testrpc-options.js', testConfig); |
||||
shell.exec(script); |
||||
assert(shell.error() === null, 'script should not error'); |
||||
collectGarbage(); |
||||
} |
||||
}); |
||||
|
||||
it('config with test command options string: should run test', () => { |
||||
if (!process.env.CI) { |
||||
assert(pathExists('./allFiredEvents') === false, 'should start without: events log'); |
||||
const testConfig = Object.assign({}, config); |
||||
|
||||
testConfig.testCommand = 'mocha --timeout 5000 > /dev/null 2>&1'; |
||||
testConfig.norpc = false; |
||||
testConfig.port = 8888; |
||||
|
||||
// Installed test will write a fake allFiredEvents to ./ after 4000ms
|
||||
// allowing test to pass
|
||||
mock.install('Simple.sol', 'command-options.js', testConfig); |
||||
shell.exec(script); |
||||
assert(shell.error() === null, 'script should not error'); |
||||
collectGarbage(); |
||||
} |
||||
}); |
||||
|
||||
it('simple contract: should generate coverage, cleanup & exit(0)', () => { |
||||
// Directory should be clean
|
||||
assert(pathExists('./coverage') === false, 'should start without: coverage'); |
||||
assert(pathExists('./coverage.json') === false, 'should start without: coverage.json'); |
||||
|
||||
// Run script (exits 0);
|
||||
mock.install('Simple.sol', 'simple.js', config); |
||||
shell.exec(script); |
||||
assert(shell.error() === null, 'script should not error'); |
||||
|
||||
// Directory should have coverage report
|
||||
assert(pathExists('./coverage') === true, 'script should gen coverage folder'); |
||||
assert(pathExists('./coverage.json') === true, 'script should gen coverage.json'); |
||||
|
||||
// Coverage should be real.
|
||||
// This test is tightly bound to the function names in Simple.sol
|
||||
const produced = JSON.parse(fs.readFileSync('./coverage.json', 'utf8')); |
||||
const path = Object.keys(produced)[0]; |
||||
assert(produced[path].fnMap['1'].name === 'test', 'coverage.json should map "test"'); |
||||
assert(produced[path].fnMap['2'].name === 'getX', 'coverage.json should map "getX"'); |
||||
collectGarbage(); |
||||
}); |
||||
|
||||
it('contract only uses .call: should generate coverage, cleanup & exit(0)', () => { |
||||
// Run against contract that only uses method.call.
|
||||
assert(pathExists('./coverage') === false, 'should start without: coverage'); |
||||
assert(pathExists('./coverage.json') === false, 'should start without: coverage.json'); |
||||
mock.install('OnlyCall.sol', 'only-call.js', config); |
||||
|
||||
shell.exec(script); |
||||
assert(shell.error() === null, 'script should not error'); |
||||
|
||||
assert(pathExists('./coverage') === true, 'script should gen coverage folder'); |
||||
assert(pathExists('./coverage.json') === true, 'script should gen coverage.json'); |
||||
|
||||
const produced = JSON.parse(fs.readFileSync('./coverage.json', 'utf8')); |
||||
const path = Object.keys(produced)[0]; |
||||
assert(produced[path].fnMap['1'].name === 'addTwo', 'coverage.json should map "addTwo"'); |
||||
collectGarbage(); |
||||
}); |
||||
|
||||
it('contract uses inheritance: should generate coverage, cleanup & exit(0)', () => { |
||||
// Run against a contract that 'is' another contract
|
||||
assert(pathExists('./coverage') === false, 'should start without: coverage'); |
||||
assert(pathExists('./coverage.json') === false, 'should start without: coverage.json'); |
||||
mock.installInheritanceTest(config); |
||||
|
||||
shell.exec(script); |
||||
assert(shell.error() === null, 'script should not error'); |
||||
|
||||
assert(pathExists('./coverage') === true, 'script should gen coverage folder'); |
||||
assert(pathExists('./coverage.json') === true, 'script should gen coverage.json'); |
||||
|
||||
const produced = JSON.parse(fs.readFileSync('./coverage.json', 'utf8')); |
||||
const ownedPath = Object.keys(produced)[0]; |
||||
const proxyPath = Object.keys(produced)[1]; |
||||
|
||||
assert(produced[ownedPath].fnMap['1'].name === 'Owned', 'coverage.json should map "Owned"'); |
||||
assert(produced[proxyPath].fnMap['1'].name === 'isOwner', 'coverage.json should map "isOwner"'); |
||||
collectGarbage(); |
||||
}); |
||||
|
||||
it('truffle tests failing: should generate coverage, cleanup & exit(0)', () => { |
||||
assert(pathExists('./coverage') === false, 'should start without: coverage'); |
||||
assert(pathExists('./coverage.json') === false, 'should start without: coverage.json'); |
||||
|
||||
// Run with Simple.sol and a failing assertion in a truffle test
|
||||
mock.install('Simple.sol', 'truffle-test-fail.js', config); |
||||
shell.exec(script); |
||||
assert(shell.error() === null, 'script should not error'); |
||||
assert(pathExists('./coverage') === true, 'script should gen coverage folder'); |
||||
assert(pathExists('./coverage.json') === true, 'script should gen coverage.json'); |
||||
|
||||
const produced = JSON.parse(fs.readFileSync('./coverage.json', 'utf8')); |
||||
const path = Object.keys(produced)[0]; |
||||
assert(produced[path].fnMap['1'].name === 'test', 'coverage.json should map "test"'); |
||||
assert(produced[path].fnMap['2'].name === 'getX', 'coverage.json should map "getX"'); |
||||
collectGarbage(); |
||||
}); |
||||
|
||||
it('deployment cost > block gasLimit: should generate coverage, cleanup & exit(0)', () => { |
||||
// Just making sure Expensive.sol compiles and deploys here.
|
||||
mock.install('Expensive.sol', 'block-gas-limit.js', config); |
||||
shell.exec(script); |
||||
assert(shell.error() === null, 'script should not error'); |
||||
collectGarbage(); |
||||
}); |
||||
|
||||
it('truffle crashes: should generate NO coverage, cleanup and exit(1)', () => { |
||||
assert(pathExists('./coverage') === false, 'should start without: coverage'); |
||||
assert(pathExists('./coverage.json') === false, 'should start without: coverage.json'); |
||||
|
||||
// Run with Simple.sol and a syntax error in the truffle test
|
||||
mock.install('Simple.sol', 'truffle-crash.js', config); |
||||
shell.exec(script); |
||||
assert(shell.error() !== null, 'script should error'); |
||||
assert(pathExists('./coverage') !== true, 'script should NOT gen coverage folder'); |
||||
assert(pathExists('./coverage.json') !== true, 'script should NOT gen coverage.json'); |
||||
collectGarbage(); |
||||
}); |
||||
|
||||
it('instrumentation errors: should generate NO coverage, cleanup and exit(1)', () => { |
||||
assert(pathExists('./coverage') === false, 'should start without: coverage'); |
||||
assert(pathExists('./coverage.json') === false, 'should start without: coverage.json'); |
||||
|
||||
// Run with SimpleError.sol (has syntax error) and working truffle test
|
||||
mock.install('SimpleError.sol', 'simple.js', config); |
||||
shell.exec(script); |
||||
assert(shell.error() !== null, 'script should error'); |
||||
assert(pathExists('./coverage') !== true, 'script should NOT gen coverage folder'); |
||||
assert(pathExists('./coverage.json') !== true, 'script should NOT gen coverage.json'); |
||||
collectGarbage(); |
||||
}); |
||||
|
||||
it('no events log produced: should generate NO coverage, cleanup and exit(1)', () => { |
||||
// Run contract and test that pass but fire no events
|
||||
assert(pathExists('./coverage') === false, 'should start without: coverage'); |
||||
assert(pathExists('./coverage.json') === false, 'should start without: coverage.json'); |
||||
mock.install('Empty.sol', 'empty.js', config); |
||||
shell.exec(script); |
||||
assert(shell.error() !== null, 'script should error'); |
||||
assert(pathExists('./coverage') !== true, 'script should NOT gen coverage folder'); |
||||
assert(pathExists('./coverage.json') !== true, 'script should NOT gen coverage.json'); |
||||
collectGarbage(); |
||||
}); |
||||
}); |
@ -0,0 +1,7 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract */ |
||||
const Expensive = artifacts.require('./Expensive.sol'); |
||||
|
||||
contract('Expensive', () => { |
||||
it('should deploy', () => Expensive.deployed()); |
||||
}); |
@ -0,0 +1,22 @@ |
||||
/* eslint-env node, mocha */ |
||||
|
||||
const assert = require('assert'); |
||||
const fs = require('fs'); |
||||
|
||||
// Fake event for Simple.sol
|
||||
const fakeEvent = { |
||||
address: '7c548f8a5ba3a37774440587743bb50f58c7e91c', |
||||
topics: ['1accf53d733f86cbefdf38d52682bc905cf6715eb3d860be0b5b052e58b0741d'], |
||||
data: '0', |
||||
}; |
||||
// Tests whether or not the testCommand option is invoked by exec.js
|
||||
// Mocha's default timeout is 2000 - here we fake the creation of
|
||||
// allFiredEvents at 4000.
|
||||
describe('Test uses mocha', () => { |
||||
it('should run "mocha --timeout 5000" successfully', done => { |
||||
setTimeout(() => { |
||||
fs.writeFileSync('./../allFiredEvents', fakeEvent); |
||||
done(); |
||||
}, 4000); |
||||
}); |
||||
}); |
@ -0,0 +1,8 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract */ |
||||
|
||||
const Empty = artifacts.require('./Empty.sol'); |
||||
|
||||
contract('Empty', () => { |
||||
it('should deploy', () => Empty.deployed()); |
||||
}); |
@ -0,0 +1,14 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const Owned = artifacts.require('./Owned.sol'); |
||||
const Proxy = artifacts.require('./Proxy.sol'); |
||||
|
||||
contract('Proxy', accounts => { |
||||
it('Should compile and run when one contract inherits from another', () => Owned.deployed() |
||||
.then(() => Proxy.deployed()) |
||||
.then(instance => instance.isOwner.call({ |
||||
from: accounts[0], |
||||
})) |
||||
.then(val => assert.equal(val, true))); |
||||
}); |
@ -0,0 +1,17 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const OnlyCall = artifacts.require('./OnlyCall.sol'); |
||||
|
||||
contract('OnlyCall', accounts => { |
||||
it('should return val + 2', done => { |
||||
OnlyCall.deployed().then(instance => { |
||||
instance.addTwo.call(5, { |
||||
from: accounts[0], |
||||
}).then(val => { |
||||
assert.equal(val, 7); |
||||
done(); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,16 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const Simple = artifacts.require('./Simple.sol'); |
||||
|
||||
contract('Simple', () => { |
||||
it('should set x to 5', () => { |
||||
let simple; |
||||
return Simple.deployed().then(instance => { |
||||
simple = instance; |
||||
return simple.test(5); |
||||
}) |
||||
.then(() => simple.getX.call()) |
||||
.then(val => assert.equal(val.toNumber(), 5)); |
||||
}); |
||||
}); |
@ -0,0 +1,17 @@ |
||||
/* 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)); |
||||
}); |
||||
}); |
@ -0,0 +1,24 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const Simple = artifacts.require('./Simple.sol'); |
||||
|
||||
contract('Simple', accounts => { |
||||
// Crash truffle if the account loaded in the options string isn't found here.
|
||||
it('should load with expected account', () => { |
||||
if (accounts[0] !== '0xa4860cedd5143bd63f347cab453bf91425f8404f') { |
||||
process.exit(1); |
||||
} |
||||
}); |
||||
|
||||
// Generate some coverage so the script doesn't exit(1) because there are no events
|
||||
it('should set x to 5', () => { |
||||
let simple; |
||||
return Simple.deployed().then(instance => { |
||||
simple = instance; |
||||
return simple.test(5); |
||||
}) |
||||
.then(() => simple.getX.call()) |
||||
.then(val => assert.equal(val.toNumber(), 5)); |
||||
}); |
||||
}); |
@ -0,0 +1,11 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract */ |
||||
|
||||
var Simple = artifacts.require('./Simple.sol'); |
||||
|
||||
// This test should break truffle because it has a syntax error.
|
||||
contract('Simple', () => { |
||||
it('should crash', function(){ |
||||
return Simple.deployed().then.why. |
||||
}) |
||||
}) |
@ -0,0 +1,16 @@ |
||||
/* eslint-env node, mocha */ |
||||
/* global artifacts, contract, assert */ |
||||
|
||||
const Simple = artifacts.require('./Simple.sol'); |
||||
|
||||
contract('Simple', () => { |
||||
it('should set x to 5', () => { |
||||
let simple; |
||||
return Simple.deployed().then(instance => { |
||||
simple = instance; |
||||
return simple.test(5); |
||||
}) |
||||
.then(() => simple.getX.call()) |
||||
.then(val => assert.equal(val.toNumber(), 4)); // <-- Wrong result: test fails
|
||||
}); |
||||
}); |
@ -0,0 +1,4 @@ |
||||
pragma solidity ^0.4.3; |
||||
|
||||
contract Empty { |
||||
} |
@ -0,0 +1,15 @@ |
||||
// Cost to deploy Expensive: 0x4e042f |
||||
// Block gas limit is: 0x47e7c4 |
||||
// Should throw out of gas on unmodified truffle |
||||
// Should pass solcover truffle |
||||
pragma solidity ^0.4.2; |
||||
|
||||
contract Expensive { |
||||
mapping (uint => address) map; |
||||
|
||||
function Expensive(){ |
||||
for(uint i = 0; i < 250; i++ ){ |
||||
map[i] = address(this); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
pragma solidity ^0.4.4; |
||||
contract Migrations { |
||||
address public owner; |
||||
uint public last_completed_migration; |
||||
|
||||
modifier restricted() {if (msg.sender == owner) _;} |
||||
|
||||
function Migrations() { |
||||
owner = msg.sender; |
||||
} |
||||
|
||||
function setCompleted(uint completed) restricted { |
||||
last_completed_migration = completed; |
||||
} |
||||
|
||||
function upgrade(address new_address) restricted { |
||||
Migrations upgraded = Migrations(new_address); |
||||
upgraded.setCompleted(last_completed_migration); |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
/** |
||||
* This contract contains a single function that is accessed using method.call |
||||
* With an unpatched testrpc it should not generate any events. |
||||
*/ |
||||
pragma solidity ^0.4.3; |
||||
|
||||
contract OnlyCall { |
||||
function addTwo(uint val) returns (uint){ |
||||
val = val + 2; |
||||
return val; |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
pragma solidity ^0.4.4; |
||||
|
||||
contract Owned { |
||||
function Owned() { owner = msg.sender; } |
||||
address owner; |
||||
} |
@ -0,0 +1,13 @@ |
||||
pragma solidity ^0.4.4; |
||||
|
||||
import "./Owned.sol"; |
||||
|
||||
contract Proxy is Owned { |
||||
function isOwner() returns (bool) { |
||||
if (msg.sender == owner) { |
||||
return true; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
pragma solidity ^0.4.3; |
||||
|
||||
contract Simple { |
||||
uint x = 0; |
||||
|
||||
function test(uint val) { |
||||
x = x + val; |
||||
} |
||||
|
||||
function getX() returns (uint){ |
||||
return x; |
||||
} |
||||
} |
@ -0,0 +1,14 @@ |
||||
// This contract should throw a parse error in instrumentSolidity.js |
||||
pragma solidity ^0.4.3; |
||||
|
||||
contract SimpleError { |
||||
uint x = 0; |
||||
|
||||
function test(uint val) { |
||||
x = x + val // <-- no semi-colon |
||||
} |
||||
|
||||
function getX() returns (uint){ |
||||
return x; |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
pragma solidity ^0.4.3; |
||||
|
||||
// This is for a test that verifies solcover can instrument a |
||||
// chained constructor/method call invoked by the new operator. |
||||
contract Chainable { |
||||
function chainWith(uint y, uint z) {} |
||||
} |
||||
contract Test { |
||||
function a(){ |
||||
new Chainable().chainWith(3, 4); |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
pragma solidity ^0.4.3; |
||||
|
||||
// This is for a test that verifies solcover can instrument a |
||||
// another kind of long CallExpression chain |
||||
contract Test { |
||||
function paySomeone(address x, address y) payable { |
||||
} |
||||
|
||||
function a() payable { |
||||
Test(0x00).paySomeone.value(msg.value)(0x00, 0x00); |
||||
} |
||||
} |
@ -0,0 +1,11 @@ |
||||
pragma solidity ^0.4.3; |
||||
|
||||
// This is for a test that verifies solcover can instrument a |
||||
// chained constructor/method call. |
||||
contract Test { |
||||
function chainWith(uint y, uint z) {} |
||||
|
||||
function a(){ |
||||
Test(0x00).chainWith(3, 4); |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
pragma solidity ^0.4.3; |
||||
|
||||
// This test verifies that an invoked function gets logged as a statement |
||||
contract Test { |
||||
function loggedAsStatement(uint x) {} |
||||
function a(){ |
||||
loggedAsStatement(5); |
||||
} |
||||
} |
@ -0,0 +1,4 @@ |
||||
pragma solidity ^0.4.3; |
||||
|
||||
contract Test { |
||||
} |
@ -0,0 +1,133 @@ |
||||
|
||||
/* |
||||
This file contains utilities for generating a mock truffle project to test solcover's |
||||
run script against. |
||||
*/ |
||||
const fs = require('fs'); |
||||
const shell = require('shelljs'); |
||||
|
||||
/** |
||||
* Installs mock truffle project at ./mock with a single contract |
||||
* and test specified by the params. |
||||
* @param {String} contract <contractName.sol> located in /test/sources/cli/ |
||||
* @param {[type]} test <testName.js> located in /test/cli/ |
||||
*/ |
||||
module.exports.install = function install(contract, test, config) { |
||||
shell.mkdir('./mock'); |
||||
shell.mkdir('./mock/contracts'); |
||||
shell.mkdir('./mock/migrations'); |
||||
shell.mkdir('./mock/test'); |
||||
|
||||
// Mock contracts
|
||||
if (Array.isArray(contract)) { |
||||
contract.forEach(item => { |
||||
shell.cp(`./test/sources/cli/${item}`, `./mock/contracts/${item}`); |
||||
}); |
||||
} else { |
||||
shell.cp(`./test/sources/cli/${contract}`, `./mock/contracts/${contract}`); |
||||
} |
||||
|
||||
shell.cp('./test/sources/cli/Migrations.sol', './mock/contracts/Migrations.sol'); |
||||
|
||||
// Mock migrations
|
||||
const initialMigration = ` |
||||
let Migrations = artifacts.require('Migrations.sol'); |
||||
module.exports = function(deployer) { |
||||
deployer.deploy(Migrations); |
||||
};`;
|
||||
|
||||
const contractLocation = `./${contract}`; |
||||
const deployContracts = ` |
||||
var contract = artifacts.require('${contractLocation}'); |
||||
module.exports = function(deployer) { |
||||
deployer.deploy(contract); |
||||
};`;
|
||||
|
||||
fs.writeFileSync('./mock/migrations/1_initial_migration.js', initialMigration); |
||||
fs.writeFileSync('./mock/migrations/2_deploy_contracts.js', deployContracts); |
||||
|
||||
// Mock test
|
||||
shell.cp(`./test/cli/${test}`, `./mock/test/${test}`); |
||||
|
||||
// Mock truffle.js
|
||||
const trufflejs = `module.exports = {
|
||||
networks: { |
||||
development: { |
||||
host: "localhost",
|
||||
port: 8545, |
||||
network_id: "*" |
||||
}}};` |
||||
; |
||||
|
||||
const configjs = `module.exports = ${JSON.stringify(config)}`; |
||||
|
||||
fs.writeFileSync('./mock/truffle.js', trufflejs); |
||||
fs.writeFileSync('./.solcover.js', configjs); |
||||
}; |
||||
|
||||
/** |
||||
* Installs mock truffle project at ./mock with a single contract |
||||
* and test specified by the params. |
||||
* @param {String} contract <contractName.sol> located in /test/sources/cli/ |
||||
* @param {[type]} test <testName.js> located in /test/cli/ |
||||
*/ |
||||
module.exports.installInheritanceTest = function installInheritanceTest(config) { |
||||
shell.mkdir('./mock'); |
||||
shell.mkdir('./mock/contracts'); |
||||
shell.mkdir('./mock/migrations'); |
||||
shell.mkdir('./mock/test'); |
||||
|
||||
// Mock contracts
|
||||
shell.cp('./test/sources/cli/Proxy.sol', './mock/contracts/Proxy.sol'); |
||||
shell.cp('./test/sources/cli/Owned.sol', './mock/contracts/Owned.sol'); |
||||
shell.cp('./test/sources/cli/Migrations.sol', './mock/contracts/Migrations.sol'); |
||||
|
||||
// Mock migrations
|
||||
const initialMigration = ` |
||||
let Migrations = artifacts.require('Migrations.sol'); |
||||
module.exports = function(deployer) { |
||||
deployer.deploy(Migrations); |
||||
};`;
|
||||
|
||||
const deployContracts = ` |
||||
var Owned = artifacts.require('./Owned.sol'); |
||||
var Proxy = artifacts.require('./Proxy.sol'); |
||||
module.exports = function(deployer) { |
||||
deployer.deploy(Owned); |
||||
deployer.link(Owned, Proxy); |
||||
deployer.deploy(Proxy); |
||||
};`;
|
||||
|
||||
fs.writeFileSync('./mock/migrations/1_initial_migration.js', initialMigration); |
||||
fs.writeFileSync('./mock/migrations/2_deploy_contracts.js', deployContracts); |
||||
|
||||
// Mock test
|
||||
shell.cp('./test/cli/inheritance.js', './mock/test/inheritance.js'); |
||||
|
||||
// Mock truffle.js
|
||||
const trufflejs = `module.exports = {
|
||||
networks: { |
||||
development: { |
||||
host: "localhost",
|
||||
port: 8545, |
||||
network_id: "*" |
||||
}}};` |
||||
; |
||||
|
||||
const configjs = `module.exports = ${JSON.stringify(config)}`; |
||||
|
||||
fs.writeFileSync('./mock/truffle.js', trufflejs); |
||||
fs.writeFileSync('./.solcover.js', configjs); |
||||
}; |
||||
|
||||
/** |
||||
* Removes mock truffle project and coverage reports generated by exec.js |
||||
*/ |
||||
module.exports.remove = function remove() { |
||||
shell.config.silent = true; |
||||
shell.rm('./.solcover.js'); |
||||
shell.rm('-Rf', 'mock'); |
||||
shell.rm('-Rf', 'coverage'); |
||||
shell.rm('coverage.json'); |
||||
shell.config.silent = false; |
||||
}; |
Loading…
Reference in new issue