Merge pull request #73 from sc-forks/signing-test

Add testrpc-sc signing test and lint
pull/76/head
c-g-e-w-e-k-e- 7 years ago committed by GitHub
commit ebda87dd3c
  1. 2
      .eslintrc
  2. 1
      bin/exec.js
  3. 65
      lib/app.js
  4. 10
      lib/instrumenter.js
  5. 4
      lib/truffleConfig.js
  6. 17
      test/app.js
  7. 31
      test/cli/sign.js
  8. 4
      test/conditional.js
  9. 8
      test/statements.js
  10. 34
      test/util/mockTestCommand.js

@ -14,7 +14,7 @@
/* General */
"consistent-return": [0],
"no-return-assign": [0],
"complexity": ["error", 6],
"complexity": ["error", 20],
"eol-last": [0],
"eqeqeq": ["error", "smart"],
"max-len": ["error", 150, 2],

@ -2,6 +2,7 @@
const App = require('./../lib/app.js');
const reqCwd = require('req-cwd');
const death = require('death');
const log = console.log;
const config = reqCwd.silent('./.solcover.js') || {};

@ -15,20 +15,20 @@ const gasPriceHex = 0x01; // Low gas price
* Coverage Runner
*/
class App {
constructor(config){
constructor(config) {
this.coverageDir = './coverageEnv'; // Env that instrumented .sols are tested in
// Options
this.network = ''; // Default truffle network execution flag
this.silence = ''; // Default log level: configurable by --silence
this.silence = ''; // Default log level passed to shell
this.log = console.log;
// Other
this.testrpcProcess; // ref to testrpc server we need to close on exit
this.events; // ref to string loaded from 'allFiredEvents'
this.testrpcProcess = null; // ref to testrpc server we need to close on exit
this.events = null; // ref to string array loaded from 'allFiredEvents'
this.testsErrored = null; // flag set to non-null if truffle tests error
this.coverage = new CoverageMap(); // initialize a coverage map
// Config
this.config = config || {};
this.workingDir = config.dir || '.'; // Relative path to contracts folder
@ -36,7 +36,7 @@ class App {
this.skipFiles = config.skipFiles || []; // Which files should be skipped during instrumentation
this.norpc = config.norpc || false; // Launch testrpc-sc internally?
this.port = config.port || 8555; // Port testrpc should listen on
this.copyNodeModules = config.copyNodeModules || false; // Copy node modules into coverageEnv?
this.testrpcOptions = config.testrpcOptions || null; // Options for testrpc-sc
this.testCommand = config.testCommand || null; // Optional test command
@ -44,23 +44,22 @@ class App {
this.setLoggingLevel(config.silent);
}
//-------------------------------------- Methods ------------------------------------------------
// -------------------------------------- Methods ------------------------------------------------
/**
* Generates a copy of the target project configured for solidity-coverage and saves to
* Generates a copy of the target project configured for solidity-coverage and saves to
* the coverage environment folder. Process exits(1) if try fails
*/
generateCoverageEnvironment(){
generateCoverageEnvironment() {
this.log('Generating coverage environment');
try {
let files = shell.ls(this.workingDir);
const nmIndex = files.indexOf('node_modules');
// Removes node_modules from array (unless requested).
if (!this.copyNodeModules && nmIndex > -1) {
files.splice(nmIndex, 1);
files.splice(nmIndex, 1);
}
files = files.map(file => `${this.workingDir}/${file}`);
@ -73,10 +72,10 @@ class App {
if (truffleConfig && truffleConfig.networks && truffleConfig.networks.coverage) {
this.port = truffleConfig.networks.coverage.port || this.port;
this.network = '--network coverage';
// No coverage network defaults to the dev network on port 8555, high gas / low price.
// No coverage network defaults to the dev network on port 8555, high gas / low price.
} else {
const trufflejs = defaultTruffleConfig(this.port, gasLimitHex, gasPriceHex)
const trufflejs = defaultTruffleConfig(this.port, gasLimitHex, gasPriceHex);
fs.writeFileSync(`${this.coverageDir}/truffle.js`, trufflejs);
}
} catch (err) {
@ -93,8 +92,7 @@ class App {
* + Save instrumented contract in the coverage environment folder where covered tests will run
* + Add instrumentation info to the coverage map
*/
instrumentTarget(){
instrumentTarget() {
this.skipFiles = this.skipFiles.map(contract => `${this.coverageDir}/contracts/${contract}`);
this.skipFiles.push(`${this.coverageDir}/contracts/Migrations.sol`);
@ -126,9 +124,8 @@ class App {
* Changes here should be also be added to the before() block of test/run.js).
* @return {Promise} Resolves when testrpc prints 'Listening' to std out / norpc is true.
*/
launchTestrpc(){
launchTestrpc() {
return new Promise((resolve, reject) => {
if (!this.norpc) {
const defaultRpcOptions = `--gasLimit ${gasLimitHex} --accounts ${this.accounts} --port ${this.port}`;
const options = this.testrpcOptions || defaultRpcOptions;
@ -137,13 +134,13 @@ class App {
// Launch
this.testrpcProcess = childprocess.exec(command + options, null, (err, stdout, stderr) => {
if (err) {
if(stdout) this.log(`testRpc stdout:\n${stdout}`);
if(stderr) this.log(`testRpc stderr:\n${stderr}`);
if (stdout) this.log(`testRpc stdout:\n${stdout}`);
if (stderr) this.log(`testRpc stderr:\n${stderr}`);
this.cleanUp('testRpc errored after launching as a childprocess.');
}
});
// Resolve when testrpc logs that it's listening.
// Resolve when testrpc logs that it's listening.
this.testrpcProcess.stdout.on('data', data => {
if (data.includes('Listening')) {
this.log(`Launched testrpc on port ${this.port}`);
@ -153,7 +150,7 @@ class App {
} else {
return resolve();
}
})
});
}
/**
@ -162,10 +159,8 @@ class App {
* as its own statement for command line options to work, apparently.
* Also reads the 'allFiredEvents' log.
*/
runTestCommand(){
runTestCommand() {
try {
const defaultCommand = `truffle test ${this.network} ${this.silence}`;
const command = this.testCommand || defaultCommand;
this.log(`Running: ${command}\n(this can take a few seconds)...`);
@ -196,8 +191,7 @@ class App {
/**
* Generate coverage / write coverage report / run istanbul
*/
generateReport(){
generateReport() {
const collector = new istanbul.Collector();
const reporter = new istanbul.Reporter();
@ -218,20 +212,19 @@ class App {
resolve();
});
} catch (err) {
const msg = 'There was a problem generating the coverage map / running Istanbul.\n';
this.cleanUp(msg + err);
const msg = 'There was a problem generating the coverage map / running Istanbul.\n';
this.cleanUp(msg + err);
}
})
});
}
// ------------------------------------------ Utils ----------------------------------------------
/**
* Allows config to turn logging off (for CI)
* @param {Boolean} isSilent
* @param {Boolean} isSilent
*/
setLoggingLevel(isSilent){
setLoggingLevel(isSilent) {
if (isSilent) {
this.silence = '> /dev/null 2>&1';
this.log = () => {};

@ -11,12 +11,12 @@ function createOrAppendInjectionPoint(contract, key, value) {
}
}
instrumenter.prePosition = function prePosition(expression){
if (expression.right.type === 'ConditionalExpression' &&
expression.left.type === 'MemberExpression'){
instrumenter.prePosition = function prePosition(expression) {
if (expression.right.type === 'ConditionalExpression' &&
expression.left.type === 'MemberExpression') {
expression.start -= 2;
}
}
};
instrumenter.instrumentAssignmentExpression = function instrumentAssignmentExpression(contract, expression) {
// The only time we instrument an assignment expression is if there's a conditional expression on
@ -29,7 +29,7 @@ instrumenter.instrumentAssignmentExpression = function instrumentAssignmentExpre
type: 'literal', string: '; (,' + expression.left.name + ')',
});
instrumenter.instrumentConditionalExpression(contract, expression.right);
} else if (expression.left.type === 'MemberExpression'){
} else if (expression.left.type === 'MemberExpression') {
createOrAppendInjectionPoint(contract, expression.left.start, {
type: 'literal', string: '(,',
});

@ -1,5 +1,5 @@
module.exports = function truffleConfig(port, gasLimit, gasPrice) {
return `
return `
module.exports = {
networks: {
development: {
@ -10,5 +10,5 @@ module.exports = function truffleConfig(port, gasLimit, gasPrice) {
gasPrice: ${gasPrice}
}
}
};`
};`;
};

@ -27,9 +27,15 @@ describe('app', () => {
norpc: true,
};
before(() => {
before(done => {
const command = `./node_modules/.bin/testrpc-sc --gasLimit 0xfffffffffff --port ${port}`;
testrpcProcess = childprocess.exec(command);
testrpcProcess.stdout.on('data', data => {
if (data.includes('Listening')) {
done();
}
});
});
afterEach(() => {
@ -198,6 +204,14 @@ describe('app', () => {
collectGarbage();
});
it('testrpc-sc signs and recovers messages correctly', () => {
// sign.js signs and recovers
mock.install('Simple.sol', 'sign.js', config);
shell.exec(script);
assert(shell.error() === null, 'script should not error');
collectGarbage();
});
it('tests require assets outside of test folder: should generate coverage, cleanup & exit(0)', () => {
// Directory should be clean
assert(pathExists('./coverage') === false, 'should start without: coverage');
@ -239,6 +253,7 @@ describe('app', () => {
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');

@ -0,0 +1,31 @@
/* eslint-env node, mocha */
/* global artifacts, contract, assert */
const Web3 = require('web3');
const ethUtil = require('ethereumjs-util');
const provider = new Web3.providers.HttpProvider('http://localhost:8555'); // testrpc-sc
const web3 = new Web3(provider);
const Simple = artifacts.require('./Simple.sol');
contract('Simple', accounts => {
it('should set x to 5', () => {
let simple;
return Simple.deployed()
.then(instance => instance.test(5)) // We need this line to generate some coverage
.then(() => {
const message = 'Enclosed is my formal application for permanent residency in New Zealand';
const messageSha3 = web3.sha3(message);
const signature = web3.eth.sign(accounts[0], messageSha3);
const messageBuffer = new Buffer(messageSha3.replace('0x', ''), 'hex');
const messagePersonalHash = ethUtil.hashPersonalMessage(messageBuffer);
const sigParams = ethUtil.fromRpcSig(signature);
const publicKey = ethUtil.ecrecover(messagePersonalHash, sigParams.v, sigParams.r, sigParams.s);
const senderBuffer = ethUtil.pubToAddress(publicKey);
const sender = ethUtil.bufferToHex(senderBuffer);
assert.equal(sender, accounts[0]);
});
});
});

@ -168,13 +168,13 @@ describe('conditional statements', () => {
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
'11': 1, '12': 1,
11: 1, 12: 1,
});
assert.deepEqual(mapping[filePath].b, {
1: [0, 1],
});
assert.deepEqual(mapping[filePath].s, {
'1': 1, '2': 1,
1: 1, 2: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1,

@ -109,14 +109,14 @@ describe('generic statements', () => {
vm.execute(info.contract, 'a', []).then(events => {
const mapping = coverage.generate(events, pathPrefix);
assert.deepEqual(mapping[filePath].l, {
6: 1, 10: 1, 11: 1
6: 1, 10: 1, 11: 1,
});
assert.deepEqual(mapping[filePath].b, {});
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1
assert.deepEqual(mapping[filePath].s, {
1: 1, 2: 1, 3: 1,
});
assert.deepEqual(mapping[filePath].f, {
1: 1, 2: 1
1: 1, 2: 1,
});
done();
}).catch(done);

@ -1,22 +1,22 @@
#!/usr/bin/env node
const fs = require('fs')
const request = require('request')
const fs = require('fs');
const request = require('request');
request({
uri: 'http://localhost:8888',
body: {
jsonrpc: "2.0",
method: "web3_clientVersion",
params: [],
id: 0
},
json: true
uri: 'http://localhost:8888',
body: {
jsonrpc: '2.0',
method: 'web3_clientVersion',
params: [],
id: 0,
},
json: true,
}, (error, response, body) => {
if(error) {
console.error(error)
process.exit(1)
}
fs.writeFileSync('../allFiredEvents', 'foobar')
process.exit(0)
})
if (error) {
console.error(error);
process.exit(1);
}
fs.writeFileSync('../allFiredEvents', 'foobar');
process.exit(0);
});

Loading…
Cancel
Save