Refactor out sol-cov, sol-profiler and sol-trace into their separate packages
This commit is contained in:
6
packages/sol-coverage/.npmignore
Normal file
6
packages/sol-coverage/.npmignore
Normal file
@@ -0,0 +1,6 @@
|
||||
.*
|
||||
yarn-error.log
|
||||
/src/
|
||||
/scripts/
|
||||
tsconfig.json
|
||||
/lib/src/monorepo_scripts/
|
||||
12
packages/sol-coverage/CHANGELOG.json
Normal file
12
packages/sol-coverage/CHANGELOG.json
Normal file
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"changes": [
|
||||
{
|
||||
"note":
|
||||
"Initial release as a separate package. For historic entries check @0x/sol-trace-based-tools-common",
|
||||
"pr": 1492
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
75
packages/sol-coverage/README.md
Normal file
75
packages/sol-coverage/README.md
Normal file
@@ -0,0 +1,75 @@
|
||||
## @0x/sol-coverage
|
||||
|
||||
A Solidity code coverage tool.
|
||||
|
||||
### Read the [Documentation](https://0xproject.com/docs/sol-coverage).
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
yarn add @0x/sol-coverage
|
||||
```
|
||||
|
||||
**Import**
|
||||
|
||||
```javascript
|
||||
import { CoverageSubprovider } from '@0x/sol-coverage';
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```javascript
|
||||
var CoverageSubprovider = require('@0x/sol-coverage').CoverageSubprovider;
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository.
|
||||
|
||||
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
|
||||
|
||||
### Install dependencies
|
||||
|
||||
If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
|
||||
|
||||
```bash
|
||||
yarn config set workspaces-experimental true
|
||||
```
|
||||
|
||||
Then install dependencies
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
|
||||
|
||||
```bash
|
||||
PKG=@0x/sol-coverage yarn build
|
||||
```
|
||||
|
||||
Or continuously rebuild on change:
|
||||
|
||||
```bash
|
||||
PKG=@0x/sol-coverage yarn watch
|
||||
```
|
||||
|
||||
### Clean
|
||||
|
||||
```bash
|
||||
yarn clean
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Run Tests
|
||||
|
||||
```bash
|
||||
yarn test
|
||||
```
|
||||
52
packages/sol-coverage/package.json
Normal file
52
packages/sol-coverage/package.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@0x/sol-coverage",
|
||||
"version": "1.0.0",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
"description": "Generate coverage reports for Solidity code",
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc -b",
|
||||
"build:ci": "yarn build",
|
||||
"lint": "tslint --format stylish --project .",
|
||||
"clean": "shx rm -rf lib src/artifacts generated_docs",
|
||||
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES"
|
||||
},
|
||||
"config": {
|
||||
"postpublish": {
|
||||
"assets": []
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x-monorepo.git"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/0xProject/0x-monorepo/issues"
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x-monorepo/packages/sol-coverage/README.md",
|
||||
"dependencies": {
|
||||
"@0x/subproviders": "^2.1.8",
|
||||
"@0x/sol-trace-based-tools-common": "^2.1.16",
|
||||
"@0x/typescript-typings": "^3.0.6",
|
||||
"ethereum-types": "^1.1.4",
|
||||
"lodash": "^4.17.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0x/tslint-config": "^2.0.0",
|
||||
"@types/node": "*",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"nyc": "^11.0.1",
|
||||
"shx": "^0.2.2",
|
||||
"sinon": "^4.0.0",
|
||||
"tslint": "5.11.0",
|
||||
"typedoc": "0.13.0",
|
||||
"typescript": "3.0.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
146
packages/sol-coverage/src/coverage_subprovider.ts
Normal file
146
packages/sol-coverage/src/coverage_subprovider.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import {
|
||||
AbstractArtifactAdapter,
|
||||
BranchCoverage,
|
||||
collectCoverageEntries,
|
||||
ContractData,
|
||||
Coverage,
|
||||
FunctionCoverage,
|
||||
FunctionDescription,
|
||||
SingleFileSubtraceHandler,
|
||||
SourceRange,
|
||||
StatementCoverage,
|
||||
StatementDescription,
|
||||
Subtrace,
|
||||
TraceCollector,
|
||||
TraceInfo,
|
||||
TraceInfoSubprovider,
|
||||
utils,
|
||||
} from '@0x/sol-trace-based-tools-common';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
/**
|
||||
* This class implements the [web3-provider-engine](https://github.com/MetaMask/provider-engine) subprovider interface.
|
||||
* It's used to compute your code coverage while running solidity tests.
|
||||
*/
|
||||
export class CoverageSubprovider extends TraceInfoSubprovider {
|
||||
private readonly _coverageCollector: TraceCollector;
|
||||
/**
|
||||
* Instantiates a CoverageSubprovider instance
|
||||
* @param artifactAdapter Adapter for used artifacts format (0x, truffle, giveth, etc.)
|
||||
* @param defaultFromAddress default from address to use when sending transactions
|
||||
* @param isVerbose If true, we will log any unknown transactions. Otherwise we will ignore them
|
||||
*/
|
||||
constructor(artifactAdapter: AbstractArtifactAdapter, defaultFromAddress: string, isVerbose: boolean = true) {
|
||||
const traceCollectionSubproviderConfig = {
|
||||
shouldCollectTransactionTraces: true,
|
||||
shouldCollectGasEstimateTraces: true,
|
||||
shouldCollectCallTraces: true,
|
||||
};
|
||||
super(defaultFromAddress, traceCollectionSubproviderConfig);
|
||||
this._coverageCollector = new TraceCollector(artifactAdapter, isVerbose, coverageHandler);
|
||||
}
|
||||
protected async _handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void> {
|
||||
await this._coverageCollector.computeSingleTraceCoverageAsync(traceInfo);
|
||||
}
|
||||
/**
|
||||
* Write the test coverage results to a file in Istanbul format.
|
||||
*/
|
||||
public async writeCoverageAsync(): Promise<void> {
|
||||
await this._coverageCollector.writeOutputAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computed partial coverage for a single file & subtrace.
|
||||
* @param contractData Contract metadata (source, srcMap, bytecode)
|
||||
* @param subtrace A subset of a transcation/call trace that was executed within that contract
|
||||
* @param pcToSourceRange A mapping from program counters to source ranges
|
||||
* @param fileIndex Index of a file to compute coverage for
|
||||
* @return Partial istanbul coverage for that file & subtrace
|
||||
*/
|
||||
export const coverageHandler: SingleFileSubtraceHandler = (
|
||||
contractData: ContractData,
|
||||
subtrace: Subtrace,
|
||||
pcToSourceRange: { [programCounter: number]: SourceRange },
|
||||
fileIndex: number,
|
||||
): Coverage => {
|
||||
const absoluteFileName = contractData.sources[fileIndex];
|
||||
const coverageEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]);
|
||||
|
||||
// if the source wasn't provided for the fileIndex, we can't cover the file
|
||||
if (_.isUndefined(coverageEntriesDescription)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
let sourceRanges = _.map(subtrace, structLog => pcToSourceRange[structLog.pc]);
|
||||
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.
|
||||
sourceRanges = _.uniqBy(sourceRanges, s => JSON.stringify(s)); // We don't care if one PC was covered multiple times within a single transaction
|
||||
sourceRanges = _.filter(sourceRanges, sourceRange => sourceRange.fileName === absoluteFileName);
|
||||
const branchCoverage: BranchCoverage = {};
|
||||
const branchIds = _.keys(coverageEntriesDescription.branchMap);
|
||||
for (const branchId of branchIds) {
|
||||
const branchDescription = coverageEntriesDescription.branchMap[branchId];
|
||||
const isBranchCoveredByBranchIndex = _.map(branchDescription.locations, location => {
|
||||
const isBranchCovered = _.some(sourceRanges, range => utils.isRangeInside(range.location, location));
|
||||
const timesBranchCovered = Number(isBranchCovered);
|
||||
return timesBranchCovered;
|
||||
});
|
||||
branchCoverage[branchId] = isBranchCoveredByBranchIndex;
|
||||
}
|
||||
const statementCoverage: StatementCoverage = {};
|
||||
const statementIds = _.keys(coverageEntriesDescription.statementMap);
|
||||
for (const statementId of statementIds) {
|
||||
const statementDescription = coverageEntriesDescription.statementMap[statementId];
|
||||
const isStatementCovered = _.some(sourceRanges, range =>
|
||||
utils.isRangeInside(range.location, statementDescription),
|
||||
);
|
||||
const timesStatementCovered = Number(isStatementCovered);
|
||||
statementCoverage[statementId] = timesStatementCovered;
|
||||
}
|
||||
const functionCoverage: FunctionCoverage = {};
|
||||
const functionIds = _.keys(coverageEntriesDescription.fnMap);
|
||||
for (const fnId of functionIds) {
|
||||
const functionDescription = coverageEntriesDescription.fnMap[fnId];
|
||||
const isFunctionCovered = _.some(sourceRanges, range =>
|
||||
utils.isRangeInside(range.location, functionDescription.loc),
|
||||
);
|
||||
const timesFunctionCovered = Number(isFunctionCovered);
|
||||
functionCoverage[fnId] = timesFunctionCovered;
|
||||
}
|
||||
// HACK: Solidity doesn't emit any opcodes that map back to modifiers with no args, that's why we map back to the
|
||||
// function range and check if there is any covered statement within that range.
|
||||
for (const modifierStatementId of coverageEntriesDescription.modifiersStatementIds) {
|
||||
if (statementCoverage[modifierStatementId]) {
|
||||
// Already detected as covered
|
||||
continue;
|
||||
}
|
||||
const modifierDescription = coverageEntriesDescription.statementMap[modifierStatementId];
|
||||
const enclosingFunction = _.find(coverageEntriesDescription.fnMap, functionDescription =>
|
||||
utils.isRangeInside(modifierDescription, functionDescription.loc),
|
||||
) as FunctionDescription;
|
||||
const isModifierCovered = _.some(
|
||||
coverageEntriesDescription.statementMap,
|
||||
(statementDescription: StatementDescription, statementId: number) => {
|
||||
const isInsideTheModifierEnclosingFunction = utils.isRangeInside(
|
||||
statementDescription,
|
||||
enclosingFunction.loc,
|
||||
);
|
||||
const isCovered = statementCoverage[statementId];
|
||||
return isInsideTheModifierEnclosingFunction && isCovered;
|
||||
},
|
||||
);
|
||||
const timesModifierCovered = Number(isModifierCovered);
|
||||
statementCoverage[modifierStatementId] = timesModifierCovered;
|
||||
}
|
||||
const partialCoverage = {
|
||||
[absoluteFileName]: {
|
||||
...coverageEntriesDescription,
|
||||
path: absoluteFileName,
|
||||
f: functionCoverage,
|
||||
s: statementCoverage,
|
||||
b: branchCoverage,
|
||||
},
|
||||
};
|
||||
return partialCoverage;
|
||||
};
|
||||
7
packages/sol-coverage/src/globals.d.ts
vendored
Normal file
7
packages/sol-coverage/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// tslint:disable:completed-docs
|
||||
declare module '*.json' {
|
||||
const json: any;
|
||||
/* tslint:disable */
|
||||
export default json;
|
||||
/* tslint:enable */
|
||||
}
|
||||
23
packages/sol-coverage/src/index.ts
Normal file
23
packages/sol-coverage/src/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export { CoverageSubprovider } from './coverage_subprovider';
|
||||
export {
|
||||
SolCompilerArtifactAdapter,
|
||||
TruffleArtifactAdapter,
|
||||
AbstractArtifactAdapter,
|
||||
ContractData,
|
||||
} from '@0x/sol-trace-based-tools-common';
|
||||
|
||||
export {
|
||||
JSONRPCRequestPayload,
|
||||
Provider,
|
||||
JSONRPCErrorCallback,
|
||||
JSONRPCResponsePayload,
|
||||
JSONRPCResponseError,
|
||||
} from 'ethereum-types';
|
||||
|
||||
export {
|
||||
JSONRPCRequestPayloadWithMethod,
|
||||
NextCallback,
|
||||
ErrorCallback,
|
||||
OnNextCompleted,
|
||||
Callback,
|
||||
} from '@0x/subproviders';
|
||||
8
packages/sol-coverage/tsconfig.json
Normal file
8
packages/sol-coverage/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["./src/**/*"]
|
||||
}
|
||||
3
packages/sol-coverage/tslint.json
Normal file
3
packages/sol-coverage/tslint.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": ["@0x/tslint-config"]
|
||||
}
|
||||
7
packages/sol-coverage/typedoc-tsconfig.json
Normal file
7
packages/sol-coverage/typedoc-tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../typedoc-tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib"
|
||||
},
|
||||
"include": ["./src/**/*", "./test/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user