Code coverage for Solidity smart-contracts
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
solidity-coverage/test/units/truffle/standard.js

399 lines
11 KiB

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/truffle.plugin');
// =======================
// Standard Use-case Tests
// =======================
describe('Truffle Plugin: standard use cases', function() {
let truffleConfig;
let solcoverConfig;
beforeEach(() => {
mock.clean();
mock.loggerOutput.val = '';
solcoverConfig = {};
truffleConfig = mock.getDefaultTruffleConfig();
verify.cleanInitialState();
})
afterEach(() => mock.clean());
it('simple contract', async function(){
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
verify.coverageGenerated(truffleConfig);
const output = mock.getOutput(truffleConfig);
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"'
);
});
// Instrumentation speed is fine - but this takes solc almost a minute to compile
// Unskip whenever modifying the instrumentation files though.....
it.skip('with many unbracketed statements (time check)', async function() {
truffleConfig.compilers.solc.version = "0.4.24";
mock.install('Oraclize', 'oraclize.js', solcoverConfig, truffleConfig, true);
await plugin(truffleConfig);
});
// This project has three contract suites and uses .deployed() instances which
// depend on truffle's migratons and the inter-test evm_revert / evm_snapshot mechanism.
it('with multiple migrations (evm_reverts repeatedly)', async function() {
mock.installFullProject('multiple-migrations');
await plugin(truffleConfig);
const expected = [
{
file: mock.pathToContract(truffleConfig, 'ContractA.sol'),
pct: 100
},
{
file: mock.pathToContract(truffleConfig, 'ContractB.sol'),
pct: 100,
},
{
file: mock.pathToContract(truffleConfig, 'ContractC.sol'),
pct: 100,
},
];
verify.lineCoverage(expected);
});
it('tests in first layer and in a sub-folder', async function() {
mock.installFullProject('tests-folder');
await plugin(truffleConfig);
const expected = [
{
file: mock.pathToContract(truffleConfig, 'ContractA.sol'),
pct: 100
},
{
file: mock.pathToContract(truffleConfig, 'ContractB.sol'),
pct: 100,
},
{
file: mock.pathToContract(truffleConfig, 'ContractC.sol'),
pct: 100,
},
];
verify.lineCoverage(expected);
});
it('with relative path solidity imports', async function() {
mock.installFullProject('import-paths');
await plugin(truffleConfig);
});
it('uses libraries', async function() {
mock.installFullProject('libraries');
await plugin(truffleConfig);
});
it('uses native solidity tests', async function(){
mock.install('Simple', 'TestSimple.sol', solcoverConfig);
truffleConfig.logger = mock.testLogger;
await plugin(truffleConfig);
assert(
mock.loggerOutput.val.includes('native solidity tests'),
`Should warn it is skipping native solidity tests: ${mock.loggerOutput.val}`
);
});
it('uses inheritance', async function() {
mock.installDouble(
['Proxy', 'Owned'],
'inheritance.js',
solcoverConfig
);
await plugin(truffleConfig);
verify.coverageGenerated(truffleConfig);
const output = mock.getOutput(truffleConfig);
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);
await plugin(truffleConfig);
verify.coverageGenerated(truffleConfig);
const output = mock.getOutput(truffleConfig);
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);
await plugin(truffleConfig);
verify.coverageGenerated(truffleConfig);
const output = mock.getOutput(truffleConfig);
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
// Test times out on CircleCI @ 100000 ms. Fine locally though.
it('deployment cost > block gasLimit', async function() {
if (process.env.CI) return;
mock.install('Expensive', 'block-gas-limit.js', solcoverConfig);
await plugin(truffleConfig);
});
// Simple.sol with a failing assertion in a truffle test
it('unit tests failing', async function() {
mock.install('Simple', 'truffle-test-fail.js', solcoverConfig);
try {
await plugin(truffleConfig);
assert.fail()
} catch(err){
assert(err.message.includes('failed under coverage'));
}
verify.coverageGenerated(truffleConfig);
const output = mock.getOutput(truffleConfig);
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 test tightly coupled to the ganache version in truffle dev dep
it('uses the server from truffle by default', async function(){
truffleConfig.logger = mock.testLogger;
truffleConfig.version = true;
// Baseline inequality check
const truffleClientVersion = "v2.5.7";
// Truffle client
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
assert(
mock.loggerOutput.val.includes(truffleClientVersion),
`Should use truffles ganache: ${mock.loggerOutput.val}`
);
});
it('uses the fallback server', async function(){
truffleConfig.logger = mock.testLogger;
solcoverConfig.forceBackupServer = true;
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
assert(
mock.loggerOutput.val.includes("Using ganache-cli"),
`Should notify about backup server module: ${mock.loggerOutput.val}`
);
});
// This test errors if the reporter is not re-designated as 'spec' correctly
it('disables eth-gas-reporter', async function(){
truffleConfig.mocha = { reporter: 'eth-gas-reporter' };
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
});
it('disables optimization when truffle-config uses V4 format', async function(){
solcoverConfig = {
silent: process.env.SILENT ? true : false,
istanbulReporter: ['json-summary', 'text']
};
truffleConfig.solc = {
optimizer: { enabled: true, runs: 200 }
};
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
const expected = [
{
file: mock.pathToContract(truffleConfig, 'Simple.sol'),
pct: 100
}
];
verify.lineCoverage(expected);
});
// This test tightly coupled to the ganache version in production deps
// "test-files" project solcoverjs includes `client: require('ganache-cli')`
it('config: client', async function(){
truffleConfig.logger = mock.testLogger;
truffleConfig.version = true;
const configClientVersion = "v2.8.0";
// Config client
mock.installFullProject('ganache-solcoverjs');
await plugin(truffleConfig);
assert(
mock.loggerOutput.val.includes(configClientVersion),
`Should use solcover provided ganache: ${mock.loggerOutput.val}`
);
});
it('config: istanbulFolder', async function(){
solcoverConfig.istanbulFolder = mock.pathToTemp('specialFolder');
// Truffle client
mock.install('Simple', 'simple.js', solcoverConfig);
await plugin(truffleConfig);
assert(verify.pathExists(solcoverConfig.istanbulFolder));
});
// 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-migrations', solcoverConfig);
await plugin(truffleConfig);
const expected = [
{
file: mock.pathToContract(truffleConfig, 'ContractA.sol'),
pct: 0
},
{
file: mock.pathToContract(truffleConfig, 'ContractB.sol'),
pct: 0,
},
{
file: mock.pathToContract(truffleConfig, '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);
await plugin(truffleConfig);
});
it('config: skipped file', async function() {
solcoverConfig.skipFiles = ['Owned.sol'];
mock.installDouble(
['Proxy', 'Owned'],
'inheritance.js',
solcoverConfig
);
await plugin(truffleConfig);
verify.coverageGenerated(truffleConfig);
const output = mock.getOutput(truffleConfig);
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');
await plugin(truffleConfig);
const expected = [{
file: mock.pathToContract(truffleConfig, 'ContractA.sol'),
pct: 100
}];
const missing = [{
file: mock.pathToContract(truffleConfig, 'skipped-folder/ContractB.sol'),
}];
verify.lineCoverage(expected);
verify.coverageMissing(missing);
});
it('config: "onServerReady", "onTestsComplete", ...', async function() {
truffleConfig.logger = mock.testLogger;
mock.installFullProject('test-files');
await plugin(truffleConfig);
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}`
);
});
})