Currently, the tests just check that after instrumentation, the contract still compiles. Of course we actually want to check that it compiles correctly, and returns the right instrumentation information. @cgewecke identified istanbul's tests as the sort of thing to aspire to, which makes sense given the use of istanbul in this project!
The below snipped is a (very rough and ready) reimplementation of the first test in our test suite (NB with the throw removed from the test contract, which stops any events from firing). It takes the contract, instruments it, compiles it, deploys it to a chain in memory, calls a function, and then compares the results to what we'd expect our instrumentation to conclude.
Obviously, this all needs to be put into a wrapper so that the rests of the tests are DRY, and similarly should use the same code that runCoveredTests.js uses, rather than just copying it into the helper.
I also don't like the use of async here, but that's a discussion for another issue, and is a consequence of me basing this off of a tutorial in `ethereumjs-vm, which I hadn't used before.
it('should compile after instrumenting else statements with brackets',function(done){
var contract = util.getCode('if/else-with-brackets.sol');
var instrumentedContractInfo = getInstrumentedVersion(contract, "test.sol", true);
var coverage = {};
var canonicalContractPath = path.resolve('./../originalContracts/' + "test.sol");
coverage[canonicalContractPath] = { "l": {}, "path": canonicalContractPath, "s": {}, "b": {}, "f": {}, "fnMap": {}, "statementMap": {}, "branchMap": {} };
for (idx in instrumentedContractInfo.runnableLines) {
coverage[canonicalContractPath]["l"][instrumentedContractInfo.runnableLines[idx]] = 0;
}
coverage[canonicalContractPath].fnMap = instrumentedContractInfo.fnMap;
for (x=1; x<=Object.keys(instrumentedContractInfo.fnMap).length; x++ ){
coverage[canonicalContractPath]["f"][x] = 0;
}
coverage[canonicalContractPath].branchMap = instrumentedContractInfo.branchMap;
for (x=1; x<=Object.keys(instrumentedContractInfo.branchMap).length; x++ ){
coverage[canonicalContractPath]["b"][x] = [0,0];
}
coverage[canonicalContractPath].statementMap= instrumentedContractInfo.statementMap;
for (x=1; x<=Object.keys(instrumentedContractInfo.statementMap).length; x++ ){
coverage[canonicalContractPath]["s"][x] = 0;
}
var output = solc.compile(instrumentedContractInfo.contract, 1);
var code = output.contracts.Test.bytecode;
var VM = require('ethereumjs-vm')
var Account = require('ethereumjs-account')
var utils = require('ethereumjs-util');
var Transaction = require('ethereumjs-tx');
var Trie = require('merkle-patricia-tree');
var stateTrie = new Trie();
var vm = new VM({state: stateTrie});
//code needs to be a buffer
code = new Buffer(code, 'hex')
//we will use this later
var storageRoot;
//Don't use this address for anything, obviously!
var secretKey = 'e81cb653c260ee12c72ec8750e6bfd8a4dc2c3d7e3ede68dd2f150d0a67263d8';
var address = new Buffer('7caf6f9bc8b3ba5c7824f934c826bd6dc38c8467', 'hex');
function setup(cb) {
//create a new account
var account = new Account();
//give the account some wei.
//This needs to be a `Buffer` or a string. all strings need to be in hex.
account.balance = 'f00000000000000000';
//store in the trie
stateTrie.put(address, account.serialize(), cb);
}
var rawTx = {
gasPrice: '1',
gasLimit: 'ffffff',
data: code
};
var createdAddress;
function runTx(raw, cb) {
//create a new transaction out of the json
var tx = new Transaction(raw);
tx.sign(new Buffer(secretKey, 'hex'));
//run the tx
vm.runTx({tx:tx}, function(err, results) {
if (err){
return cb(err)
}
createdAddress = results.createdAddress;
cb();
});
}
var rawTx2 = {
gasPrice: '1',
gasLimit: 'ffffff',
data: utils.bufferToHex(utils.sha3('a(uint256)')).substr(0,10) + "0".repeat(63) + "1" ,
//Note even though the contract is a(uint), uint is just an alias for uint256 so we have to use
//that when working out the hash.
nonce: 0x1
};
var seenEvents = "";
function runTest(raw, cb){
raw.to = utils.bufferToHex(createdAddress);
var tx = new Transaction(raw);
tx.sign(new Buffer(secretKey, 'hex'));
vm.runTx({tx:tx}, function(err, results) {
if (err){
return cb(err)
}
results.vm.runState.logs.map(function(log){
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');
seenEvents += JSON.stringify(toWrite) + '\n'
})
cb();
});
}
function checkTest(cb){
var events = seenEvents.split('\n').slice(0,-1);
events.map(function(event){
event = JSON.parse(event);
if (event.topics.indexOf("b8995a65f405d9756b41a334f38d8ff0c93c4934e170d3c1429c3e7ca101014d") >= 0) {
var data = SolidityCoder.decodeParams(["string", "uint256"], event.data.replace("0x", ""));
var canonicalContractPath = path.resolve('./../originalContracts/' + path.basename(data[0]));
coverage[canonicalContractPath]["l"][data[1].toNumber()] += 1;
}else if(event.topics.indexOf("d4ce765fd23c5cc3660249353d61ecd18ca60549dd62cb9ca350a4244de7b87f")>=0){
var data = SolidityCoder.decodeParams(["string", "uint256"], event.data.replace("0x", ""));
var canonicalContractPath = path.resolve('./../originalContracts/' + path.basename(data[0]));
coverage[canonicalContractPath]["f"][data[1].toNumber()] += 1;
}else if(event.topics.indexOf("d4cf56ed5ba572684f02f889f12ac42d9583c8e3097802060e949bfbb3c1bff5")>=0){
var data = SolidityCoder.decodeParams(["string", "uint256", "uint256"], event.data.replace("0x", ""));
var canonicalContractPath = path.resolve('./../originalContracts/' + path.basename(data[0]));
coverage[canonicalContractPath]["b"][data[1].toNumber()][data[2].toNumber()] += 1;
}else if(event.topics.indexOf("b51abbff580b3a34bbc725f2dc6f736e9d4b45a41293fd0084ad865a31fde0c8")>=0){
var data = SolidityCoder.decodeParams(["string","uint256"], event.data.replace("0x", ""));
var canonicalContractPath = path.resolve('./../originalContracts/' + path.basename(data[0]));
coverage[canonicalContractPath]["s"][data[1].toNumber()]+= 1;
}
})
assert.deepEqual(coverage[canonicalContractPath].l, { '5': 1, '6': 1, '8': 0 })
assert.deepEqual(coverage[canonicalContractPath].b, { '1': [ 1, 0 ] })
assert.deepEqual(coverage[canonicalContractPath].s, { '1': 1, '2': 1, '3': 0 })
assert.deepEqual(coverage[canonicalContractPath].f, { '1': 1 })
cb();
}
async.series([
setup,
async.apply(runTx, rawTx), //Deploy the contract
async.apply(runTest, rawTx2), //Call the contract
checkTest
], done);
})
Currently, the tests just check that after instrumentation, the contract still compiles. Of course we actually want to check that it compiles correctly, and returns the right instrumentation information. @cgewecke identified istanbul's tests as the sort of thing to aspire to, which makes sense given the use of istanbul in this project!
The below snipped is a (very rough and ready) reimplementation of the first test in our test suite (NB with the throw removed from the test contract, which stops any events from firing). It takes the contract, instruments it, compiles it, deploys it to a chain in memory, calls a function, and then compares the results to what we'd expect our instrumentation to conclude.
Obviously, this all needs to be put into a wrapper so that the rests of the tests are DRY, and similarly should use the same code that
runCoveredTests.js
uses, rather than just copying it into the helper.I also don't like the use of async here, but that's a discussion for another issue, and is a consequence of me basing this off of a tutorial in `ethereumjs-vm, which I hadn't used before.