|
|
|
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('imports: relative path imports & file w/ only-import statements', async function() {
|
|
|
|
// Earlier versions of truffle crash on this case (5.0.31)
|
|
|
|
// Later versions of truffle OOM (5.1.2)
|
|
|
|
truffleConfig.useGlobalTruffle = true;
|
|
|
|
|
|
|
|
mock.installFullProject('import-paths');
|
|
|
|
await plugin(truffleConfig);
|
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
file: mock.pathToContract(truffleConfig, 'OnlyUsesImports.sol'),
|
|
|
|
pct: 100
|
|
|
|
},
|
|
|
|
{
|
|
|
|
file: mock.pathToContract(truffleConfig, 'UsesImports.sol'),
|
|
|
|
pct: 100,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
verify.lineCoverage(expected);
|
|
|
|
});
|
|
|
|
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('locates .coverage_contracts correctly when dir is subfolder', async function(){
|
|
|
|
solcoverConfig = {
|
|
|
|
silent: process.env.SILENT ? true : false,
|
|
|
|
istanbulReporter: ['json-summary', 'text']
|
|
|
|
};
|
|
|
|
|
|
|
|
truffleConfig.contracts_directory = path.join(
|
|
|
|
process.cwd(),
|
|
|
|
mock.pathToTemp("contracts/A")
|
|
|
|
);
|
|
|
|
|
|
|
|
mock.installFullProject('contract-subfolders');
|
|
|
|
await plugin(truffleConfig);
|
|
|
|
})
|
|
|
|
|
|
|
|
// 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.13.2";
|
|
|
|
|
|
|
|
// 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}`
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('config: includeStatementCoverage, includeFunctionCoverage', async function(){
|
|
|
|
solcoverConfig.istanbulReporter = ['json-summary', 'text']
|
|
|
|
solcoverConfig.measureStatementCoverage = false;
|
|
|
|
solcoverConfig.measureFunctionCoverage = false;
|
|
|
|
|
|
|
|
mock.install('Simple', 'simple.js', solcoverConfig);
|
|
|
|
await plugin(truffleConfig);
|
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
file: mock.pathToContract(truffleConfig, 'Simple.sol'),
|
|
|
|
total: 0
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
verify.statementCoverage(expected, 'total');
|
|
|
|
verify.functionCoverage(expected, 'total');
|
|
|
|
});
|
|
|
|
|
|
|
|
// Fails with Truffle 5.0.31, but newer Truffle causes OOM when running whole suite.
|
|
|
|
// Running the same test with Buidler though...
|
|
|
|
it.skip('solc 0.6.x', async function(){
|
|
|
|
mock.installFullProject('solc-6');
|
|
|
|
await plugin(truffleConfig);
|
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
file: mock.pathToContract(truffleConfig, 'ContractA.sol'),
|
|
|
|
pct: 100
|
|
|
|
},
|
|
|
|
{
|
|
|
|
file: mock.pathToContract(truffleConfig, 'ContractB.sol'),
|
|
|
|
pct: 0,
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
verify.lineCoverage(expected);
|
|
|
|
})
|
|
|
|
|
|
|
|
it('compiles when a project includes vyper contracts', async function() {
|
|
|
|
const skipMigration = true;
|
|
|
|
|
|
|
|
truffleConfig.logger = mock.testLogger;
|
|
|
|
solcoverConfig.istanbulReporter = ['json-summary', 'text']
|
|
|
|
|
|
|
|
mock.installDouble(
|
|
|
|
['Simple', 'auction.vy'],
|
|
|
|
'simple.js',
|
|
|
|
solcoverConfig,
|
|
|
|
skipMigration
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await plugin(truffleConfig);
|
|
|
|
|
|
|
|
assert(
|
|
|
|
mock.loggerOutput.val.includes('Compiling ./.coverage_contracts/auction.vy')
|
|
|
|
);
|
|
|
|
|
|
|
|
const expected = [
|
|
|
|
{
|
|
|
|
file: mock.pathToContract(truffleConfig, 'Simple.sol'),
|
|
|
|
pct: 100
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
verify.lineCoverage(expected);
|
|
|
|
});
|
|
|
|
})
|