aboutsummaryrefslogtreecommitdiffstats
path: root/packages/sol-coverage
diff options
context:
space:
mode:
Diffstat (limited to 'packages/sol-coverage')
-rw-r--r--packages/sol-coverage/.npmignore6
-rw-r--r--packages/sol-coverage/CHANGELOG.json12
-rw-r--r--packages/sol-coverage/README.md75
-rw-r--r--packages/sol-coverage/package.json52
-rw-r--r--packages/sol-coverage/src/coverage_subprovider.ts146
-rw-r--r--packages/sol-coverage/src/globals.d.ts7
-rw-r--r--packages/sol-coverage/src/index.ts23
-rw-r--r--packages/sol-coverage/tsconfig.json8
-rw-r--r--packages/sol-coverage/tslint.json3
-rw-r--r--packages/sol-coverage/typedoc-tsconfig.json7
10 files changed, 339 insertions, 0 deletions
diff --git a/packages/sol-coverage/.npmignore b/packages/sol-coverage/.npmignore
new file mode 100644
index 000000000..037786e46
--- /dev/null
+++ b/packages/sol-coverage/.npmignore
@@ -0,0 +1,6 @@
+.*
+yarn-error.log
+/src/
+/scripts/
+tsconfig.json
+/lib/src/monorepo_scripts/
diff --git a/packages/sol-coverage/CHANGELOG.json b/packages/sol-coverage/CHANGELOG.json
new file mode 100644
index 000000000..ca6048d99
--- /dev/null
+++ b/packages/sol-coverage/CHANGELOG.json
@@ -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
+ }
+ ]
+ }
+]
diff --git a/packages/sol-coverage/README.md b/packages/sol-coverage/README.md
new file mode 100644
index 000000000..f8cc62eb8
--- /dev/null
+++ b/packages/sol-coverage/README.md
@@ -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
+```
diff --git a/packages/sol-coverage/package.json b/packages/sol-coverage/package.json
new file mode 100644
index 000000000..b95fa5502
--- /dev/null
+++ b/packages/sol-coverage/package.json
@@ -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"
+ }
+}
diff --git a/packages/sol-coverage/src/coverage_subprovider.ts b/packages/sol-coverage/src/coverage_subprovider.ts
new file mode 100644
index 000000000..255fa9481
--- /dev/null
+++ b/packages/sol-coverage/src/coverage_subprovider.ts
@@ -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;
+};
diff --git a/packages/sol-coverage/src/globals.d.ts b/packages/sol-coverage/src/globals.d.ts
new file mode 100644
index 000000000..e799b3529
--- /dev/null
+++ b/packages/sol-coverage/src/globals.d.ts
@@ -0,0 +1,7 @@
+// tslint:disable:completed-docs
+declare module '*.json' {
+ const json: any;
+ /* tslint:disable */
+ export default json;
+ /* tslint:enable */
+}
diff --git a/packages/sol-coverage/src/index.ts b/packages/sol-coverage/src/index.ts
new file mode 100644
index 000000000..dcf97b72e
--- /dev/null
+++ b/packages/sol-coverage/src/index.ts
@@ -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';
diff --git a/packages/sol-coverage/tsconfig.json b/packages/sol-coverage/tsconfig.json
new file mode 100644
index 000000000..233008d61
--- /dev/null
+++ b/packages/sol-coverage/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../tsconfig",
+ "compilerOptions": {
+ "outDir": "lib",
+ "rootDir": "."
+ },
+ "include": ["./src/**/*"]
+}
diff --git a/packages/sol-coverage/tslint.json b/packages/sol-coverage/tslint.json
new file mode 100644
index 000000000..dd9053357
--- /dev/null
+++ b/packages/sol-coverage/tslint.json
@@ -0,0 +1,3 @@
+{
+ "extends": ["@0x/tslint-config"]
+}
diff --git a/packages/sol-coverage/typedoc-tsconfig.json b/packages/sol-coverage/typedoc-tsconfig.json
new file mode 100644
index 000000000..c9b0af1ae
--- /dev/null
+++ b/packages/sol-coverage/typedoc-tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../typedoc-tsconfig",
+ "compilerOptions": {
+ "outDir": "lib"
+ },
+ "include": ["./src/**/*", "./test/**/*"]
+}