UNPKG

17 kBJavaScriptView Raw
1/* @flow weak */
2/**
3 # JSVerify
4
5 <img src="https://raw.githubusercontent.com/jsverify/jsverify/master/jsverify-300.png" align="right" height="100" />
6
7 > Property-based checking. Like QuickCheck.
8
9 [![Build Status](https://secure.travis-ci.org/jsverify/jsverify.svg?branch=master)](http://travis-ci.org/jsverify/jsverify)
10 [![NPM version](https://badge.fury.io/js/jsverify.svg)](http://badge.fury.io/js/jsverify)
11 [![Dependency Status](https://david-dm.org/jsverify/jsverify.svg)](https://david-dm.org/jsverify/jsverify)
12 [![devDependency Status](https://david-dm.org/jsverify/jsverify/dev-status.svg)](https://david-dm.org/jsverify/jsverify#info=devDependencies)
13 [![Code Climate](https://img.shields.io/codeclimate/github/jsverify/jsverify.svg)](https://codeclimate.com/github/jsverify/jsverify)
14
15 ## Getting Started
16
17 Install the module with: `npm install jsverify`
18
19 ## Synopsis
20
21 ```js
22 var jsc = require("jsverify");
23
24 // forall (f : bool -> bool) (b : bool), f (f (f b)) = f(b).
25 var boolFnAppliedThrice =
26 jsc.forall("bool -> bool", "bool", function (f, b) {
27 return f(f(f(b))) === f(b);
28 });
29
30 jsc.assert(boolFnAppliedThrice);
31 // OK, passed 100 tests
32 ```
33*/
34"use strict";
35
36/**
37 ## Documentation
38
39 ### Usage with [mocha](http://mochajs.org/)
40
41 Using jsverify with mocha is easy, just define the properties and use `jsverify.assert`.
42
43 Starting from version 0.4.3 you can write your specs without any boilerplate:
44
45 ```js
46 describe("sort", function () {
47 jsc.property("idempotent", "array nat", function (arr) {
48 return _.isEqual(sort(sort(arr)), sort(arr));
49 });
50 });
51 ```
52
53 Starting from version 0.8.0 you can write the specs in TypeScript. There are
54 typings provided. The drawback is that you cannot use type DSL:
55
56 ```typescript
57 describe("basic jsverify usage", () => {
58 jsc.property("(b && b) === b", jsc.bool, b => (b && b) === b);
59
60 jsc.property("boolean fn thrice", jsc.fn(jsc.bool), jsc.bool, (f, b) =>
61 f(f(f(b))) === f(b)
62 );
63 });
64 ```
65
66 You can also provide `--jsverifyRngState state` command line argument, to run tests with particular random generator state.
67
68 ```
69 $ mocha examples/nat.js
70
71 1) natural numbers are less than 90:
72 Error: Failed after 49 tests and 1 shrinks. rngState: 074e9b5f037a8c21d6; Counterexample: 90;
73
74 $ mocha examples/nat.js --grep 'are less than' --jsverifyRngState 074e9b5f037a8c21d6
75
76 1) natural numbers are less than 90:
77 Error: Failed after 1 tests and 1 shrinks. rngState: 074e9b5f037a8c21d6; Counterexample: 90;
78 ```
79
80 Erroneous case is found with first try.
81
82 ### Usage with [jasmine](https://jasmine.github.io/)
83
84 Check [jasmineHelpers.js](helpers/jasmineHelpers.js) and [jasmineHelpers2.js](helpers/jasmineHelpers2.js) for jasmine 1.3 and 2.0 respectively.
85
86 ## API Reference
87
88 > _Testing shows the presence, not the absence of bugs._
89 >
90 > Edsger W. Dijkstra
91
92 To show that propositions hold, we need to construct proofs.
93 There are two extremes: proof by example (unit tests) and formal (machine-checked) proof.
94 Property-based testing is somewhere in between.
95 We formulate propositions, invariants or other properties we believe to hold, but
96 only test it to hold for numerous (randomly generated) values.
97
98 Types and function signatures are written in [Coq](http://coq.inria.fr/)/[Haskell](http://www.haskell.org/haskellwiki/Haskell)-influenced style:
99 C# -style `List<T> filter(List<T> v, Func<T, bool> predicate)` is represented by
100 `filter(v: array T, predicate: T -> bool): array T` in our style.
101
102 Methods and objects live in `jsc` object, e.g. `shrink.bless` method is used by
103 ```js
104 var jsc = require("jsverify");
105 var foo = jsc.shrink.bless(...);
106 ```
107
108 Methods starting with `.dot` are prototype methods:
109 ```js
110 var arb = jsc.nat;
111 var arb2 = jsc.nat.smap(f, g);
112 ```
113
114 `jsverify` can operate with both synchronous and asynchronous-promise properties.
115 Generally every property can be wrapped inside [functor](http://learnyouahaskell.com/functors-applicative-functors-and-monoids),
116 for now in either identity or promise functor, for synchronous and promise properties respectively.
117*/
118
119var assert = require("assert");
120var lazyseq = require("lazy-seq");
121
122var api = require("./api.js");
123var either = require("./either.js");
124var environment = require("./environment.js");
125var FMap = require("./finitemap.js");
126var fn = require("./fn.js");
127var functor = require("./functor.js");
128var random = require("./random.js");
129var show = require("./show.js");
130var shrink = require("./shrink.js");
131var suchthat = require("./suchthat.js");
132var sum = require("./sum.js");
133var typify = require("./typify.js");
134var utils = require("./utils.js");
135
136/**
137 ### Properties
138*/
139
140function shrinkResult(arbs, x, test, size, shrinksN, exc, transform) {
141 assert(arbs.length === x.length, "shrinkResult: arbs and x has to be of same size");
142 assert(typeof size === "number", "shrinkResult: size should be number");
143 assert(typeof shrinksN === "number", "shrinkResult: shrinkN should be number");
144
145 var shrinks = utils.pluck(arbs, "shrink");
146 var shows = utils.pluck(arbs, "show");
147
148 var shrinked = shrink.tuple(shrinks, x);
149
150 var shrinkP = lazyseq.fold(shrinked, true, function (y, rest) {
151 var t = test(size, y, shrinksN + 1);
152 return functor.map(t, function (tprime) {
153 return tprime !== true ? tprime : rest();
154 });
155 });
156
157 return functor.map(shrinkP, function (shrinkPPrime) {
158 if (shrinkPPrime === true) {
159 var res = {
160 counterexample: x,
161 counterexamplestr: show.tuple(shows, x),
162 shrinks: shrinksN,
163 exc: exc,
164 };
165 return transform(res);
166 } else {
167 return shrinkPPrime;
168 }
169 });
170}
171
172function isArbitrary(arb) {
173 return (typeof arb === "object" || typeof arb === "function") &&
174 typeof arb.generator === "function" &&
175 typeof arb.shrink === "function" &&
176 typeof arb.show === "function";
177}
178
179/**
180 - `forall(arbs: arbitrary a ..., userenv: (map arbitrary)?, prop : a -> property): property`
181
182 Property constructor
183*/
184function forall() {
185 var args = Array.prototype.slice.call(arguments);
186 var gens = args.slice(0, -1);
187 var property = args[args.length - 1];
188 var env;
189
190 var lastgen = gens[gens.length - 1];
191
192 if (!isArbitrary(lastgen) && typeof lastgen !== "string") {
193 env = utils.merge(environment, lastgen);
194 gens = gens.slice(0, -1);
195 } else {
196 env = environment;
197 }
198
199 assert(gens.length > 0, "forall requires at least single generator");
200
201 // Map typify-dsl to hard generators
202 gens = gens.map(function (g) {
203 g = typeof g === "string" ? typify.parseTypify(env, g) : g;
204 return utils.force(g);
205 });
206
207 assert(typeof property === "function", "property should be a function");
208
209 function test(size, x, shrinks) {
210 assert(Array.isArray(x), "generators results should be always tuple");
211
212 return functor.bind(property, x, function (r, exc) {
213 if (r === true) {
214 return true;
215 } else if (typeof r === "function") {
216 var rRec = r(size);
217
218 return functor.map(rRec, function (rRecPrime) {
219 if (rRecPrime === true) {
220 return true;
221 } else {
222 return shrinkResult(gens, x, test, size, shrinks, exc, function (rr) {
223 return {
224 counterexample: rr.counterexample.concat(rRecPrime.counterexample),
225 counterexamplestr: rr.counterexamplestr, // + "; " + rRec.counterexamplestr,
226 shrinks: rr.shrinks,
227 exc: rr.exc || rRecPrime.exc,
228 };
229 });
230 }
231 });
232 } else {
233 return shrinkResult(gens, x, test, size, shrinks, exc || r, utils.identity);
234 }
235 });
236 }
237
238 return function (size) {
239 var x = gens.map(function (arb) { return arb.generator(size); });
240 var r = test(size, x, 0);
241 return r;
242 };
243}
244
245function formatFailedCase(r, state, includeStack) {
246 var msg = "Failed after " + r.tests + " tests and " + r.shrinks + " shrinks. ";
247 msg += "rngState: " + (r.rngState || state) + "; ";
248 msg += "Counterexample: " + r.counterexamplestr + "; ";
249 if (r.exc) {
250 if (r.exc instanceof Error) {
251 msg += "Exception: " + r.exc.message;
252 if (includeStack) {
253 msg += "\nStack trace: " + r.exc.stack;
254 }
255 } else {
256 msg += "Error: " + r.exc;
257 }
258 }
259 return msg;
260}
261
262function findRngState(argv) { // eslint-disable-line consistent-return
263 for (var i = 0; i < argv.length - 1; i++) {
264 if (argv[i] === "--jsverifyRngState") {
265 return argv[i + 1];
266 }
267 }
268}
269
270/**
271 - `check (prop: property, opts: checkoptions?): result`
272
273 Run random checks for given `prop`. If `prop` is promise based, result is also wrapped in promise.
274
275 Options:
276 - `opts.tests` - test count to run, default 100
277 - `opts.size` - maximum size of generated values, default 50
278 - `opts.quiet` - do not `console.log`
279 - `opts.rngState` - state string for the rng
280
281 The `result` is `true` if check succeeds, otherwise it's an object with various fields:
282 - `counterexample` - an input for which property fails.
283 - `tests` - number of tests run before failing case is found
284 - `shrinks` - number of shrinks performed
285 - `exc` - an optional exception thrown by property function
286 - `rngState` - random number generator's state before execution of the property
287*/
288function check(property, opts) {
289 opts = opts || {};
290 opts.size = opts.size || 50;
291 opts.tests = opts.tests || 100;
292 opts.quiet = opts.quiet || false;
293
294 assert(typeof property === "function", "property should be a function");
295
296 var state;
297
298 if (opts.rngState) {
299 random.setStateString(opts.rngState);
300 } else if (typeof process !== "undefined") {
301 var argvState = findRngState(process.argv);
302 if (argvState) {
303 random.setStateString(argvState);
304 }
305 }
306
307 function loop(i) {
308 state = random.currentStateString();
309 if (i > opts.tests) {
310 return true;
311 }
312
313 var size = random(0, opts.size);
314
315 // wrap non-promises in trampoline
316 var r = functor.pure(property(size));
317
318 return functor.map(r, function (rPrime) {
319 if (rPrime === true) {
320 return loop(i + 1);
321 } else {
322 rPrime.tests = i;
323 /* global console */
324 if (!opts.quiet) {
325 console.error(formatFailedCase(rPrime, state, true), rPrime.counterexample);
326 }
327 return rPrime;
328 }
329 });
330 }
331
332 return functor.run(functor.map(loop(1), function (r) {
333 if (r === true) {
334 if (!opts.quiet) { console.info("OK, passed " + opts.tests + " tests"); }
335 } else {
336 r.rngState = state;
337 }
338
339 return r;
340 }));
341}
342
343/**
344 - `assert(prop: property, opts: checkoptions?) : void`
345
346 Same as `check`, but throw exception if property doesn't hold.
347*/
348function checkThrow(property, opts) {
349 opts = opts || {};
350 if (opts.quiet === undefined) {
351 opts.quiet = true;
352 }
353
354 return functor.run(functor.map(check(property, opts), function (r) {
355 if (r !== true) {
356 if (r.exc instanceof Error) {
357 r.exc.message = formatFailedCase(r);
358 throw r.exc;
359 } else {
360 throw new Error(formatFailedCase(r));
361 }
362 }
363 }));
364}
365
366/**
367 - `property(name: string, ...)`
368
369 Assuming there is globally defined `it`, the same as:
370
371 ```js
372 it(name, function () {
373 jsc.assert(jsc.forall(...));
374 }
375 ```
376
377 You can use `property` to write facts too:
378 ```js
379 jsc.property("+0 === -0", function () {
380 return +0 === -0;
381 });
382 ```
383*/
384function bddProperty(name) {
385 /* global it: true */
386 var args = Array.prototype.slice.call(arguments, 1);
387 if (args.length === 1) {
388 it(name, function () {
389 return functor.run(functor.map(args[0](), function (result) { // eslint-disable-line consistent-return
390 if (typeof result === "function") {
391 return checkThrow(result);
392 } else if (result !== true) {
393 throw new Error(name + " doesn't hold");
394 }
395 }));
396 });
397 } else {
398 var prop = forall.apply(undefined, args);
399 it(name, function () {
400 return checkThrow(prop);
401 });
402 }
403 /* global it: false */
404}
405
406/**
407 - `compile(desc: string, env: typeEnv?): arbitrary a`
408
409 Compile the type description in provided type environment, or default one.
410*/
411function compile(str, env) {
412 env = env ? utils.merge(environment, env) : environment;
413 return typify.parseTypify(env, str);
414}
415
416/**
417 - `sampler(arb: arbitrary a, genSize: nat = 10): (sampleSize: nat?) -> a`
418
419 Create a sampler for a given arbitrary with an optional size. Handy when used in
420 a REPL:
421 ```
422 > jsc = require('jsverify') // or require('./lib/jsverify') w/in the project
423 ...
424 > jsonSampler = jsc.sampler(jsc.json, 4)
425 [Function]
426 > jsonSampler()
427 0.08467432763427496
428 > jsonSampler()
429 [ [ [] ] ]
430 > jsonSampler()
431 ''
432 > sampledJson(2)
433 [-0.4199344692751765, false]
434 ```
435*/
436function sampler(arb, size) {
437 size = typeof size === "number" ? Math.abs(size) : 10;
438 return function (count) {
439 if (typeof count === "number") {
440 var acc = [];
441 count = Math.abs(count);
442 for (var i = 0; i < count; i++) {
443 acc.push(arb.generator(size));
444 }
445 return acc;
446 } else {
447 return arb.generator(size);
448 }
449 };
450}
451
452/**
453 - `throws(block: () -> a, error: class?, message: string?): bool`
454
455 Executes nullary function `block`. Returns `true` if `block` throws. See [assert.throws](https://nodejs.org/api/assert.html#assert_assert_throws_block_error_message)
456*/
457function throws(block, error, message) {
458 assert(error === undefined || typeof error === "function", "throws: error parameter must be a constructor");
459 assert(message === undefined || typeof message === "string", "throws: message parameter must be a string");
460
461 try {
462 block();
463 return false;
464 } catch (e) {
465 if (error !== undefined) {
466 if (e instanceof error) {
467 return message === undefined || e.message === message;
468 } else {
469 return false;
470 }
471 } else {
472 return true;
473 }
474 }
475}
476
477/**
478 - `assertForall(arbs: arbitrary a ..., userenv: (map arbitrary)?, prop : a -> property): void`
479
480 Combines 'assert' and 'forall'.
481 Constructs a property with forall from arguments, then throws an exception if the property doesn't hold.
482 Options for 'assert' cannot be set here - use assert(forall(...)) if you need that.
483*/
484function assertForall() {
485 return checkThrow(forall.apply(null, arguments));
486}
487
488/**
489 - `checkForall(arbs: arbitrary a ..., userenv: (map arbitrary)?, prop : a -> property): result`
490
491 Combines 'check' and 'forall'.
492 Constructs a property with forall from arguments, and returns a value based on if the property holds or not.
493 See 'check' for description of return value.
494
495 Options for 'check' cannot be set here - use check(forall(...)) if you need that.
496*/
497function checkForall() {
498 return check(forall.apply(null, arguments));
499}
500
501/**
502 ### Types
503
504 - `generator a` is a function `(size: nat) -> a`.
505 - `show` is a function `a -> string`.
506 - `shrink` is a function `a -> [a]`, returning *smaller* values.
507 - `arbitrary a` is a triple of generator, shrink and show functions.
508 - `{ generator: nat -> a, shrink : a -> array a, show: a -> string }`
509
510 ### Blessing
511
512 We chose to represent generators and shrinks by functions, yet we would
513 like to have additional methods on them. Thus we *bless* objects with
514 additional properties.
515
516 Usually you don't need to bless anything explicitly, as all combinators
517 return blessed values.
518
519 See [perldoc for bless](http://perldoc.perl.org/functions/bless.html).
520*/
521
522/// include ./typify.js
523/// include ./arbitraryBless.js
524/// include ./bless.js
525/// include ./primitive.js
526/// include ./arbitrary.js
527/// include ./recordWithEnv.js
528/// include ./record.js
529/// include ./string.js
530/// include ./fn.js
531/// include ./small.js
532/// include ./generator.js
533/// include ./shrink.js
534/// include ./show.js
535/// include ./random.js
536/// include ./either.js
537/// include ./utils.js
538
539// Export
540var jsc = {
541 forall: forall,
542 check: check,
543 assert: checkThrow,
544 assertForall: assertForall,
545 checkForall: checkForall,
546 property: bddProperty,
547 sampler: sampler,
548 throws: throws,
549
550 // generators
551 fn: fn.fn,
552 fun: fn.fn,
553 suchthat: suchthat.suchthat,
554
555 // either
556 left: either.left,
557 right: either.right,
558
559 // sum
560 addend: sum.addend,
561
562 // compile
563 compile: compile,
564
565 generator: api.generator,
566 shrink: api.shrink,
567
568 // internal utility lib
569 random: random,
570
571 show: show,
572 utils: utils,
573 _: {
574 FMap: FMap,
575 },
576};
577
578/* primitives */
579/* eslint-disable guard-for-in */
580for (var k in api.arbitrary) {
581 jsc[k] = api.arbitrary[k];
582}
583/* eslint-enable guard-for-in */
584
585module.exports = jsc;
586
587/// plain ../FAQ.md
588/// plain ../CONTRIBUTING.md
589/// plain ../CHANGELOG.md
590/// plain ../related-work.md
591/// plain ../LICENSE