Merge pull request #457 from 0xProject/feature/sol-cov-tests
Add tests for sol-cov
This commit is contained in:
@@ -111,6 +111,10 @@ jobs:
|
||||
key: coverage-subproviders-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- ~/repo/packages/subproviders/coverage/lcov.info
|
||||
- save_cache:
|
||||
key: coverage-sol-cov-{{ .Environment.CIRCLE_SHA1 }}
|
||||
paths:
|
||||
- ~/repo/packages/sol-cov/coverage/lcov.info
|
||||
lint:
|
||||
working_directory: ~/repo
|
||||
docker:
|
||||
@@ -155,6 +159,9 @@ jobs:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- coverage-subproviders-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- coverage-sol-cov-{{ .Environment.CIRCLE_SHA1 }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- coverage-deployer-{{ .Environment.CIRCLE_SHA1 }}
|
||||
|
||||
0
packages/sol-cov/coverage/.gitkeep
Normal file
0
packages/sol-cov/coverage/.gitkeep
Normal file
@@ -2,13 +2,18 @@
|
||||
"name": "@0xproject/sol-cov",
|
||||
"version": "0.0.1",
|
||||
"description": "Generate coverage reports for Solidity code",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"build:watch": "tsc -w",
|
||||
"lint": "tslint --project . 'src/**/*.ts'",
|
||||
"test": "run-s clean build run_mocha",
|
||||
"test:coverage": "nyc npm run test --all && yarn coverage:report:lcov",
|
||||
"coverage:report:lcov": "nyc report --reporter=text-lcov > coverage/lcov.info",
|
||||
"test:circleci": "yarn test:coverage",
|
||||
"run_mocha": "mocha lib/test/**/*_test.js",
|
||||
"clean": "shx rm -rf lib scripts",
|
||||
"build": "tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts"
|
||||
"build": "copyfiles 'test/fixtures/**/*' ./lib && tsc && copyfiles -u 2 './lib/monorepo_scripts/**/*' ./scripts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -37,7 +42,14 @@
|
||||
"@0xproject/tslint-config": "^0.4.9",
|
||||
"@types/istanbul": "^0.4.29",
|
||||
"@types/node": "^8.0.53",
|
||||
"@types/mocha": "^2.2.42",
|
||||
"sinon": "^4.0.0",
|
||||
"copyfiles": "^1.2.0",
|
||||
"nyc": "^11.0.1",
|
||||
"chai": "^4.0.1",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"chai-typescript-typings": "^0.0.4",
|
||||
"mocha": "^4.0.1",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"shx": "^0.2.2",
|
||||
"tslint": "5.8.0",
|
||||
|
||||
@@ -113,8 +113,8 @@ export class ASTVisitor {
|
||||
this._statementMap[this._entryId++] = this._getExpressionRange(ast);
|
||||
}
|
||||
private _getExpressionRange(ast: Parser.ASTNode): SingleFileSourceRange {
|
||||
const start = this._locationByOffset[ast.range[0] - 1];
|
||||
const end = this._locationByOffset[ast.range[1]];
|
||||
const start = this._locationByOffset[ast.range[0]];
|
||||
const end = this._locationByOffset[ast.range[1] + 1];
|
||||
const range = {
|
||||
start,
|
||||
end,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getLocationByOffset } from './source_maps';
|
||||
// Parsing source code for each transaction/code is slow and therefore we cache it
|
||||
const coverageEntriesBySourceHash: { [sourceHash: string]: CoverageEntriesDescription } = {};
|
||||
|
||||
export const collectCoverageEntries = (contractSource: string, fileName: string) => {
|
||||
export const collectCoverageEntries = (contractSource: string) => {
|
||||
const sourceHash = ethUtil.sha3(contractSource).toString('hex');
|
||||
if (_.isUndefined(coverageEntriesBySourceHash[sourceHash])) {
|
||||
const ast = parser.parse(contractSource, { range: true });
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
// tslint:disable:number-literal-format
|
||||
export const constants = {
|
||||
NEW_CONTRACT: 'NEW_CONTRACT',
|
||||
PUSH1: 0x60,
|
||||
PUSH2: 0x61,
|
||||
PUSH32: 0x7f,
|
||||
TIMESTAMP: 0x42,
|
||||
};
|
||||
|
||||
@@ -39,7 +39,7 @@ export class CoverageManager {
|
||||
fileIndex: number,
|
||||
): Coverage {
|
||||
const fileName = contractData.sources[fileIndex];
|
||||
const coverageEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex], fileName);
|
||||
const coverageEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]);
|
||||
let sourceRanges = _.map(coveredPcs, coveredPc => pcToSourceRange[coveredPc]);
|
||||
sourceRanges = _.compact(sourceRanges); // Some PC's don't map to a source range and we just ignore them.
|
||||
// By default lodash does a shallow object comparasion. We JSON.stringify them and compare as strings.
|
||||
|
||||
3
packages/sol-cov/src/globals.d.ts
vendored
3
packages/sol-cov/src/globals.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
// tslint:disable:completed-docs
|
||||
declare module 'dirty-chai';
|
||||
|
||||
// tslint:disable:completed-docs
|
||||
declare module '*.json' {
|
||||
const json: any;
|
||||
/* tslint:disable */
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// tslint:disable:number-literal-format
|
||||
const PUSH1 = 0x60;
|
||||
const PUSH32 = 0x7f;
|
||||
const isPush = (inst: number) => inst >= PUSH1 && inst <= PUSH32;
|
||||
import { constants } from './constants';
|
||||
|
||||
const pushDataLength = (inst: number) => inst - PUSH1 + 1;
|
||||
const isPush = (inst: number) => inst >= constants.PUSH1 && inst <= constants.PUSH32;
|
||||
|
||||
const pushDataLength = (inst: number) => inst - constants.PUSH1 + 1;
|
||||
|
||||
const instructionLength = (inst: number) => (isPush(inst) ? pushDataLength(inst) + 1 : 1);
|
||||
|
||||
|
||||
@@ -12,12 +12,12 @@ export interface SourceLocation {
|
||||
}
|
||||
|
||||
export function getLocationByOffset(str: string): LocationByOffset {
|
||||
const locationByOffset: LocationByOffset = {};
|
||||
const locationByOffset: LocationByOffset = { 0: { line: 1, column: 0 } };
|
||||
let currentOffset = 0;
|
||||
for (const char of str.split('')) {
|
||||
const location = locationByOffset[currentOffset - 1] || { line: 1, column: 0 };
|
||||
const location = locationByOffset[currentOffset];
|
||||
const isNewline = char === '\n';
|
||||
locationByOffset[currentOffset] = {
|
||||
locationByOffset[currentOffset + 1] = {
|
||||
line: location.line + (isNewline ? 1 : 0),
|
||||
column: isNewline ? 0 : location.column + 1,
|
||||
};
|
||||
@@ -59,9 +59,8 @@ export function parseSourceMap(
|
||||
if (parsedEntry.fileIndex !== -1) {
|
||||
const sourceRange = {
|
||||
location: {
|
||||
start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset - 1],
|
||||
end:
|
||||
locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length - 1],
|
||||
start: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset],
|
||||
end: locationByOffsetByFileIndex[parsedEntry.fileIndex][parsedEntry.offset + parsedEntry.length],
|
||||
},
|
||||
fileName: sources[parsedEntry.fileIndex],
|
||||
};
|
||||
|
||||
30
packages/sol-cov/test/collect_contracts_data_test.ts
Normal file
30
packages/sol-cov/test/collect_contracts_data_test.ts
Normal file
@@ -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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
129
packages/sol-cov/test/collect_coverage_entries_test.ts
Normal file
129
packages/sol-cov/test/collect_coverage_entries_test.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
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([]);
|
||||
});
|
||||
it('correctly collects coverage entries for AllSolidityFeatures contract', () => {
|
||||
const simpleStorageContractBaseName = 'AllSolidityFeatures.sol';
|
||||
const simpleStorageContractFileName = path.resolve(
|
||||
__dirname,
|
||||
'fixtures/contracts',
|
||||
simpleStorageContractBaseName,
|
||||
);
|
||||
const simpleStorageContract = fs.readFileSync(simpleStorageContractFileName).toString();
|
||||
const coverageEntries = collectCoverageEntries(simpleStorageContract);
|
||||
const fnDescriptions = _.values(coverageEntries.fnMap);
|
||||
const fnNames = _.map(fnDescriptions, fnDescription => fnDescription.name);
|
||||
const expectedFnNames = [
|
||||
'f',
|
||||
'c',
|
||||
'test',
|
||||
'getChoice',
|
||||
'Base',
|
||||
'Derived',
|
||||
'f',
|
||||
'f',
|
||||
'',
|
||||
'g',
|
||||
'setData',
|
||||
'getData',
|
||||
'sendHalf',
|
||||
'insert',
|
||||
'remove',
|
||||
'contains',
|
||||
'iterate_start',
|
||||
'iterate_valid',
|
||||
'iterate_advance',
|
||||
'iterate_get',
|
||||
'insert',
|
||||
'sum',
|
||||
'restricted',
|
||||
'DualIndex',
|
||||
'set',
|
||||
'transfer_ownership',
|
||||
'lookup',
|
||||
'',
|
||||
'',
|
||||
'sum',
|
||||
'someFunction',
|
||||
'fun',
|
||||
'at',
|
||||
'test',
|
||||
'get',
|
||||
'returnNumber',
|
||||
'alloc',
|
||||
'ham',
|
||||
'getMyTuple',
|
||||
'ham',
|
||||
'abstain',
|
||||
'foobar',
|
||||
'foobar',
|
||||
'a',
|
||||
];
|
||||
expect(fnNames).to.be.deep.equal(expectedFnNames);
|
||||
|
||||
const branchDescriptions = _.values(coverageEntries.branchMap);
|
||||
const branchLines = _.map(branchDescriptions, branchDescription => branchDescription.line);
|
||||
expect(branchLines).to.be.deep.equal([94, 115, 119, 130, 151, 187]);
|
||||
const branchTypes = _.map(branchDescriptions, branchDescription => branchDescription.type);
|
||||
expect(branchTypes).to.be.deep.equal(['if', 'if', 'if', 'if', 'binary-expr', 'if']);
|
||||
});
|
||||
});
|
||||
});
|
||||
64
packages/sol-cov/test/fixtures/artifacts/SimpleStorage.json
vendored
Normal file
64
packages/sol-cov/test/fixtures/artifacts/SimpleStorage.json
vendored
Normal file
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
20
packages/sol-cov/test/fixtures/artifacts/Simplest.json
vendored
Normal file
20
packages/sol-cov/test/fixtures/artifacts/Simplest.json
vendored
Normal file
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
413
packages/sol-cov/test/fixtures/contracts/AllSolidityFeatures.sol
vendored
Normal file
413
packages/sol-cov/test/fixtures/contracts/AllSolidityFeatures.sol
vendored
Normal file
@@ -0,0 +1,413 @@
|
||||
// Examples taken from the Solidity documentation online.
|
||||
|
||||
// for pragma version numbers, see https://docs.npmjs.com/misc/semver#versions
|
||||
pragma solidity 0.4.0;
|
||||
pragma solidity ^0.4.0;
|
||||
|
||||
import "SomeFile.sol";
|
||||
import "SomeFile.sol" as SomeOtherFile;
|
||||
import * as SomeSymbol from "AnotherFile.sol";
|
||||
import {symbol1 as alias, symbol2} from "File.sol";
|
||||
|
||||
interface i {
|
||||
function f();
|
||||
}
|
||||
|
||||
contract c {
|
||||
function c()
|
||||
{
|
||||
val1 = 1 wei; // 1
|
||||
val2 = 1 szabo; // 1 * 10 ** 12
|
||||
val3 = 1 finney; // 1 * 10 ** 15
|
||||
val4 = 1 ether; // 1 * 10 ** 18
|
||||
}
|
||||
uint256 val1;
|
||||
uint256 val2;
|
||||
uint256 val3;
|
||||
uint256 val4;
|
||||
}
|
||||
|
||||
contract test {
|
||||
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
|
||||
|
||||
function test()
|
||||
{
|
||||
choices = ActionChoices.GoStraight;
|
||||
}
|
||||
function getChoice() returns (uint d)
|
||||
{
|
||||
d = uint256(choices);
|
||||
}
|
||||
ActionChoices choices;
|
||||
}
|
||||
|
||||
contract Base {
|
||||
function Base(uint i)
|
||||
{
|
||||
m_i = i;
|
||||
}
|
||||
uint public m_i;
|
||||
}
|
||||
contract Derived is Base(0) {
|
||||
function Derived(uint i) Base(i) {}
|
||||
}
|
||||
|
||||
contract C {
|
||||
uint248 x; // 31 bytes: slot 0, offset 0
|
||||
uint16 y; // 2 bytes: slot 1, offset 0 (does not fit in slot 0)
|
||||
uint240 z; // 30 bytes: slot 1, offset 2 bytes
|
||||
uint8 a; // 1 byte: slot 2, offset 0 bytes
|
||||
struct S {
|
||||
uint8 a; // 1 byte, slot +0, offset 0 bytes
|
||||
uint256 b; // 32 bytes, slot +1, offset 0 bytes (does not fit)
|
||||
}
|
||||
S structData; // 2 slots, slot 3, offset 0 bytes (does not really apply)
|
||||
uint8 alpha; // 1 byte, slot 4 (start new slot after struct)
|
||||
uint16[3] beta; // 3*16 bytes, slots 5+6 (start new slot for array)
|
||||
uint8 gamma; // 1 byte, slot 7 (start new slot after array)
|
||||
}
|
||||
|
||||
contract test {
|
||||
function f(uint x, uint y) returns (uint z) {
|
||||
var c = x + 3;
|
||||
var b = 7 + (c * (8 - 7)) - x;
|
||||
return -(-b | 0);
|
||||
}
|
||||
}
|
||||
|
||||
contract test {
|
||||
function f(uint x, uint y) returns (uint z) {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
contract c {
|
||||
function () returns (uint) { return g(8); }
|
||||
function g(uint pos) internal returns (uint) { setData(pos, 8); return getData(pos); }
|
||||
function setData(uint pos, uint value) internal { data[pos] = value; }
|
||||
function getData(uint pos) internal { return data[pos]; }
|
||||
mapping(uint => uint) data;
|
||||
}
|
||||
|
||||
contract Sharer {
|
||||
function sendHalf(address addr) returns (uint balance) {
|
||||
if (!addr.send(msg.value/2))
|
||||
throw; // also reverts the transfer to Sharer
|
||||
return address(this).balance;
|
||||
}
|
||||
}
|
||||
|
||||
/// @dev Models a modifiable and iterable set of uint values.
|
||||
library IntegerSet
|
||||
{
|
||||
struct data
|
||||
{
|
||||
/// Mapping item => index (or zero if not present)
|
||||
mapping(uint => uint) index;
|
||||
/// Items by index (index 0 is invalid), items with index[item] == 0 are invalid.
|
||||
uint[] items;
|
||||
/// Number of stored items.
|
||||
uint size;
|
||||
}
|
||||
function insert(data storage self, uint value) returns (bool alreadyPresent)
|
||||
{
|
||||
uint index = self.index[value];
|
||||
if (index > 0)
|
||||
return true;
|
||||
else
|
||||
{
|
||||
if (self.items.length == 0) self.items.length = 1;
|
||||
index = self.items.length++;
|
||||
self.items[index] = value;
|
||||
self.index[value] = index;
|
||||
self.size++;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function remove(data storage self, uint value) returns (bool success)
|
||||
{
|
||||
uint index = self.index[value];
|
||||
if (index == 0)
|
||||
return false;
|
||||
delete self.index[value];
|
||||
delete self.items[index];
|
||||
self.size --;
|
||||
}
|
||||
function contains(data storage self, uint value) returns (bool)
|
||||
{
|
||||
return self.index[value] > 0;
|
||||
}
|
||||
function iterate_start(data storage self) returns (uint index)
|
||||
{
|
||||
return iterate_advance(self, 0);
|
||||
}
|
||||
function iterate_valid(data storage self, uint index) returns (bool)
|
||||
{
|
||||
return index < self.items.length;
|
||||
}
|
||||
function iterate_advance(data storage self, uint index) returns (uint r_index)
|
||||
{
|
||||
index++;
|
||||
while (iterate_valid(self, index) && self.index[self.items[index]] == index)
|
||||
index++;
|
||||
return index;
|
||||
}
|
||||
function iterate_get(data storage self, uint index) returns (uint value)
|
||||
{
|
||||
return self.items[index];
|
||||
}
|
||||
}
|
||||
|
||||
/// How to use it:
|
||||
contract User
|
||||
{
|
||||
/// Just a struct holding our data.
|
||||
IntegerSet.data data;
|
||||
/// Insert something
|
||||
function insert(uint v) returns (uint size)
|
||||
{
|
||||
/// Sends `data` via reference, so IntegerSet can modify it.
|
||||
IntegerSet.insert(data, v);
|
||||
/// We can access members of the struct - but we should take care not to mess with them.
|
||||
return data.size;
|
||||
}
|
||||
/// Computes the sum of all stored data.
|
||||
function sum() returns (uint s)
|
||||
{
|
||||
for (var i = IntegerSet.iterate_start(data); IntegerSet.iterate_valid(data, i); i = IntegerSet.iterate_advance(data, i))
|
||||
s += IntegerSet.iterate_get(data, i);
|
||||
}
|
||||
}
|
||||
|
||||
// This broke it at one point (namely the modifiers).
|
||||
contract DualIndex {
|
||||
mapping(uint => mapping(uint => uint)) data;
|
||||
address public admin;
|
||||
|
||||
modifier restricted { if (msg.sender == admin) _; }
|
||||
|
||||
function DualIndex() {
|
||||
admin = msg.sender;
|
||||
}
|
||||
|
||||
function set(uint key1, uint key2, uint value) restricted {
|
||||
uint[2][4] memory defaults; // "memory" broke things at one time.
|
||||
data[key1][key2] = value;
|
||||
}
|
||||
|
||||
function transfer_ownership(address _admin) restricted {
|
||||
admin = _admin;
|
||||
}
|
||||
|
||||
function lookup(uint key1, uint key2) returns(uint) {
|
||||
return data[key1][key2];
|
||||
}
|
||||
}
|
||||
|
||||
contract A {
|
||||
|
||||
}
|
||||
|
||||
contract B {
|
||||
|
||||
}
|
||||
|
||||
contract C is A, B {
|
||||
|
||||
}
|
||||
|
||||
contract TestPrivate
|
||||
{
|
||||
uint private value;
|
||||
}
|
||||
|
||||
contract TestInternal
|
||||
{
|
||||
uint internal value;
|
||||
}
|
||||
|
||||
contract FromSolparse is A, B, TestPrivate, TestInternal {
|
||||
function() {
|
||||
uint a = 6 ** 9;
|
||||
var (x) = 100;
|
||||
uint y = 2 days;
|
||||
}
|
||||
}
|
||||
|
||||
contract CommentedOutFunction {
|
||||
// FYI: This empty function, as well as the commented
|
||||
// out function below (bad code) is important to this test.
|
||||
function() {
|
||||
|
||||
}
|
||||
|
||||
// function something()
|
||||
// uint x = 10;
|
||||
// }
|
||||
}
|
||||
|
||||
library VarHasBrackets {
|
||||
string constant specialRight = "}";
|
||||
//string storage specialLeft = "{";
|
||||
}
|
||||
|
||||
library UsingExampleLibrary {
|
||||
function sum(uint[] storage self) returns (uint s) {
|
||||
for (uint i = 0; i < self.length; i++)
|
||||
s += self[i];
|
||||
}
|
||||
}
|
||||
|
||||
contract UsingExampleContract {
|
||||
using UsingExampleLibrary for uint[];
|
||||
}
|
||||
|
||||
contract NewStuff {
|
||||
uint[] b;
|
||||
|
||||
function someFunction() payable {
|
||||
string storage a = hex"ab1248fe";
|
||||
b[2+2];
|
||||
}
|
||||
}
|
||||
|
||||
// modifier with expression
|
||||
contract MyContract {
|
||||
function fun() mymodifier(foo.bar()) {}
|
||||
}
|
||||
|
||||
library GetCode {
|
||||
function at(address _addr) returns (bytes o_code) {
|
||||
assembly {
|
||||
// retrieve the size of the code, this needs assembly
|
||||
let size := extcodesize(_addr)
|
||||
// allocate output byte array - this could also be done without assembly
|
||||
// by using o_code = new bytes(size)
|
||||
o_code := mload(0x40)
|
||||
// new "memory end" including padding
|
||||
mstore(0x40, add(o_code, and(add(add(size, 0x20), 0x1f), not(0x1f))))
|
||||
// store length in memory
|
||||
mstore(o_code, size)
|
||||
// actually retrieve the code, this needs assembly
|
||||
extcodecopy(_addr, add(o_code, 0x20), 0, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract assemblyLocalBinding {
|
||||
function test(){
|
||||
assembly {
|
||||
let v := 1
|
||||
let x := 0x00
|
||||
let y := x
|
||||
let z := "hello"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract assemblyReturn {
|
||||
uint a = 10;
|
||||
|
||||
function get() constant returns(uint) {
|
||||
assembly {
|
||||
mstore(0x40, sload(0))
|
||||
byte(0)
|
||||
address(0)
|
||||
return(0x40,32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract usesConst {
|
||||
uint const = 0;
|
||||
}
|
||||
|
||||
contract memoryArrays {
|
||||
uint seven = 7;
|
||||
|
||||
function returnNumber(uint number) returns (uint){
|
||||
return number;
|
||||
}
|
||||
|
||||
function alloc() {
|
||||
uint[] memory a = new uint[](7);
|
||||
uint[] memory b = new uint[](returnNumber(seven));
|
||||
}
|
||||
}
|
||||
|
||||
contract DeclarativeExpressions {
|
||||
uint a;
|
||||
uint b = 7;
|
||||
uint b2=0;
|
||||
uint public c;
|
||||
uint constant public d;
|
||||
uint public constant e;
|
||||
uint private constant f = 7;
|
||||
struct S { uint q;}
|
||||
|
||||
function ham(S storage s1, uint[] storage arr) internal {
|
||||
uint x;
|
||||
uint y = 7;
|
||||
S storage s2 = s1;
|
||||
uint[] memory stor;
|
||||
uint[] storage stor2 = arr;
|
||||
}
|
||||
}
|
||||
|
||||
contract VariableDeclarationTuple {
|
||||
function getMyTuple() returns (bool, bool){
|
||||
return (true, false);
|
||||
}
|
||||
|
||||
function ham (){
|
||||
var (x, y) = (10, 20);
|
||||
var (a, b) = getMyTuple();
|
||||
var (,c) = (10, 20);
|
||||
var (d,,) = (10, 20, 30);
|
||||
var (,e,,f,) = (10, 20, 30, 40, 50);
|
||||
|
||||
var (
|
||||
num1, num2,
|
||||
num3, ,num5
|
||||
) = (10, 20, 30, 40, 50);
|
||||
}
|
||||
}
|
||||
|
||||
contract TypeIndexSpacing {
|
||||
uint [ 7 ] x;
|
||||
uint [] y;
|
||||
}
|
||||
|
||||
contract Ballot {
|
||||
|
||||
struct Voter {
|
||||
uint weight;
|
||||
bool voted;
|
||||
}
|
||||
|
||||
function abstain() returns (bool) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names) {}
|
||||
function foobar() payable owner (myPrice) returns (uint[], address myAdd, string[] names);
|
||||
|
||||
Voter you = Voter(1, true);
|
||||
|
||||
Voter me = Voter({
|
||||
weight: 2,
|
||||
voted: abstain()
|
||||
});
|
||||
|
||||
Voter airbnb = Voter({
|
||||
weight: 2,
|
||||
voted: true,
|
||||
});
|
||||
}
|
||||
|
||||
contract multilineReturn {
|
||||
function a() returns (uint x) {
|
||||
return
|
||||
5;
|
||||
}
|
||||
}
|
||||
9
packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol
vendored
Normal file
9
packages/sol-cov/test/fixtures/contracts/SimpleStorage.sol
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
contract SimpleStorage {
|
||||
uint public storedData;
|
||||
function set(uint x) {
|
||||
storedData = x;
|
||||
}
|
||||
function get() constant returns (uint retVal) {
|
||||
return storedData;
|
||||
}
|
||||
}
|
||||
2
packages/sol-cov/test/fixtures/contracts/Simplest.sol
vendored
Normal file
2
packages/sol-cov/test/fixtures/contracts/Simplest.sol
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
contract Simplest {
|
||||
}
|
||||
20
packages/sol-cov/test/instructions_test.ts
Normal file
20
packages/sol-cov/test/instructions_test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import * as chai from 'chai';
|
||||
import * as fs from 'fs';
|
||||
import 'mocha';
|
||||
import * as path from 'path';
|
||||
|
||||
import { constants } from '../src/constants';
|
||||
import { getPcToInstructionIndexMapping } from '../src/instructions';
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('instructions', () => {
|
||||
describe('#getPcToInstructionIndexMapping', () => {
|
||||
it('correctly maps pcs to instruction indexed', () => {
|
||||
const bytecode = new Uint8Array([constants.PUSH1, 42, constants.PUSH2, 1, 2, constants.TIMESTAMP]);
|
||||
const pcToInstruction = getPcToInstructionIndexMapping(bytecode);
|
||||
const expectedPcToInstruction = { '0': 0, '2': 1, '5': 2 };
|
||||
expect(pcToInstruction).to.be.deep.equal(expectedPcToInstruction);
|
||||
});
|
||||
});
|
||||
});
|
||||
71
packages/sol-cov/test/source_maps_test.ts
Normal file
71
packages/sol-cov/test/source_maps_test.ts
Normal file
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
53
packages/sol-cov/test/utils_test.ts
Normal file
53
packages/sol-cov/test/utils_test.ts
Normal file
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,8 +5,10 @@
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*",
|
||||
"./test/**/*",
|
||||
"../../node_modules/types-bn/index.d.ts",
|
||||
"../../node_modules/web3-typescript-typings/index.d.ts",
|
||||
"../../node_modules/chai-typescript-typings/index.d.ts",
|
||||
"../../node_modules/types-ethereumjs-util/index.d.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user