Add API documentation (#447)

pull/448/head
cgewecke 5 years ago committed by GitHub
parent 3612a815e6
commit f2cdb63498
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      README.md
  2. 368
      docs/api.md
  3. 5
      lib/api.js
  4. 11
      plugins/resources/plugin.utils.js
  5. 25
      test/units/api.js
  6. 4
      utils.js

@ -95,6 +95,16 @@ module.exports = {
[<sup>*</sup> Advanced use][14] [<sup>*</sup> 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 ## FAQ
Common problems & questions: Common problems & questions:
@ -171,3 +181,4 @@ $ yarn
[25]: https://github.com/sc-forks/solidity-coverage/issues/417 [25]: https://github.com/sc-forks/solidity-coverage/issues/417
[26]: https://buidler.dev/ [26]: https://buidler.dev/
[27]: https://www.trufflesuite.com/docs [27]: https://www.trufflesuite.com/docs
[28]: https://github.com/sc-forks/solidity-coverage/blob/beta/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 <img width=200/>| Type <img width=200/> | Default <img width=1300/> | Description <img width=800/> |
| ------ | ---- | ------- | ----------- |
| 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();
```

@ -12,8 +12,6 @@ const Instrumenter = require('./instrumenter');
const Coverage = require('./coverage'); const Coverage = require('./coverage');
const DataCollector = require('./collector'); const DataCollector = require('./collector');
const AppUI = require('./ui').AppUI; const AppUI = require('./ui').AppUI;
const utils = require('./../plugins/resources/plugin.utils');
/** /**
* Coverage Runner * Coverage Runner
@ -61,7 +59,6 @@ class API {
this.setLoggingLevel(config.silent); this.setLoggingLevel(config.silent);
this.ui = new AppUI(this.log); this.ui = new AppUI(this.log);
this.utils = utils;
} }
/** /**
@ -138,7 +135,7 @@ class API {
* the consumer can control the 'server.listen' invocation themselves. * the consumer can control the 'server.listen' invocation themselves.
* @param {Object} client ganache client * @param {Object} client ganache client
* @param {Boolean} autoLaunchServer boolean * @param {Boolean} autoLaunchServer boolean
* @return {String | Server} address of server to connect to, or initialized, unlaunched server. * @return {<Promise> (String | Server) } address of server to connect to, or initialized, unlaunched server.
*/ */
async ganache(client, autoLaunchServer){ async ganache(client, autoLaunchServer){
// Check for port-in-use // Check for port-in-use

@ -61,8 +61,8 @@ function setupTempFolders(config, tempContractsDir, tempArtifactsDir){
/** /**
* Save a set of instrumented files to a temporary directory. * Save a set of instrumented files to a temporary directory.
* @param {Object[]} targets array of targets generated by `assembleTargets` * @param {Object[]} targets array of targets generated by `assembleTargets`
* @param {[type]} originalDir absolute path to parent directory of original source * @param {[type]} originalDir absolute path to original contracts directory
* @param {[type]} tempDir absolute path to temp parent directory * @param {[type]} tempDir absolute path to temp contracts directory
*/ */
function save(targets, originalDir, tempDir){ function save(targets, originalDir, tempDir){
let _path; let _path;
@ -189,10 +189,11 @@ function assembleSkipped(config, targets, skipFiles=[]){
return skipFiles; return skipFiles;
} }
function loadSolcoverJS(config){ function loadSolcoverJS(config={}){
let solcoverjs; let solcoverjs;
let coverageConfig; 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 // Handle --solcoverjs flag
(config.solcoverjs) (config.solcoverjs)
@ -215,7 +216,7 @@ function loadSolcoverJS(config){
} }
// Truffle writes to coverage config // Truffle writes to coverage config
coverageConfig.log = config.logger.log; coverageConfig.log = log;
coverageConfig.cwd = config.workingDir; coverageConfig.cwd = config.workingDir;
coverageConfig.originalContractsDir = config.contractsDir; coverageConfig.originalContractsDir = config.contractsDir;

@ -1,9 +1,11 @@
const assert = require('assert'); const assert = require('assert');
const util = require('./../util/util.js');
const API = require('./../../api.js');
const detect = require('detect-port'); const detect = require('detect-port');
const Ganache = require('ganache-cli'); const Ganache = require('ganache-cli');
const util = require('./../util/util.js');
const API = require('./../../api.js');
const utils = require('./../../utils.js');
describe('api', () => { describe('api', () => {
let opts; let opts;
@ -86,15 +88,14 @@ describe('api', () => {
assert(freePort === port); assert(freePort === port);
}) })
it('api.utils', async function(){ it('utils', async function(){
const api = new API(opts); assert(utils.assembleFiles !== undefined)
assert(api.utils.assembleFiles !== undefined) assert(utils.checkContext !== undefined)
assert(api.utils.checkContext !== undefined) assert(utils.finish !== undefined)
assert(api.utils.finish !== undefined) assert(utils.getTempLocations !== undefined)
assert(api.utils.getTempLocations !== undefined) assert(utils.setupTempFolders !== undefined)
assert(api.utils.setupTempFolders !== undefined) assert(utils.loadSource !== undefined)
assert(api.utils.loadSource !== undefined) assert(utils.loadSolcoverJS !== undefined)
assert(api.utils.loadSolcoverJS !== undefined) assert(utils.save !== undefined)
assert(api.utils.save !== undefined)
}); });
}) })

@ -0,0 +1,4 @@
// For require('solidity-coverage/utils');
const utils = require('./plugins/resources/plugin.utils');
module.exports = utils;
Loading…
Cancel
Save