Skip to content

Validate solcoverjs options with json-schema #403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const istanbul = require('istanbul');
const util = require('util');
const assert = require('assert');

const ConfigValidator = require('./validator');
const Instrumenter = require('./instrumenter');
const Coverage = require('./coverage');
const DataCollector = require('./collector');
Expand All @@ -20,8 +21,12 @@ class App {
constructor(config) {
this.coverage = new Coverage();
this.instrumenter = new Instrumenter();
this.validator = new ConfigValidator()
this.config = config || {};

// Validate
this.validator.validate(this.config);

// Options
this.testsErrored = false;
this.instrumentToFile = (config.instrumentToFile === false) ? false : true;
Expand Down
3 changes: 3 additions & 0 deletions lib/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ class AppUI extends UI {
const c = this.chalk;

const kinds = {
'config-fail':`${c.red('A config option (.solcover.js) is incorrectly formatted: ')}` +
`${c.red(args[0])}.`,

'instr-fail': `${c.red('Could not instrument:')} ${args[0]}. ` +
`${c.red('(Please verify solc can compile this file without errors.) ')}`,

Expand Down
72 changes: 72 additions & 0 deletions lib/validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
const Validator = require('jsonschema').Validator;
const AppUI = require('./ui').AppUI;
const util = require('util')


function isFunction(input){

}

Validator.prototype.customFormats.isFunction = function(input) {
return typeof input === "function"
};

const configSchema = {
id: "/solcoverjs",
type: "object",
properties: {

client: {type: "object"},
cwd: {type: "string"},
host: {type: "string"},


originalContractsDir: {type: "string"},
port: {type: "number"},
providerOptions: {type: "object"},
silent: {type: "boolean"},

// Hooks:
onServerReady: {type: "function", format: "isFunction"},
onTestComplete: {type: "function", format: "isFunction"},
onIstanbulComplete: {type: "function", format: "isFunction"},

// Arrays
skipFiles: {
type: "array",
items: {type: "string"}
},

istanbulReporter: {
type: "array",
items: {type: "string"}
},
},
};

class ConfigValidator {
constructor(){
this.validator = new Validator();
this.validator.addSchema(configSchema);
this.ui = new AppUI();
}

validate(config){
let result = this.validator.validate(config, configSchema);

if (result.errors.length){
let msg;
const option = `"${result.errors[0].property.replace('instance.', '')}"`;

(result.errors[0].argument === 'isFunction')
? msg = `${option} is not a function`
: msg = `${option} ${result.errors[0].message}`;

throw new Error(this.ui.generate('config-fail', [msg]));
}

return true;
}
}

module.exports = ConfigValidator;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"global-modules": "^2.0.0",
"globby": "^10.0.1",
"istanbul": "^0.4.5",
"jsonschema": "^1.2.4",
"node-dir": "^0.1.17",
"node-emoji": "^1.10.0",
"pify": "^4.0.1",
Expand Down
17 changes: 16 additions & 1 deletion test/units/truffle/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ describe('Truffle Plugin: error cases', function() {
verify.coverageNotGenerated(truffleConfig);
});

it('project .solcover.js has syntax error', async function(){
it('.solcover.js has syntax error', async function(){
verify.cleanInitialState();

mock.installFullProject('bad-solcoverjs');
Expand All @@ -64,7 +64,22 @@ describe('Truffle Plugin: error cases', function() {
verify.coverageNotGenerated(truffleConfig);
})

it('.solcover.js has incorrectly formatted option', async function(){
verify.cleanInitialState();
solcoverConfig.port = "Antwerpen";

mock.install('Simple', 'simple.js', solcoverConfig);

try {
await plugin(truffleConfig);
assert.fail()
} catch (err) {
assert(
err.message.includes('config option'),
`Should error on incorrect config options: ${err.message}`
);
}
});

it('lib module load failure', async function(){
verify.cleanInitialState();
Expand Down
137 changes: 137 additions & 0 deletions test/units/validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
const assert = require('assert');
const util = require('util');
const ConfigValidator = require('./../../lib/validator');

describe('config validation', () => {
let validator;
let solcoverjs;

before(() => validator = new ConfigValidator());
beforeEach(() => solcoverjs = {});

it('validates an empty config', function() {
assert(validator.validate(solcoverjs), '{} should be valid');
})

it('validates config with unknown options', function(){
solcoverjs.unknown_option = 'hello';
assert(validator.validate(solcoverjs), '.cwd string should be valid')
})

it('validates the "string" options', function(){
const options = [
"cwd",
"host",
"originalContractsDir",
]

options.forEach(name => {
// Pass
solcoverjs = {};
solcoverjs[name] = "a_string";
assert(validator.validate(solcoverjs), `${name} string should be valid`)

// Fail
solcoverjs[name] = 0;
try {
validator.validate(solcoverjs);
assert.fail()
} catch (err){
assert(err.message.includes(`"${name}" is not of a type(s) string`), err.message);
}
});
});

it('validates the "object" options', function(){
const options = [
"client",
"providerOptions",
]

options.forEach(name => {
// Pass
solcoverjs = {};
solcoverjs[name] = {a_property: 'a'};
assert(validator.validate(solcoverjs), `${name} object should be valid`)

// Fail
solcoverjs[name] = 0;
try {
validator.validate(solcoverjs);
assert.fail()
} catch (err){
assert(err.message.includes(`"${name}" is not of a type(s) object`), err.message);
}
});
});

it('validates the "number" options', function(){
const options = [
"port",
]

options.forEach(name => {
// Pass
solcoverjs = {};
solcoverjs[name] = 0;
assert(validator.validate(solcoverjs), `${name} number should be valid`)

// Fail
solcoverjs[name] = "a_string";
try {
validator.validate(solcoverjs);
assert.fail()
} catch (err){
assert(err.message.includes(`"${name}" is not of a type(s) number`), err.message);
}
});
});

it('validates string array options', function(){
const options = [
"skipFiles",
"istanbulReporter",
]

options.forEach(name => {
// Pass
solcoverjs = {};
solcoverjs[name] = ['a_string'];
assert(validator.validate(solcoverjs), `${name} string array should be valid`)

// Fail
solcoverjs[name] = "a_string";
try {
validator.validate(solcoverjs);
assert.fail()
} catch (err){
assert(err.message.includes(`"${name}" is not of a type(s) array`), err.message);
}
});
});

it('validates function options', function(){

const options = [
"onServerReady",
"onTestComplete",
"onIstanbulComplete",
]

options.forEach(name => {
// Pass
solcoverjs = {};
solcoverjs[name] = async (a,b) => {};
assert(validator.validate(solcoverjs), `${name} string array should be valid`)

// Fail
solcoverjs[name] = "a_string";
try {
validator.validate(solcoverjs);
assert.fail()
} catch (err){
assert(err.message.includes(`"${name}" is not a function`), err.message);
}
});
});
});
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4138,6 +4138,11 @@ jsonparse@^1.2.0:
resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=

jsonschema@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.4.tgz#a46bac5d3506a254465bc548876e267c6d0d6464"
integrity sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw==

jsprim@^1.2.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
Expand Down