aboutsummaryrefslogtreecommitdiffstats
path: root/packages/sol-cov/src/coverage_manager.ts
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-06-12 06:23:48 +0800
committerFabio Berger <me@fabioberger.com>2018-06-12 06:23:48 +0800
commit7e78f5941ad9cb2fd5225899f79823c7376894c0 (patch)
tree13fadf90f821e9fead1373a2e567e6d86fe27e0f /packages/sol-cov/src/coverage_manager.ts
parent20f93185975d16c77492f05eb86dd89695539cd9 (diff)
parentbc0ae6be318a15bf8670a6da9a59d9bdb12cadae (diff)
downloaddexon-sol-tools-7e78f5941ad9cb2fd5225899f79823c7376894c0.tar
dexon-sol-tools-7e78f5941ad9cb2fd5225899f79823c7376894c0.tar.gz
dexon-sol-tools-7e78f5941ad9cb2fd5225899f79823c7376894c0.tar.bz2
dexon-sol-tools-7e78f5941ad9cb2fd5225899f79823c7376894c0.tar.lz
dexon-sol-tools-7e78f5941ad9cb2fd5225899f79823c7376894c0.tar.xz
dexon-sol-tools-7e78f5941ad9cb2fd5225899f79823c7376894c0.tar.zst
dexon-sol-tools-7e78f5941ad9cb2fd5225899f79823c7376894c0.zip
Merge branch 'v2-prototype' into feature/combinatorial-testing
* v2-prototype: (68 commits) Stop exporting ArtifactWriter Fix no-unused-variable tslint rule to include parameters and fix issues Fix linter exclude rule Validate all signature types rather then only ECSignatures Store the instantiated OrderValidationUtils Remove global hooks from tests and deploy contracts from within the specific tests Add EmitStatement to ASTVisitor Fix tslint issues Add back artifacts file Fix a bug in SolCompilerArtifacts adapter config overriding Move OrderValidationUtils (+ tests) and ExchangeTransferSimulator to order-utils export parseECSignature method Export ArtifactWriter from migrations package Remove unused artifact file Pass in generated contract wrapper to orderValidationUtils at instantiation Refactor orderValidationUtils to use the generated contract wrapper instead of the higher-level one Refactor ExchangeTransferSimulator public interface to accet an AbstractBalanceAndProxyAllowanceLazyStore so that this module could be re-used in different contexts. Increase timeout for contract migrations Remove some copy-paste code Await transactions in migrations ...
Diffstat (limited to 'packages/sol-cov/src/coverage_manager.ts')
-rw-r--r--packages/sol-cov/src/coverage_manager.ts154
1 files changed, 59 insertions, 95 deletions
diff --git a/packages/sol-cov/src/coverage_manager.ts b/packages/sol-cov/src/coverage_manager.ts
index 064338a32..3ab363b52 100644
--- a/packages/sol-cov/src/coverage_manager.ts
+++ b/packages/sol-cov/src/coverage_manager.ts
@@ -21,6 +21,7 @@ import {
SourceRange,
StatementCoverage,
StatementDescription,
+ Subtrace,
TraceInfo,
TraceInfoExistingContract,
TraceInfoNewContract,
@@ -29,21 +30,30 @@ import { utils } from './utils';
const mkdirpAsync = promisify<undefined>(mkdirp);
+/**
+ * CoverageManager is used by CoverageSubprovider to compute code coverage based on collected trace data.
+ */
export class CoverageManager {
private _artifactAdapter: AbstractArtifactAdapter;
private _logger: Logger;
private _traceInfos: TraceInfo[] = [];
- // tslint:disable-next-line:no-unused-variable
- private _getContractCodeAsync: (address: string) => Promise<string>;
- private static _getSingleFileCoverageForTrace(
+ /**
+ * 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
+ */
+ private static _getSingleFileCoverageForSubtrace(
contractData: ContractData,
- coveredPcs: number[],
+ subtrace: Subtrace,
pcToSourceRange: { [programCounter: number]: SourceRange },
fileIndex: number,
): Coverage {
const absoluteFileName = contractData.sources[fileIndex];
const coverageEntriesDescription = collectCoverageEntries(contractData.sourceCodes[fileIndex]);
- let sourceRanges = _.map(coveredPcs, coveredPc => pcToSourceRange[coveredPc]);
+ 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
@@ -52,26 +62,32 @@ export class CoverageManager {
const branchIds = _.keys(coverageEntriesDescription.branchMap);
for (const branchId of branchIds) {
const branchDescription = coverageEntriesDescription.branchMap[branchId];
- const isCoveredByBranchIndex = _.map(branchDescription.locations, location =>
- _.some(sourceRanges, range => utils.isRangeInside(range.location, location)),
- );
- branchCoverage[branchId] = isCoveredByBranchIndex;
+ 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 isCovered = _.some(sourceRanges, range => utils.isRangeInside(range.location, statementDescription));
- statementCoverage[statementId] = isCovered;
+ 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 isCovered = _.some(sourceRanges, range =>
+ const isFunctionCovered = _.some(sourceRanges, range =>
utils.isRangeInside(range.location, functionDescription.loc),
);
- functionCoverage[fnId] = isCovered;
+ 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.
@@ -95,12 +111,12 @@ export class CoverageManager {
return isInsideTheModifierEnclosingFunction && isCovered;
},
);
- statementCoverage[modifierStatementId] = isModifierCovered;
+ const timesModifierCovered = Number(isModifierCovered);
+ statementCoverage[modifierStatementId] = timesModifierCovered;
}
const partialCoverage = {
[absoluteFileName]: {
...coverageEntriesDescription,
- l: {}, // It's able to derive it from statement coverage
path: absoluteFileName,
f: functionCoverage,
s: statementCoverage,
@@ -109,37 +125,7 @@ export class CoverageManager {
};
return partialCoverage;
}
- private static _bytecodeToBytecodeRegex(bytecode: string): string {
- const bytecodeRegex = bytecode
- // Library linking placeholder: __ConvertLib____________________________
- .replace(/_.*_/, '.*')
- // Last 86 characters is solidity compiler metadata that's different between compilations
- .replace(/.{86}$/, '')
- // Libraries contain their own address at the beginning of the code and it's impossible to know it in advance
- .replace(/^0x730000000000000000000000000000000000000000/, '0x73........................................');
- return bytecodeRegex;
- }
- private static _getContractDataIfExists(contractsData: ContractData[], bytecode: string): ContractData | undefined {
- if (!bytecode.startsWith('0x')) {
- throw new Error(`0x hex prefix missing: ${bytecode}`);
- }
- const contractData = _.find(contractsData, contractDataCandidate => {
- const bytecodeRegex = CoverageManager._bytecodeToBytecodeRegex(contractDataCandidate.bytecode);
- const runtimeBytecodeRegex = CoverageManager._bytecodeToBytecodeRegex(
- contractDataCandidate.runtimeBytecode,
- );
- // We use that function to find by bytecode or runtimeBytecode. Those are quasi-random strings so
- // collisions are practically impossible and it allows us to reuse that code
- return !_.isNull(bytecode.match(bytecodeRegex)) || !_.isNull(bytecode.match(runtimeBytecodeRegex));
- });
- return contractData;
- }
- constructor(
- artifactAdapter: AbstractArtifactAdapter,
- getContractCodeAsync: (address: string) => Promise<string>,
- isVerbose: boolean,
- ) {
- this._getContractCodeAsync = getContractCodeAsync;
+ constructor(artifactAdapter: AbstractArtifactAdapter, isVerbose: boolean) {
this._artifactAdapter = artifactAdapter;
this._logger = getLogger('sol-cov');
this._logger.setLevel(isVerbose ? levels.TRACE : levels.ERROR);
@@ -157,56 +143,34 @@ export class CoverageManager {
const contractsData = await this._artifactAdapter.collectContractsDataAsync();
const collector = new Collector();
for (const traceInfo of this._traceInfos) {
- if (traceInfo.address !== constants.NEW_CONTRACT) {
- // Runtime transaction
- const runtimeBytecode = (traceInfo as TraceInfoExistingContract).runtimeBytecode;
- const contractData = CoverageManager._getContractDataIfExists(contractsData, runtimeBytecode);
- if (_.isUndefined(contractData)) {
- this._logger.warn(`Transaction to an unknown address: ${traceInfo.address}`);
- continue;
- }
- const bytecodeHex = stripHexPrefix(runtimeBytecode);
- const sourceMap = contractData.sourceMapRuntime;
- const pcToSourceRange = parseSourceMap(
- contractData.sourceCodes,
- sourceMap,
- bytecodeHex,
- contractData.sources,
- );
- for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
- const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForTrace(
- contractData,
- traceInfo.coveredPcs,
- pcToSourceRange,
- fileIndex,
- );
- collector.add(singleFileCoverageForTrace);
- }
- } else {
- // Contract creation transaction
- const bytecode = (traceInfo as TraceInfoNewContract).bytecode;
- const contractData = CoverageManager._getContractDataIfExists(contractsData, bytecode);
- if (_.isUndefined(contractData)) {
- this._logger.warn(`Unknown contract creation transaction`);
- continue;
- }
- const bytecodeHex = stripHexPrefix(bytecode);
- const sourceMap = contractData.sourceMap;
- const pcToSourceRange = parseSourceMap(
- contractData.sourceCodes,
- sourceMap,
- bytecodeHex,
- contractData.sources,
+ const isContractCreation = traceInfo.address === constants.NEW_CONTRACT;
+ const bytecode = isContractCreation
+ ? (traceInfo as TraceInfoNewContract).bytecode
+ : (traceInfo as TraceInfoExistingContract).runtimeBytecode;
+ const contractData = utils.getContractDataIfExists(contractsData, bytecode);
+ if (_.isUndefined(contractData)) {
+ const errMsg = isContractCreation
+ ? `Unknown contract creation transaction`
+ : `Transaction to an unknown address: ${traceInfo.address}`;
+ this._logger.warn(errMsg);
+ continue;
+ }
+ const bytecodeHex = stripHexPrefix(bytecode);
+ const sourceMap = isContractCreation ? contractData.sourceMap : contractData.sourceMapRuntime;
+ const pcToSourceRange = parseSourceMap(
+ contractData.sourceCodes,
+ sourceMap,
+ bytecodeHex,
+ contractData.sources,
+ );
+ for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
+ const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForSubtrace(
+ contractData,
+ traceInfo.subtrace,
+ pcToSourceRange,
+ fileIndex,
);
- for (let fileIndex = 0; fileIndex < contractData.sources.length; fileIndex++) {
- const singleFileCoverageForTrace = CoverageManager._getSingleFileCoverageForTrace(
- contractData,
- traceInfo.coveredPcs,
- pcToSourceRange,
- fileIndex,
- );
- collector.add(singleFileCoverageForTrace);
- }
+ collector.add(singleFileCoverageForTrace);
}
}
return collector.getFinalCoverage();