From f6c01520ae75d173937f0847ac45e636b06f4146 Mon Sep 17 00:00:00 2001 From: Leonid Logvinov Date: Thu, 15 Mar 2018 15:42:48 +0100 Subject: Add tests for sol-cov --- .../sol-cov/test/collect_contracts_data_test.ts | 30 +++++++++ .../sol-cov/test/collect_coverage_entries_test.ts | 64 +++++++++++++++++++ .../test/fixtures/artifacts/SimpleStorage.json | 64 +++++++++++++++++++ .../sol-cov/test/fixtures/artifacts/Simplest.json | 20 ++++++ .../test/fixtures/contracts/SimpleStorage.sol | 9 +++ .../sol-cov/test/fixtures/contracts/Simplest.sol | 2 + packages/sol-cov/test/instructions_test.ts | 22 +++++++ packages/sol-cov/test/source_maps_test.ts | 71 ++++++++++++++++++++++ packages/sol-cov/test/utils_test.ts | 53 ++++++++++++++++ 9 files changed, 335 insertions(+) create mode 100644 packages/sol-cov/test/collect_contracts_data_test.ts create mode 100644 packages/sol-cov/test/collect_coverage_entries_test.ts create mode 100644 packages/sol-cov/test/fixtures/artifacts/SimpleStorage.json create mode 100644 packages/sol-cov/test/fixtures/artifacts/Simplest.json create mode 100644 packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol create mode 100644 packages/sol-cov/test/fixtures/contracts/Simplest.sol create mode 100644 packages/sol-cov/test/instructions_test.ts create mode 100644 packages/sol-cov/test/source_maps_test.ts create mode 100644 packages/sol-cov/test/utils_test.ts (limited to 'packages/sol-cov/test') diff --git a/packages/sol-cov/test/collect_contracts_data_test.ts b/packages/sol-cov/test/collect_contracts_data_test.ts new file mode 100644 index 000000000..e793085e3 --- /dev/null +++ b/packages/sol-cov/test/collect_contracts_data_test.ts @@ -0,0 +1,30 @@ +import * as chai from 'chai'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { collectContractsData } from '../src/collect_contract_data'; + +const expect = chai.expect; + +describe('Collect contracts data', () => { + describe('#collectContractsData', () => { + it('correctly collects contracts data', () => { + const artifactsPath = path.resolve(__dirname, 'fixtures/artifacts'); + const sourcesPath = path.resolve(__dirname, 'fixtures/contracts'); + const networkId = 50; + const contractsData = collectContractsData(artifactsPath, sourcesPath, networkId); + _.forEach(contractsData, contractData => { + expect(contractData).to.have.keys([ + 'baseName', + 'sourceCodes', + 'sources', + 'sourceMap', + 'sourceMapRuntime', + 'bytecode', + 'runtimeBytecode', + ]); + }); + }); + }); +}); diff --git a/packages/sol-cov/test/collect_coverage_entries_test.ts b/packages/sol-cov/test/collect_coverage_entries_test.ts new file mode 100644 index 000000000..84451686f --- /dev/null +++ b/packages/sol-cov/test/collect_coverage_entries_test.ts @@ -0,0 +1,64 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { collectCoverageEntries } from '../src/collect_coverage_entries'; +import { SingleFileSourceRange } from '../src/types'; + +const expect = chai.expect; + +const getRange = (sourceCode: string, range: SingleFileSourceRange) => { + const lines = sourceCode.split('\n').slice(range.start.line - 1, range.end.line); + lines[lines.length - 1] = lines[lines.length - 1].slice(0, range.end.column); + lines[0] = lines[0].slice(range.start.column); + return lines.join('\n'); +}; + +describe('Collect coverage entries', () => { + describe('#collectCoverageEntries', () => { + it('correctly collects coverage entries for Simplest contract', () => { + const simplestContractBaseName = 'Simplest.sol'; + const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); + const simplestContract = fs.readFileSync(simplestContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simplestContract); + expect(coverageEntries.fnMap).to.be.deep.equal({}); + expect(coverageEntries.branchMap).to.be.deep.equal({}); + expect(coverageEntries.statementMap).to.be.deep.equal({}); + expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); + }); + it('correctly collects coverage entries for SimpleStorage contract', () => { + const simpleStorageContractBaseName = 'SimpleStorage.sol'; + const simpleStorageContractFileName = path.resolve( + __dirname, + 'fixtures/contracts', + simpleStorageContractBaseName, + ); + const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString(); + const coverageEntries = collectCoverageEntries(simpleStorageContract); + const fnIds = _.keys(coverageEntries.fnMap); + expect(coverageEntries.fnMap[fnIds[0]].name).to.be.equal('set'); + expect(coverageEntries.fnMap[fnIds[0]].line).to.be.equal(3); + const setFunction = `function set(uint x) { + storedData = x; + }`; + expect(getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[0]].loc)).to.be.equal(setFunction); + expect(coverageEntries.fnMap[fnIds[1]].name).to.be.equal('get'); + expect(coverageEntries.fnMap[fnIds[1]].line).to.be.equal(6); + const getFunction = `function get() constant returns (uint retVal) { + return storedData; + }`; + expect(getRange(simpleStorageContract, coverageEntries.fnMap[fnIds[1]].loc)).to.be.equal(getFunction); + expect(coverageEntries.branchMap).to.be.deep.equal({}); + const statementIds = _.keys(coverageEntries.statementMap); + expect(getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[1]])).to.be.equal( + 'storedData = x', + ); + expect(getRange(simpleStorageContract, coverageEntries.statementMap[statementIds[3]])).to.be.equal( + 'return storedData;', + ); + expect(coverageEntries.modifiersStatementIds).to.be.deep.equal([]); + }); + }); +}); diff --git a/packages/sol-cov/test/fixtures/artifacts/SimpleStorage.json b/packages/sol-cov/test/fixtures/artifacts/SimpleStorage.json new file mode 100644 index 000000000..416170ef2 --- /dev/null +++ b/packages/sol-cov/test/fixtures/artifacts/SimpleStorage.json @@ -0,0 +1,64 @@ +{ + "contract_name": "SimpleStorage", + "networks": { + "50": { + "solc_version": "0.4.21", + "keccak256": "0x18dc5b5a0e813c17e49936d2f2f7c07c51f050c09ba5e7206f17c855f23f4b6a", + "source_tree_hash": "0x18dc5b5a0e813c17e49936d2f2f7c07c51f050c09ba5e7206f17c855f23f4b6a", + "optimizer_enabled": 0, + "abi": [ + { + "constant": true, + "inputs": [], + "name": "storedData", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "x", + "type": "uint256" + } + ], + "name": "set", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "get", + "outputs": [ + { + "name": "retVal", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": + "0x6060604052341561000f57600080fd5b6101098061001e6000396000f3006060604052600436106053576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605857806360fe47b114607e5780636d4ce63c14609e575b600080fd5b3415606257600080fd5b606860c4565b6040518082815260200191505060405180910390f35b3415608857600080fd5b609c600480803590602001909190505060ca565b005b341560a857600080fd5b60ae60d4565b6040518082815260200191505060405180910390f35b60005481565b8060008190555050565b600080549050905600a165627a7a723058207f743855fd0c71699620424a21a257cd197ed05032d6768eb9b874e4898f44c60029", + "runtime_bytecode": + "0x6060604052600436106053576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632a1afcd914605857806360fe47b114607e5780636d4ce63c14609e575b600080fd5b3415606257600080fd5b606860c4565b6040518082815260200191505060405180910390f35b3415608857600080fd5b609c600480803590602001909190505060ca565b005b341560a857600080fd5b60ae60d4565b6040518082815260200191505060405180910390f35b60005481565b8060008190555050565b600080549050905600a165627a7a723058207f743855fd0c71699620424a21a257cd197ed05032d6768eb9b874e4898f44c60029", + "updated_at": 1521118350895, + "source_map": "26:196:0:-;;;;;;;;;;;;;;;;;", + "source_map_runtime": + "26:196:0:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;55:22;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;83:52;;;;;;;;;;;;;;;;;;;;;;;;;;140:80;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;55:22;;;;:::o;83:52::-;127:1;114:10;:14;;;;83:52;:::o;140:80::-;173:11;203:10;;196:17;;140:80;:::o", + "sources": ["SimpleStorage.sol"] + } + } +} diff --git a/packages/sol-cov/test/fixtures/artifacts/Simplest.json b/packages/sol-cov/test/fixtures/artifacts/Simplest.json new file mode 100644 index 000000000..8de60e481 --- /dev/null +++ b/packages/sol-cov/test/fixtures/artifacts/Simplest.json @@ -0,0 +1,20 @@ +{ + "contract_name": "Simplest", + "networks": { + "50": { + "solc_version": "0.4.21", + "keccak256": "0x8e7d62e19c7c7b8bf9a4a43749e111605950cc877574fb9640a1a07d1c3749f9", + "source_tree_hash": "0x8e7d62e19c7c7b8bf9a4a43749e111605950cc877574fb9640a1a07d1c3749f9", + "optimizer_enabled": 0, + "abi": [], + "bytecode": + "0x60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a7230582097cfe05b4d18d6ffb3a8d8fab0570cf09640d3131b9677ddb9be4e9fbcb65f010029", + "runtime_bytecode": + "0x6060604052600080fd00a165627a7a7230582097cfe05b4d18d6ffb3a8d8fab0570cf09640d3131b9677ddb9be4e9fbcb65f010029", + "updated_at": 1521118525393, + "source_map": "26:21:0:-;;;;;;;;;;;;;;;;;", + "source_map_runtime": "26:21:0:-;;;;;", + "sources": ["Simplest.sol"] + } + } +} diff --git a/packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol b/packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol new file mode 100644 index 000000000..178a52318 --- /dev/null +++ b/packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol @@ -0,0 +1,9 @@ +contract SimpleStorage { + uint public storedData; + function set(uint x) { + storedData = x; + } + function get() constant returns (uint retVal) { + return storedData; + } +} diff --git a/packages/sol-cov/test/fixtures/contracts/Simplest.sol b/packages/sol-cov/test/fixtures/contracts/Simplest.sol new file mode 100644 index 000000000..d71016e07 --- /dev/null +++ b/packages/sol-cov/test/fixtures/contracts/Simplest.sol @@ -0,0 +1,2 @@ +contract Simplest { +} diff --git a/packages/sol-cov/test/instructions_test.ts b/packages/sol-cov/test/instructions_test.ts new file mode 100644 index 000000000..f64ec11ed --- /dev/null +++ b/packages/sol-cov/test/instructions_test.ts @@ -0,0 +1,22 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import 'mocha'; +import * as path from 'path'; + +import { getPcToInstructionIndexMapping } from '../src/instructions'; + +const expect = chai.expect; + +describe('instructions', () => { + describe('#getPcToInstructionIndexMapping', () => { + it('correctly maps pcs to instruction indexed', () => { + const PUSH1 = 0x60; + const PUSH2 = 0x61; + const TIMESTAMP = 0x42; + const bytecode = new Uint8Array([PUSH1, 42, PUSH2, 1, 2, TIMESTAMP]); + const pcToInstruction = getPcToInstructionIndexMapping(bytecode); + const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 }; + expect(pcToInstruction).to.be.deep.equal(expectedPcToInstruction); + }); + }); +}); diff --git a/packages/sol-cov/test/source_maps_test.ts b/packages/sol-cov/test/source_maps_test.ts new file mode 100644 index 000000000..5820bedd7 --- /dev/null +++ b/packages/sol-cov/test/source_maps_test.ts @@ -0,0 +1,71 @@ +import * as chai from 'chai'; +import * as fs from 'fs'; +import * as _ from 'lodash'; +import 'mocha'; +import * as path from 'path'; + +import { getLocationByOffset, parseSourceMap } from '../src/source_maps'; + +const expect = chai.expect; + +const simplestContractBaseName = 'Simplest.sol'; +const simplestContractFileName = path.resolve(__dirname, 'fixtures/contracts', simplestContractBaseName); +const simplestContract = fs.readFileSync(simplestContractFileName).toString(); + +describe('source maps', () => { + describe('#getLocationByOffset', () => { + it('correctly computes location by offset', () => { + const locationByOffset = getLocationByOffset(simplestContract); + const expectedLocationByOffset = { + '0': { line: 1, column: 0 }, + '1': { line: 1, column: 1 }, + '2': { line: 1, column: 2 }, + '3': { line: 1, column: 3 }, + '4': { line: 1, column: 4 }, + '5': { line: 1, column: 5 }, + '6': { line: 1, column: 6 }, + '7': { line: 1, column: 7 }, + '8': { line: 1, column: 8 }, + '9': { line: 1, column: 9 }, + '10': { line: 1, column: 10 }, + '11': { line: 1, column: 11 }, + '12': { line: 1, column: 12 }, + '13': { line: 1, column: 13 }, + '14': { line: 1, column: 14 }, + '15': { line: 1, column: 15 }, + '16': { line: 1, column: 16 }, + '17': { line: 1, column: 17 }, + '18': { line: 1, column: 18 }, + '19': { line: 1, column: 19 }, + '20': { line: 2, column: 0 }, + '21': { line: 2, column: 1 }, + '22': { line: 3, column: 0 }, + }; + expect(locationByOffset).to.be.deep.equal(expectedLocationByOffset); + }); + }); + describe('#parseSourceMap', () => { + it('correctly parses the source map', () => { + // This is the source map and bytecode for an empty contract like Example.sol + const srcMap = '0:21:0:-;;;;;;;;;;;;;;;;;'; + const bytecodeHex = + '60606040523415600e57600080fd5b603580601b6000396000f3006060604052600080fd00a165627a7a72305820377cdef690e46589f40efeef14d8ef73504af059fb3fd46f1da3cd2fc52ef7890029'; + const sources = [simplestContractBaseName]; + const pcToSourceRange = parseSourceMap([simplestContract], srcMap, bytecodeHex, sources); + const expectedSourceRange = { + location: { + start: { line: 1, column: 0 }, + end: { line: 2, column: 1 }, + }, + fileName: simplestContractBaseName, + }; + _.forEach(pcToSourceRange, sourceRange => { + // Solidity source maps are too short and we map some instructions to undefined + // Source: https://github.com/ethereum/solidity/issues/3741 + if (!_.isUndefined(sourceRange)) { + expect(sourceRange).to.be.deep.equal(expectedSourceRange); + } + }); + }); + }); +}); diff --git a/packages/sol-cov/test/utils_test.ts b/packages/sol-cov/test/utils_test.ts new file mode 100644 index 000000000..6fc8fcfe1 --- /dev/null +++ b/packages/sol-cov/test/utils_test.ts @@ -0,0 +1,53 @@ +import * as chai from 'chai'; +import * as dirtyChai from 'dirty-chai'; +import 'mocha'; + +import { utils } from '../src/utils'; + +chai.use(dirtyChai); +const expect = chai.expect; + +describe('utils', () => { + describe('#compareLineColumn', () => { + it('correctly compares LineColumns', () => { + expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 4 })).to.be.lessThan(0); + expect(utils.compareLineColumn({ line: 1, column: 4 }, { line: 1, column: 3 })).to.be.greaterThan(0); + expect(utils.compareLineColumn({ line: 1, column: 3 }, { line: 1, column: 3 })).to.be.equal(0); + expect(utils.compareLineColumn({ line: 0, column: 2 }, { line: 1, column: 0 })).to.be.lessThan(0); + expect(utils.compareLineColumn({ line: 1, column: 0 }, { line: 0, column: 2 })).to.be.greaterThan(0); + }); + }); + + describe('#isRangeInside', () => { + it('returns true if inside', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 2 }, end: { line: 1, column: 5 } }, + ), + ).to.be.true(); + }); + it('returns true if the same', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + ), + ).to.be.true(); + }); + it('returns false if not inside', () => { + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 4 }, end: { line: 1, column: 4 } }, + ), + ).to.be.false(); + expect( + utils.isRangeInside( + { start: { line: 1, column: 3 }, end: { line: 1, column: 4 } }, + { start: { line: 1, column: 4 }, end: { line: 1, column: 5 } }, + ), + ).to.be.false(); + }); + }); +}); -- cgit v1.2.3