Adds solc option to tests. Runs smart contract tests using remix-tests

Ref: #817
This commit is contained in:
Subramanian Venkatesan 2018-10-04 17:10:41 +05:30 committed by Pascal Precht
parent f71c158164
commit ee77ac7f7f
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
8 changed files with 5271 additions and 635 deletions

View File

@ -278,6 +278,7 @@ class Cmd {
.option('--nobrowser', __('do not start browser after coverage report is generated'))
.option('--locale [locale]', __('language to use (default: en)'))
.option('--loglevel [loglevel]', __('level of logging to display') + ' ["error", "warn", "info", "debug", "trace"]', /^(error|warn|info|debug|trace)$/i, 'warn')
.option('--solc', __('run only solidity tests'))
.description(__('run tests'))
.action(function(file, options) {
const node = options.node || 'vm';
@ -295,7 +296,7 @@ class Cmd {
}
checkDeps();
i18n.setOrDetectLocale(options.locale);
embark.runTests({file, loglevel: options.loglevel, gasDetails: options.gasDetails,
embark.runTests({file, solc:options.solc, loglevel: options.loglevel, gasDetails: options.gasDetails,
node: options.node, coverage: options.coverage, noBrowser: options.nobrowser});
});
}

View File

@ -6,99 +6,78 @@ const fs = require('../core/fs');
const assert = require('assert');
const Test = require('./test');
const EmbarkSpec = require('./reporter');
const SolcTest = require('./solc_test');
function getFilesFromDir(filePath, cb) {
fs.readdir(filePath, (err, files) => {
fs.stat(filePath, (err, fileStat) => {
const errorMessage = `File "${filePath}" doesn't exist or you don't have permission to it`.red;
if (err) {
return cb(err);
return cb(errorMessage);
}
const testFiles = files.filter((file) => {
// Only keep the .js files
// TODO: make this a configuration in embark.json
return file.substr(-3) === '.js';
}).map((file) => {
return path.join(filePath, file);
});
cb(null, testFiles);
let isDirectory = fileStat.isDirectory();
if (isDirectory) {
return fs.readdir(filePath, (err, files) => {
if (err) {
return cb(err);
}
async.map(files, (file, _cb) => {
getFilesFromDir(path.join(filePath, file), _cb);
}, (err, arr) => {
if (err) {
return cb(errorMessage);
}
cb(null, arr.reduce((a,b) => a.concat(b), []));
});
});
}
cb(null, [filePath]);
});
}
module.exports = {
run: function (options) {
let failures = 0;
let filePath = options.file;
const loglevel = options.loglevel || 'warn';
if (!filePath) {
filePath = 'test/';
}
function runJSTests(files, options, cb) {
const loglevel = options.loglevel || 'warn';
async.waterfall([
function setupGlobalNamespace(next) {
// TODO put default config
const test = new Test({loglevel, node: options.node});
global.embark = test;
global.assert = assert;
global.config = test.config.bind(test);
async.waterfall([
function checkIfDir(next) {
if (filePath.substr(-1) === '/') {
return next(null, null);
let deprecatedWarning = function () {
console.error(__('%s are not supported anymore', 'EmbarkSpec & deployAll').red);
console.info(__('You can learn about the new revamped tests here: %s', 'https://embark.status.im/docs/testing.html'.underline));
process.exit();
};
global.deployAll = deprecatedWarning;
global.EmbarkSpec = {};
global.EmbarkSpec.deployAll = deprecatedWarning;
// Override require to enable `require('Embark/contracts/contractName');`
const Module = require('module');
const originalRequire = require('module').prototype.require;
Module.prototype.require = function (requireName) {
if (requireName.startsWith('Embark')) {
return test.require(...arguments);
}
fs.stat(filePath, (err, stats) => {
if (err) {
return next(`File "${filePath}" doesn't exist or you don't have permission to it`.red);
}
if (stats.isDirectory()) {
return next(null, null);
}
next(null, [filePath]);
});
},
function getFiles(files, next) {
if (files) {
return next(null, files);
}
getFilesFromDir(filePath, (err, files) => {
if (err) {
console.error('Error while reading the directory');
return next(err);
}
next(null, files);
});
},
function setupGlobalNamespace(files, next) {
// TODO put default config
const test = new Test({loglevel, node: options.node, coverage: options.coverage});
global.embark = test;
global.assert = assert;
global.config = test.config.bind(test);
return originalRequire.apply(this, arguments);
};
let deprecatedWarning = function () {
console.error(__('%s are not supported anymore', 'EmbarkSpec & deployAll').red);
console.info(__('You can learn about the new revamped tests here: %s', 'https://embark.status.im/docs/testing.html'.underline));
process.exit();
};
// TODO: this global here might not be necessary at all
global.web3 = global.embark.web3;
global.deployAll = deprecatedWarning;
global.EmbarkSpec = {};
global.EmbarkSpec.deployAll = deprecatedWarning;
global.contract = function (describeName, callback) {
return Mocha.describe(describeName, callback);
};
// Override require to enable `require('Embark/contracts/contractName');`
const Module = require('module');
const originalRequire = require('module').prototype.require;
Module.prototype.require = function (requireName) {
if (requireName.startsWith('Embark')) {
return test.require(...arguments);
}
return originalRequire.apply(this, arguments);
};
// TODO: this global here might not be necessary at all
global.web3 = global.embark.web3;
global.contract = function (describeName, callback) {
return Mocha.describe(describeName, callback);
};
test.init((err) => {
next(err, files);
});
},
function executeForAllFiles(files, next) {
async.eachLimit(files, 1, (file, eachCb) => {
test.init((err) => {
next(err, files);
});
},
function executeForAllFiles(files, next) {
let fns = files.map((file) => {
return (cb) => {
const mocha = new Mocha();
mocha.reporter(EmbarkSpec, {
events: global.embark.engine.events,
@ -107,9 +86,7 @@ module.exports = {
});
mocha.addFile(file);
mocha.suite.timeout(0);
mocha.suite.beforeAll('Wait for deploy', (done) => {
if (global.embark.needConfig) {
global.config({});
@ -119,16 +96,96 @@ module.exports = {
});
});
mocha.run(function (fails) {
failures += fails;
mocha.suite.removeAllListeners();
// Mocha prints the error already
eachCb();
cb(null, fails);
});
}, next);
};
});
async.series(fns, next);
}
], (err, runs) => {
if(err) {
return cb(err);
}
let failures = runs.reduce((acc, val) => acc + val, 0);
fs.remove('.embark/contracts', (_err) => {
cb(null, {failures});
});
});
}
function runSolidityTests(files, options, cb) {
console.log('Running solc tests');
const loglevel = options.loglevel || 'warn';
let solcTest = new SolcTest({loglevel, node: options.node});
global.embark = solcTest;
async.waterfall([
function initEngine(next) {
solcTest.init(next);
},
function setupTests(next) {
solcTest.setupTests(files, next);
},
function runTests(_reciepts ,cb) {
let fns = files.map((file) => {
return (cb) => {
return solcTest.runTests(file, cb);
};
});
async.series(fns, cb);
}
], (err, results) => {
if(err) return cb(err);
let totalPass = 0;
let totalFailures = 0;
results.forEach((result) => {
result.forEach((r) => {
totalPass = totalPass + r.passingNum;
totalFailures = totalFailures + r.failureNum;
});
});
cb(null, {failures: totalFailures, pass: totalPass});
});
}
module.exports = {
run: function (options) {
let filePath = options.file;
if (!filePath) {
filePath = 'test';
}
async.waterfall([
function getFiles(next) {
getFilesFromDir(filePath, next);
},
function runCoverage(next) {
function groupFiles(files, next) {
let jsFiles = files.filter((filename) => filename.substr(-3) === '.js');
let solidityFiles = files.filter((filename) => filename.indexOf('_test.sol') > 0);
next(null, {jsFiles, solidityFiles});
},
function runTests(files, next) {
const fns = [];
if (!options.solc && files.jsFiles.length > 0) {
let fn = (callback) => {
runJSTests(files.jsFiles, options, callback);
};
fns.push(fn);
}
if(files.solidityFiles.length > 0) {
let fn = (callback) => {
runSolidityTests(files.solidityFiles, options, callback);
};
fns.push(fn);
}
if(fns.length === 0){
return next('No tests to run');
}
async.series(fns, next);
},
function runCoverage(results, next) {
if (!options.coverage) {
return next();
return next(null, results);
}
global.embark.events.emit('tests:finished', function() {
@ -139,9 +196,9 @@ module.exports = {
}
console.log(`Coverage report created. You can find it here: ${fs.dappPath('coverage/__root__/index.html')}\n`);
const opn = require('opn');
const _next = () => { next(); };
const _next = () => { next(null, results); };
if (options.noBrowser) {
return next();
return next(null, results);
}
opn(fs.dappPath('coverage/__root__/index.html'), {wait: false})
.then(() => new Promise(resolve => setTimeout(resolve, 1000)))
@ -149,21 +206,18 @@ module.exports = {
});
});
}
], (err) => {
], (err, results) => {
if (err) {
console.error(err);
process.exit(1);
}
if (failures) {
console.error(` > Total number of failures: ${failures}`.red.bold);
let totalFailures = results.reduce((acc, result) => acc + result.failures, 0);
if (totalFailures) {
console.error(` > Total number of failures: ${totalFailures}`.red.bold);
} else {
console.log(' > All tests passed'.green.bold);
}
// Clean contracts folder for next test run
fs.remove('.embark/contracts', (_err) => {
process.exit(failures);
});
process.exit(totalFailures);
});
}
};

124
lib/tests/solc_test.js Normal file
View File

@ -0,0 +1,124 @@
const Test = require('./test');
const async = require('async');
const fs = require('fs-extra');
const File = require('./../core/file');
const remixTests = require('remix-tests');
const Base = require('mocha/lib/reporters/base');
const color = Base.color;
class SolcTest extends Test {
constructor(options) {
super(options);
this.assertLibCode = remixTests.assertLibCode;
}
init(cb) {
const self = this;
super.init(() => {
let assertFile = new File({
filename: 'remix_tests.sol',
type: File.types.custom,
path: 'remix_tests.sol',
resolver: (callback) => {
callback(self.assertLibCode);
}});
self.engine.config.contractsFiles.push(assertFile);
cb();
});
}
setupTests(files, cb) {
const self = this;
files.forEach((file) => {
let testFile = self._prepareContractForTest(file);
self.engine.config.contractsFiles.push(new File({filename: file, type: File.types.custom, path: file, resolver: function (callback) {
callback(testFile);
}}));
});
async.waterfall([
function initWeb3Provider(next) {
self.initWeb3Provider(next);
},
function resetContracts(next) {
self.engine.events.request("contracts:reset:dependencies", next);
},
function compile(next) {
console.info('Compiling contracts'.cyan);
self.engine.events.request("contracts:build", false, next);
},
function determineContractsToDeploy(next) {
self.engine.events.request("contracts:list", (err, contracts) => {
let contractsToDeploy = contracts.filter((contract) => contract.filename.indexOf('_test.sol') >=0);
let assertLib = contracts.filter((contract) => contract.filename === 'remix_tests.sol')[0];
next(null, [assertLib].concat(contractsToDeploy));
});
},
function deployContracts(contracts, next) {
console.info('Deploying contracts'.cyan);
let fns = [];
contracts.forEach((contract) => {
contract._gasLimit = self.gasLimit;
let fn = (cb) => {
self.engine.events.request('deploy:contract', contract, cb);
};
fns.push(fn);
});
async.series(fns, next);
}
],cb);
}
runTests(file, cb) {
console.info('Running tests'.cyan);
const self = this;
self.engine.events.request('contracts:all', (err, contracts) => {
let contractsToTest = [];
Object.keys(contracts).forEach((contract) => {
if(contracts[contract].filename === file) {
contractsToTest.push(contracts[contract]);
}
});
let fns = [];
contractsToTest.forEach((contract) => {
let contractObject = self._convertToWeb3(contract);
let fn = (_callback) => {
// TODO: web3 is not injected into the function. Issue has been raised on remixTests.
// To fix once web3 has been made injectable.
remixTests.runTest(contract.className, contractObject, self._prettyPrint.bind(self), _callback);
};
fns.push(fn);
});
async.series(fns, cb);
});
}
_convertToWeb3(contract) {
let contractObject = new this.web3.eth.Contract(contract.abiDefinition);
contractObject.options.address = contract.deployedAddress;
contractObject.options.from = contract.deploymentAccount;
contractObject.options.gas = contract.gas;
contractObject.filename = contract.filename;
return contractObject;
}
// dynamically insert Assert library as an import
// regexIndexOf has been added to String's prototype in remix-tests module
_prepareContractForTest(file) {
let c = fs.readFileSync(file).toString();
const s = /^(import)\s['"](remix_tests.sol)['"];/gm;
if (c.regexIndexOf(s) < 0) {
c = c.replace(/(pragma solidity \^\d+\.\d+\.\d+;)/, '$1\nimport \"remix_tests.sol\";');
}
return c;
}
_prettyPrint(obj) {
if (obj.type === 'contract') {
console.log(color('suite', '%s'), obj.value);
} else if(obj.type === 'testPass') {
let fmt = color('checkmark', ' ' + Base.symbols.ok) + color('pass', ' %s');
console.log(fmt, obj.value);
} else if(obj.type === 'testFailure') {
let fmt = color('fail', ' %s %s');
console.log(fmt, Base.symbols.err, obj.value);
}
}
}
module.exports = SolcTest;

View File

@ -41,6 +41,12 @@ class Test {
this.logsSubscription = null;
this.needConfig = true;
this.web3 = new Web3();
this.engine = new Engine({
env: this.options.env || 'test',
// TODO: config will need to detect if this is a obj
embarkConfig: this.options.embarkConfig || 'embark.json',
interceptLogs: false
});
}
initWeb3Provider(callback) {
@ -150,17 +156,12 @@ class Test {
compileOnceOnly: true,
disableOptimizations: true
});
this.events.request('deploy:setGasLimit', 6000000);
this.gasLimit = 6000000;
this.engine.events.request('deploy:setGasLimit', this.gasLimit);
}
init(callback) {
let self = this;
this.engine = new Engine({
env: this.options.env || 'test',
// TODO: config will need to detect if this is a obj
embarkConfig: this.options.embarkConfig || 'embark.json',
interceptLogs: false
});
async.waterfall([
function initEngine(cb) {
self.engine.init({

5483
npm-shrinkwrap.json generated

File diff suppressed because it is too large Load Diff

View File

@ -89,6 +89,7 @@
"pkg-up": "2.0.0",
"promptly": "2.2.0",
"propose": "0.0.5",
"remix-tests": "0.0.13",
"request": "2.88.0",
"sass-loader": "7.1.0",
"serve-static": "1.13.2",
@ -106,8 +107,8 @@
"viz.js": "1.8.2",
"web3": "1.0.0-beta.34",
"webpack": "4.19.0",
"window-size": "1.1.1",
"ws": "^6.0.0"
"websocket": "1.0.28",
"window-size": "1.1.1"
},
"author": "Iuri Matias <iuri.matias@gmail.com>",
"contributors": [],

View File

@ -2,14 +2,6 @@ pragma solidity ^0.4.17;
import "ownable.sol";
library Assert {
event TestEvent(bool passed, string message);
function triggerEvent(bool passed, string message) internal {
emit TestEvent(passed, message);
}
}
contract SimpleStorage is Ownable {
uint public storedData;
@ -24,7 +16,6 @@ contract SimpleStorage is Ownable {
for(uint i = 0; i < 1000; i++) {
storedData += i;
}
Assert.triggerEvent(true, "hi");
}
function set2(uint x) public onlyOwner {

View File

@ -0,0 +1,17 @@
pragma solidity ^0.4.7;
import "./../ownable.sol";
contract OwnableTests {
Ownable own;
function beforeAll() {
own = new Ownable();
}
function beforeEach() {
}
function shouldnotbezeroAddress() public {
Assert.equal(true, true, "owner is uninitialized");
}
}