227 lines
7.6 KiB
JavaScript
227 lines
7.6 KiB
JavaScript
|
function jsc_utils(jsc) {
|
||
|
function copyArgs(f) {
|
||
|
function f_(...args) {
|
||
|
let copies = args.map(a => {
|
||
|
if (typeof a === "function") return a;
|
||
|
else return _.cloneDeep(a);
|
||
|
});
|
||
|
return f.apply(this, copies);
|
||
|
};
|
||
|
return f_;
|
||
|
}
|
||
|
|
||
|
// patch the function to use copied arbitrary values
|
||
|
// jsverify goes crazy if arbitrary arguments are mutated
|
||
|
function patchArgs(args) {
|
||
|
let args_ = _.initial(args);
|
||
|
let f = _.last(args);
|
||
|
args_.push(copyArgs(f));
|
||
|
return args_;
|
||
|
}
|
||
|
|
||
|
function mocha_property(name, ...args) {
|
||
|
let args_ = patchArgs(args);
|
||
|
args_.unshift(name);
|
||
|
return jsc.property.apply(this, args_);
|
||
|
}
|
||
|
|
||
|
function pretty_function(data) {
|
||
|
function display(pair) {
|
||
|
let [as, r] = pair;
|
||
|
if (!_.isArray(as)) as = [as];
|
||
|
return '\t\tcase ' + JSON.stringify(as) + ': return ' +
|
||
|
JSON.stringify(r) + ';';
|
||
|
}
|
||
|
return 'function(...args) {\n\tswitch(args) {\n' + data.map(display).join('\n') + '}\n}';
|
||
|
}
|
||
|
function pretty(v) {
|
||
|
if (typeof v === 'function') {
|
||
|
if (v.original_fun !== undefined) v = v.original_fun;
|
||
|
return pretty_function(v.internalMap.data);
|
||
|
return JSON.stringify(v.internalMap.data, undefined, 2);
|
||
|
}
|
||
|
return JSON.stringify(v, undefined, 2);
|
||
|
}
|
||
|
|
||
|
function qunit_property_result(name, ...args) {
|
||
|
let args_ = patchArgs(args);
|
||
|
let res = jsc.check(jsc.forall.apply(this, args_));
|
||
|
if (res == true)
|
||
|
return QUnit.assert.pushResult({result: true, message: name});
|
||
|
let ce = '(' + _.take(res.counterexample, args.length - 1).map(pretty).join(',\n ') + ')\n';
|
||
|
return QUnit.assert.pushResult(
|
||
|
{result: false,
|
||
|
actual: res.exc.actual,
|
||
|
expected: res.exc.expected,
|
||
|
message: name + ' failed after ' +
|
||
|
res.tests + ' tests, counterexample:\n' + ce,
|
||
|
expectedLog: res.exc.expectedLog,
|
||
|
resultLog: res.exc.resultLog,
|
||
|
outputLog: res.exc.outputLog});
|
||
|
}
|
||
|
|
||
|
function qunit_property(name, ...args) {
|
||
|
let args_ = patchArgs(args);
|
||
|
return ok(jsc.assert(jsc.forall.apply(this,args_)) || true, name);
|
||
|
}
|
||
|
|
||
|
function propEqual(x,y,rl,el,ol) {
|
||
|
if (_.isEqual(x,y)) return true;
|
||
|
throw { actual: x, expected: y, expectedLog: el, resultLog: rl, outputLog: ol};
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
mocha_property: mocha_property,
|
||
|
qunit_property: qunit_property,
|
||
|
qunit_property_result: qunit_property_result,
|
||
|
propEqual: propEqual
|
||
|
};
|
||
|
}
|
||
|
|
||
|
function arbitrary_utils(jsc) {
|
||
|
/* custom arbitraries */
|
||
|
function set(arb) {
|
||
|
return arb.smap(_.uniq, _.identity);
|
||
|
}
|
||
|
|
||
|
function oneOfWeight(...args) {
|
||
|
return jsc.oneof(_.flatMap(args, x => _.fill(Array(x[1]), x[0])));
|
||
|
}
|
||
|
|
||
|
function equalLengthArrays(...args) {
|
||
|
// generate an array of tuples
|
||
|
let arbA = jsc.array(jsc.tuple(args));
|
||
|
return arbA.smap(
|
||
|
x => {
|
||
|
if (x.length == 0) return _.map(args, x => []);
|
||
|
return _.unzip(x);
|
||
|
},
|
||
|
y => _.zip.apply(null, y)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
function sortedArray(t) {
|
||
|
return jsc.array(t).smap(_.sortBy,_.identity);
|
||
|
}
|
||
|
|
||
|
function vect(t, n) {
|
||
|
return jsc.tuple(_.fill(Array(n), t));
|
||
|
}
|
||
|
|
||
|
function roseTree(t, max_level) {
|
||
|
if (max_level <= 0) return jsc.array(t);
|
||
|
return jsc.array(jsc.oneof(t, roseTree(t, max_level - 1)));
|
||
|
}
|
||
|
|
||
|
function charRange(min,max) {
|
||
|
return jsc.nonshrink(jsc.integer(min.charCodeAt(0), max.charCodeAt(0)).smap(
|
||
|
String.fromCharCode,
|
||
|
c => c.charCodeAt(0)));
|
||
|
}
|
||
|
|
||
|
let lLetter = charRange('a', 'z');
|
||
|
let uLetter = charRange('A', 'Z');
|
||
|
let letter = jsc.oneof(lLetter, uLetter);
|
||
|
|
||
|
let whitespace = oneOfWeight([jsc.constant(' '), 5],
|
||
|
[jsc.constant(' '), 4],
|
||
|
[jsc.constant(' '), 1],
|
||
|
[jsc.constant('\t'), 1]);
|
||
|
|
||
|
let punctuation = oneOfWeight([jsc.constant('.'), 3],
|
||
|
[jsc.constant('...'),1],
|
||
|
[jsc.constant('!'), 1],
|
||
|
[jsc.constant('?'), 1]);
|
||
|
|
||
|
function permutation(n) {
|
||
|
return jsc.tuple(_.times(n-1, i => jsc.nat(n-i-1)));
|
||
|
}
|
||
|
|
||
|
function some(arb,min,max) {
|
||
|
return jsc.bless({
|
||
|
generator: jsc.integer(min,max).generator
|
||
|
.flatmap(n => vect(arb,n).generator)
|
||
|
});
|
||
|
}
|
||
|
|
||
|
function word(l,min,max) {
|
||
|
return some(l,min,max).smap(a => a.join(''), s => s.split(''));
|
||
|
}
|
||
|
|
||
|
function sentence(w,wSp,initWSp,trailWSp,p,min,max) {
|
||
|
return jsc.bless({
|
||
|
generator: jsc.integer(min,max).generator.flatmap(
|
||
|
n => jsc.generator.tuple([vect(w,n).generator,
|
||
|
vect(wSp, n-1).generator,
|
||
|
initWSp.generator,
|
||
|
trailWSp.generator,
|
||
|
p.generator]).map(a => {
|
||
|
let [ws,wSps,iWs,tWs,p] = a;
|
||
|
wSps.push(p + tWs);
|
||
|
return iWs +
|
||
|
_.zipWith(ws, wSps, _.add).join('');
|
||
|
}))
|
||
|
});
|
||
|
}
|
||
|
|
||
|
let posInteger = jsc.nat.smap(x => x+1, x => x-1);
|
||
|
|
||
|
// jsverify doesn't have arbitrary multi-argument functions
|
||
|
// but it has curried functions and functions accepting tuples
|
||
|
// to get usual multi-argument functions we can either uncurry
|
||
|
// the generated function (in case we choose to generated
|
||
|
// curried function) or grab all functions arguments as an
|
||
|
// array and pass it to the generated function accepting tuples.
|
||
|
// Here we choose to use tuple-based version to get better counterexamples.
|
||
|
// There is a small catch: we have to ensure the right length of the tuple,
|
||
|
// otherwise f(1,2,ununsed_arg) won't be the same as f(1,2).
|
||
|
function fixManyArgFun(n,f) {
|
||
|
let fixed_f = function(...args) {
|
||
|
let as = _.take(args,n);
|
||
|
let d = n - as.length;
|
||
|
if (d) as.concat(_.fill(Array(d), undefined));
|
||
|
return f(as);
|
||
|
};
|
||
|
fixed_f.original_fun = f;
|
||
|
return fixed_f;
|
||
|
}
|
||
|
|
||
|
// arbitrary function taking n arguments
|
||
|
function fnN(n, returnArb) {
|
||
|
let funArb = jsc.fn(returnArb);
|
||
|
let oldShow = funArb.show;
|
||
|
return funArb.smap(f => fixManyArgFun(n,f),
|
||
|
f => f.original_fun,
|
||
|
f => oldShow(f.original_fun));
|
||
|
}
|
||
|
|
||
|
function fn2(returnArb) { return fnN(2, returnArb); }
|
||
|
function fn3(returnArb) { return fnN(3, returnArb); }
|
||
|
|
||
|
let anything = jsc.oneof(jsc.json, jsc.fn(jsc.json), jsc.falsy);
|
||
|
|
||
|
return {
|
||
|
set: set,
|
||
|
oneOfWeight: oneOfWeight,
|
||
|
vect: vect,
|
||
|
equalLengthArrays: equalLengthArrays,
|
||
|
sortedArray: sortedArray,
|
||
|
roseTree: roseTree,
|
||
|
charRange: charRange,
|
||
|
permutation: permutation,
|
||
|
uLetter: uLetter,
|
||
|
lLetter: lLetter,
|
||
|
letter: letter,
|
||
|
whitespace: whitespace,
|
||
|
punctuation: punctuation,
|
||
|
some: some,
|
||
|
word: word,
|
||
|
sentence: sentence,
|
||
|
posInteger: posInteger,
|
||
|
fnN : fnN,
|
||
|
fn2 : fn2,
|
||
|
fn3 : fn3,
|
||
|
anything
|
||
|
};
|
||
|
}
|