UNPKG

jsverify

Version:

Property-based testing for JavaScript.

227 lines (187 loc) 5.49 kB
/* @flow weak */ "use strict"; var assert = require("assert"); var either = require("./either.js"); var lazyseq = require("lazy-seq"); var sum = require("./sum.js"); var utils = require("./utils.js"); /** ### Shrink functions A shrink function, `shrink a`, is a function `a -> [a]`, returning an array of *smaller* values. Shrink combinators are auto-curried: ```js var xs = jsc.shrink.array(jsc.nat.shrink, [1]); // ≡ var ys = jsc.shrink.array(jsc.nat.shrink)([1]); ``` */ // Blessing: i.e adding prototype /* eslint-disable no-use-before-define */ function shrinkProtoIsoMap(f, g) { /* jshint validthis:true */ var shrink = this; // eslint-disable-line no-invalid-this return shrinkBless(function (value) { return shrink(g(value)).map(f); }); } /* eslint-enable no-use-before-define */ /** - `shrink.bless(f: a -> [a]): shrink a` Bless function with `.smap` property. - `.smap(f: a -> b, g: b -> a): shrink b` Transform `shrink a` into `shrink b`. For example: ```js positiveIntegersShrink = nat.shrink.smap( function (x) { return x + 1; }, function (x) { return x - 1; }); ``` */ function shrinkBless(shrink) { shrink.smap = shrinkProtoIsoMap; return shrink; } /** - `shrink.noop: shrink a` */ var shrinkNoop = shrinkBless(function shrinkNoop() { return []; }); /** - `shrink.pair(shrA: shrink a, shrB: shrink b): shrink (a, b)` */ function shrinkPair(shrinkA, shrinkB) { var result = shrinkBless(function (pair) { assert(pair.length === 2, "shrinkPair: pair should be an Array of length 2"); var a = pair[0]; var b = pair[1]; var shrinkedA = shrinkA(a); var shrinkedB = shrinkB(b); var pairA = shrinkedA.map(function (ap) { return [ap, b]; }); if (Array.isArray(pairA)) { pairA = lazyseq.fromArray(pairA); } return pairA.append(function () { var pairB = shrinkedB.map(function (bp) { return [a, bp]; }); return pairB; }); }); return utils.curried3(result, arguments); } /** - `shrink.either(shrA: shrink a, shrB: shrink b): shrink (either a b)` */ function shrinkEither(shrinkA, shrinkB) { function shrinkLeft(value) { return shrinkA(value).map(either.left); } function shrinkRight(value) { return shrinkB(value).map(either.right); } var result = shrinkBless(function (e) { return e.either(shrinkLeft, shrinkRight); }); return utils.curried3(result, arguments); } // We represent non-empty linked list as // singl x = [x] // cons h t = [h, t] function fromLinkedList(ll) { assert(ll.length === 1 || ll.length === 2, "linked list must be either [] or [x, linkedlist]"); if (ll.length === 1) { return [ll[0]]; } else { return [ll[0]].concat(fromLinkedList(ll[1])); } } function toLinkedList(arr) { assert(Array.isArray(arr) && arr.length > 0, "toLinkedList expects non-empty array"); if (arr.length === 1) { return [arr[0]]; } else { return [arr[0], toLinkedList(arr.slice(1))]; } } function toSingleton(x) { return [x]; } // Vec a 1 → a function fromSingleton(a) { return a[0]; } function flattenShrink(shrinksLL) { if (shrinksLL.length === 1) { return shrinksLL[0].smap(toSingleton, fromSingleton); } else { var head = shrinksLL[0]; var tail = shrinksLL[1]; return shrinkPair(head, flattenShrink(tail)); } } /** - `shrink.tuple(shrs: (shrink a, shrink b...)): shrink (a, b...)` */ function shrinkTuple(shrinks) { assert(shrinks.length > 0, "shrinkTuple needs > 0 values"); var shrinksLL = toLinkedList(shrinks); var shrink = flattenShrink(shrinksLL); var result = shrinkBless(function (tuple) { assert(tuple.length === shrinks.length, "shrinkTuple: not-matching params"); var ll = toLinkedList(tuple); return shrink(ll).map(fromLinkedList); }); return utils.curried2(result, arguments); } /** - `shrink.sum(shrs: (shrink a, shrink b...)): shrink (a | b...)` */ function shrinkSum(shrinks) { assert(shrinks.length > 0, "shrinkTuple needs > 0 values"); var result = shrinkBless(function (s) { return s.fold(function (idx, len, value) { assert(len === shrinks.length, "shrinkSum: not-matching params"); return shrinks[idx](value).map(function (shrinked) { return sum.addend(idx, len, shrinked); }); }); }); return utils.curried2(result, arguments); } function shrinkArrayWithMinimumSize(size) { function shrinkArrayImpl(shrink) { var result = shrinkBless(function (arr) { assert(Array.isArray(arr), "shrinkArrayImpl() expects array, got: " + arr); if (arr.length <= size) { return lazyseq.nil; } else { var x = arr[0]; var xs = arr.slice(1); return lazyseq.cons(xs, lazyseq.nil) .append(shrink(x).map(function (xp) { return [xp].concat(xs); })) .append(shrinkArrayImpl(shrink, xs).map(function (xsp) { return [x].concat(xsp); })); } }); return utils.curried2(result, arguments); } return shrinkArrayImpl; } /** - `shrink.array(shr: shrink a): shrink (array a)` */ var shrinkArray = shrinkArrayWithMinimumSize(0); /** - `shrink.nearray(shr: shrink a): shrink (nearray a)` */ var shrinkNEArray = shrinkArrayWithMinimumSize(1); module.exports = { noop: shrinkNoop, pair: shrinkPair, either: shrinkEither, tuple: shrinkTuple, sum: shrinkSum, array: shrinkArray, nearray: shrinkNEArray, bless: shrinkBless, };