diff --git a/README.md b/README.md
index 2fceb17e..d502fb36 100644
--- a/README.md
+++ b/README.md
@@ -95,6 +95,16 @@ module.exports = {
[* Advanced use][14]
+## API
+
+Solidity-coverage's core methods and many utilities are available as an API.
+
+```javascript
+const CoverageAPI = require('solidity-coverage/api');
+```
+
+[Documentation available here][28].
+
## FAQ
Common problems & questions:
@@ -171,3 +181,4 @@ $ yarn
[25]: https://github.com/sc-forks/solidity-coverage/issues/417
[26]: https://buidler.dev/
[27]: https://www.trufflesuite.com/docs
+[28]: https://github.com/sc-forks/solidity-coverage/blob/beta/docs/api.md
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 00000000..8c8d7c63
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,368 @@
+# Solidity-Coverage API Documentation
+
+`solidity-coverage`'s API provides test coverage measurement for the Solidity language.
+The repository contains two complete coverage tool/plugin implementations (for Buidler and Truffle)
+which can be used as sources if you're building something similar.
+
+`solidity-coverage`'s core algorithm resembles the one used by [Istanbul][3] for javascript programs.
+It tracks line and branch locations by 'instrumenting' solidity contracts with special solidity
+statements and detecting their execution in a coverage-enabled EVM. As such, its API spans the
+full set of tasks typically required to run a solidity test suite.
+
++ compile
++ ethereum client launch
++ test
++ report outcome and exit
+
+[3]: https://github.com/gotwarlost/istanbul
+
+The API's corresponding methods are:
+
++ `instrument`: Rewrites contracts for instrumented compilation. Generates an instrumentation data map.
++ `ganache`: Launches a ganache client with coverage collection enabled in its VM. As the client
+ runs it will mark line/branch hits on the instrumentation data map.
++ `report`: Generates a coverage report from the data collected by the VM after tests complete. Converts
+ the instrumentation data map into an object IstanbulJS can process.
++ `finish`: Shuts client down
+
+The library also includes some file system [utilities](#Utils) which are helpful for managing the
+disposable set of contracts/artifacts which coverage must use in lieu of the 'real' contracts/artifacts.
+
+# Table of Contents
+
+- [API Methods](#api)
+ * [constructor](#constructor)
+ * [instrument](#instrument)
+ * [ganache](#ganache)
+ * [report](#report)
+ * [finish](#finish)
+ * [getInstrumentationData](#getinstrumentationdata)
+ * [setInstrumentationData](#setinstrumentationdata)
+- [Utils Methods](#utils)
+ * [loadSolcoverJS](#loadsolcoverjs)
+ * [assembleFiles](#assemblefiles)
+ * [getTempLocations](#gettemplocations)
+ * [setupTempFolders](#setuptempfolders)
+ * [save](#save)
+ * [finish](#finish-1)
+
+# API
+
+**Example**
+```javascript
+const CoverageAPI = require("solidity-coverage/api");
+const api = new CoverageAPI(options);
+```
+
+## constructor
+
+Creates a coverage API instance. Configurable.
+
+**Parameters**
+
+- `options` **Object** : API options
+
+| Option
| Type
| Default
| Description
|
+| ------ | ---- | ------- | ----------- |
+| port | *Number* | 8555 | Port to launch client on |
+| silent | *Boolean* | false | Suppress logging output |
+| client | *Object* | `require("ganache-core")` | JS Ethereum client |
+| providerOptions | *Object* | `{ }` | [ganache-core options][1] |
+| skipFiles | *Array* | `[]` | Array of contracts or folders (with paths expressed relative to the `contracts` directory) that should be skipped when doing instrumentation. |
+| istanbulFolder | *String* | `./coverage` | Folder location for Istanbul coverage reports. |
+| istanbulReporter | *Array* | `['html', 'lcov', 'text', 'json']` | [Istanbul coverage reporters][2] |
+
+[1]: https://github.com/trufflesuite/ganache-core#options
+[2]: https://istanbul.js.org/docs/advanced/alternative-reporters/
+
+--------------
+
+## instrument
+
+Instruments a set of sources to prepare them for compilation.
+
+:warning: **Important:** Instrumented sources must be compiled with **solc optimization OFF** :warning:
+
+**Parameters**
+
+- `contracts` **Object[]**: Array of solidity sources and their paths
+
+Returns **Object[]** in the same format as the `contracts` param, but with sources instrumented.
+
+**Example**
+```javascript
+const contracts = [{
+ source: "contract Simple { uint x = 5; }",
+ canonicalPath: "/Users/user/project/contracts/Simple.sol",
+ relativePath: "Simple.sol" // Optional, used for pretty printing.
+},...]
+
+const instrumented = api.instrument(contracts)
+```
+
+--------------
+
+## ganache
+
+Enables coverage data collection on an in-process ganache server. By default, will return
+a url after server has begun listening on the port specified in the [config](#constructor)
+(or 8555 by default). When `autoLaunchServer` is false, method returns`ganache.server`
+so the consumer can control the 'server.listen' invocation themselves.
+
+**Parameters**
+
+- `client` **Object**: (*Optional*) ganache module
+- `autoLaunchServer` **Boolean**: (*Optional*)
+
+Returns **Promise** Address of server to connect to, or initialized, unlaunched server
+
+**Example**
+```javascript
+const client = require('ganache-cli');
+
+const api = new CoverageAPI( { client: client } );
+const address = await api.ganache();
+
+> http://127.0.0.1:8555
+
+// Alternatively...
+
+const server = await api.ganache(client, false);
+await pify(server.listen()(8545));
+```
+
+--------------
+
+## report
+
+Generates coverage report using IstanbulJS
+
+**Parameters**
+
+- `istanbulFolder` **String**: (*Optional*) path to folder IstanbulJS will deposit coverage reports in.
+
+Returns **Promise**
+
+**Example**
+```javascript
+await api.report('./coverage_4A3cd2b'); // Default folder name is 'coverage'
+```
+
+-------------
+
+## finish
+
+Shuts down coverage-enabled ganache server instance
+
+Returns **Promise**
+
+**Example**
+```javascript
+const client = require('ganache-cli');
+
+await api.ganache(client); // Server listening...
+await api.finish(); // Server shut down.
+```
+
+-------------
+
+## getInstrumentationData
+
+Returns a copy of the hit map created during instrumentation. Useful if you'd like to delegate
+coverage collection to multiple processes.
+
+Returns **Object** instrumentation data;
+
+
+**Example**
+```javascript
+const contracts = api.instrument(contracts);
+const data = api.getInstrumentationData();
+save(data);
+```
+
+-------------
+
+## setInstrumentationData
+
+Sets the hit map object generated during instrumentation. Useful if you'd like
+to collect or convert data to coverage for an instrumentation which was generated
+in a different process.
+
+**Example**
+```javascript
+const data = load(data);
+api.setIntrumentationData(data);
+
+// Client will collect data for the loaded map
+const address = await api.ganache(client);
+
+// Or to `report` instrumentation data which was collected in a different process.
+const data = load(data);
+api.setInstrumentationData(data);
+
+api.report();
+```
+
+----------------------------------------------------------------------------------------------------
+
+# Utils
+
+```javascript
+const utils = require('solidity-coverage/utils');
+```
+
+Many of the utils methods take a `config` object param which
+defines the absolute paths to your project root and contracts directory.
+
+**Example**
+```javascript
+const config = {
+ workingDir: process.cwd(),
+ contractsDir: path.join(process.cwd(), 'contracts'),
+}
+```
+-------------
+
+## loadSolcoverJS
+
+Loads `.solcoverjs`. Users may specify options described in the README in `.solcover.js` config
+file which your application needs to consume.
+
+**Parameters**
+
+- `config` **Object**: [See *config* above](#Utils)
+
+Returns **Object** Normalized coverage config
+
+
+**Example**
+```javascript
+const solcoverJS = utils.loadSolcoverJS(config);
+const api = new CoverageAPI(solcoverJS);
+```
+
+-------------
+
+## assembleFiles
+
+Loads contracts from the filesystem in a format that can be passed directly to the
+[api.instrument](#instrument) method. Filters by an optional `skipFiles` parameter.
+
+**Parameters**
+
+- `config` **Object**: [See *config* above](#Utils)
+- `skipFiles` **String[]**: (*Optional*) Array of files or folders to skip
+ [See API *constructor*](#constructor)
+
+Returns **Object** with `targets` and `skipped` keys. These are Object arrays of contract sources
+and paths.
+
+**Example**
+```javascript
+const {
+ targets,
+ skipped
+} = utils.assembleFiles(config, ['Migrations.sol'])
+
+const instrumented = api.instrument(targets);
+```
+
+--------------
+
+## getTempLocations
+
+Returns a pair of canonically named temporary directory paths for contracts
+and artifacts. Instrumented assets can be compiled from and written to these so the unit tests can
+use them as sources.
+
+**Parameters**
+
+- `config` **Object**: [See *config* above](#Utils)
+
+Returns **Object** with two absolute paths to disposable folders, `tempContractsDir`, `tempArtifactsDir`.
+These directories are named `.coverage_contracts` and `.coverage_artifacts`.
+
+**Example**
+```javascript
+const {
+ tempContractsDir,
+ tempArtifactsDir
+} = utils.getTempLocations(config)
+
+utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir)
+
+// Later, you can call `utils.finish` to delete these...
+utils.finish(config, api)
+```
+
+----------
+
+## setupTempFolders
+
+Creates temporary directories to store instrumented contracts and their compilation artifacts in.
+
+**Parameters**
+
+- `config` **Object**: [See *config* above](#Utils)
+- `tempContractsDir` **String**: absolute path to temporary contracts directory
+- `tempArtifactsDir` **String**: absolute path to temporary artifacts directory
+
+**Example**
+```javascript
+const {
+ tempContractsDir,
+ tempArtifactsDir
+} = utils.getTempLocations(config)
+
+utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir);
+```
+-------------
+
+## save
+
+Writes an array of instrumented sources in the object format returned by
+[api.instrument](#instrument) to a temporary directory.
+
+**Parameters**
+
+- `contracts` **Object[]**: array of contracts & paths generated by [api.instrument](#instrument)
+- `originalDir` **String**: absolute path to original contracts directory
+- `tempDir` **String**: absolute path to temp contracts directory (the destination of the save)
+
+**Example**
+```javascript
+const {
+ tempContractsDir,
+ tempArtifactsDir
+} = utils.getTempLocations(config)
+
+utils.setupTempFolders(config, tempContractsDir, tempArtifactsDir);
+
+const instrumented = api.instrument(targets);
+
+utils.save(instrumented, config.contractsDir, tempContractsDir);
+```
+
+-------------
+
+## finish
+
+Deletes temporary folders and shuts the ganache server down. Is tolerant - if folders or ganache
+server don't exist it will return silently.
+
+**Parameters**
+
+- `config` **Object**: [See *config* above](#Utils)
+- `api` **Object**: (*Optional*) coverage api instance whose own `finish` method will be called
+
+Returns **Promise**
+
+**Example**
+```javascript
+await utils.finish();
+```
+
+
+
+
diff --git a/lib/api.js b/lib/api.js
index 480d33f4..3b5d1cf9 100644
--- a/lib/api.js
+++ b/lib/api.js
@@ -12,8 +12,6 @@ const Instrumenter = require('./instrumenter');
const Coverage = require('./coverage');
const DataCollector = require('./collector');
const AppUI = require('./ui').AppUI;
-const utils = require('./../plugins/resources/plugin.utils');
-
/**
* Coverage Runner
@@ -61,7 +59,6 @@ class API {
this.setLoggingLevel(config.silent);
this.ui = new AppUI(this.log);
- this.utils = utils;
}
/**
@@ -138,7 +135,7 @@ class API {
* the consumer can control the 'server.listen' invocation themselves.
* @param {Object} client ganache client
* @param {Boolean} autoLaunchServer boolean
- * @return {String | Server} address of server to connect to, or initialized, unlaunched server.
+ * @return { (String | Server) } address of server to connect to, or initialized, unlaunched server.
*/
async ganache(client, autoLaunchServer){
// Check for port-in-use
diff --git a/plugins/resources/plugin.utils.js b/plugins/resources/plugin.utils.js
index 20c3034a..72fa5a31 100644
--- a/plugins/resources/plugin.utils.js
+++ b/plugins/resources/plugin.utils.js
@@ -61,8 +61,8 @@ function setupTempFolders(config, tempContractsDir, tempArtifactsDir){
/**
* Save a set of instrumented files to a temporary directory.
* @param {Object[]} targets array of targets generated by `assembleTargets`
- * @param {[type]} originalDir absolute path to parent directory of original source
- * @param {[type]} tempDir absolute path to temp parent directory
+ * @param {[type]} originalDir absolute path to original contracts directory
+ * @param {[type]} tempDir absolute path to temp contracts directory
*/
function save(targets, originalDir, tempDir){
let _path;
@@ -189,10 +189,11 @@ function assembleSkipped(config, targets, skipFiles=[]){
return skipFiles;
}
-function loadSolcoverJS(config){
+function loadSolcoverJS(config={}){
let solcoverjs;
let coverageConfig;
- let ui = new PluginUI(config.logger.log);
+ let log = config.logger ? config.logger.log : console.log;
+ let ui = new PluginUI(log);
// Handle --solcoverjs flag
(config.solcoverjs)
@@ -215,7 +216,7 @@ function loadSolcoverJS(config){
}
// Truffle writes to coverage config
- coverageConfig.log = config.logger.log;
+ coverageConfig.log = log;
coverageConfig.cwd = config.workingDir;
coverageConfig.originalContractsDir = config.contractsDir;
diff --git a/test/units/api.js b/test/units/api.js
index 39f9e314..4290e7a0 100644
--- a/test/units/api.js
+++ b/test/units/api.js
@@ -1,9 +1,11 @@
const assert = require('assert');
-const util = require('./../util/util.js');
-const API = require('./../../api.js');
const detect = require('detect-port');
const Ganache = require('ganache-cli');
+const util = require('./../util/util.js');
+const API = require('./../../api.js');
+const utils = require('./../../utils.js');
+
describe('api', () => {
let opts;
@@ -86,15 +88,14 @@ describe('api', () => {
assert(freePort === port);
})
- it('api.utils', async function(){
- const api = new API(opts);
- assert(api.utils.assembleFiles !== undefined)
- assert(api.utils.checkContext !== undefined)
- assert(api.utils.finish !== undefined)
- assert(api.utils.getTempLocations !== undefined)
- assert(api.utils.setupTempFolders !== undefined)
- assert(api.utils.loadSource !== undefined)
- assert(api.utils.loadSolcoverJS !== undefined)
- assert(api.utils.save !== undefined)
+ it('utils', async function(){
+ assert(utils.assembleFiles !== undefined)
+ assert(utils.checkContext !== undefined)
+ assert(utils.finish !== undefined)
+ assert(utils.getTempLocations !== undefined)
+ assert(utils.setupTempFolders !== undefined)
+ assert(utils.loadSource !== undefined)
+ assert(utils.loadSolcoverJS !== undefined)
+ assert(utils.save !== undefined)
});
})
diff --git a/utils.js b/utils.js
new file mode 100644
index 00000000..6520168e
--- /dev/null
+++ b/utils.js
@@ -0,0 +1,4 @@
+// For require('solidity-coverage/utils');
+const utils = require('./plugins/resources/plugin.utils');
+
+module.exports = utils;