Comparing version 0.0.1 to 0.0.2
@@ -41,4 +41,44 @@ /** | ||
## Documentation | ||
### Use with [jasmine](http://pivotal.github.io/jasmine/) 1.3.x | ||
Check [jasmineHelpers.js](speclib/jasmineHelpers.js) file. | ||
## API | ||
> _Testing shows the presence, not the absence of bugs._ | ||
> | ||
> Edsger W. Dijkstra | ||
To show that propositions hold, we need to construct proofs. | ||
There are two extremes: proof by example (unit tests) and formal (machine-checked) proof. | ||
Property-based testing is something in between. | ||
We formulate propositions, invariants or other properties we believe to hold, but | ||
only test it to hold for numerous (random generated) values. | ||
Types and function signatures are written in [Coq](http://coq.inria.fr/)/[Haskell](http://www.haskell.org/haskellwiki/Haskell) influented style: | ||
C# -style `List<T> filter(List<T> v, Func<T, bool> predicate)` is represented by | ||
`filter (v : array T) (predicate : T -> bool) : array T` in our style. | ||
`jsverify` can operate with both synchronous and asynchronous-promise properties. | ||
Generally every property can be wrapped inside [functor](http://learnyouahaskell.com/functors-applicative-functors-and-monoids), | ||
for now in either identity or promise functor, for synchronous and promise properties respectively. | ||
Some type definitions to keep developers sane: | ||
- Functor f => property (size : nat) : f result | ||
- result := true | { counterexample: any } | ||
- Functor f => property_rec := f (result | property) | ||
- generator a := { arbitrary : a, shrink : a -> [a] } | ||
*/ | ||
/** | ||
### jsc._ - miscellaneous utilities | ||
*/ | ||
/** | ||
#### assert (exp : bool) (message : string) : void | ||
Throw an error with `message` if `exp` is falsy. | ||
*/ | ||
function assert(exp, message) { | ||
@@ -50,2 +90,8 @@ if (!exp) { | ||
/** | ||
#### isPromise p : bool | ||
Optimistic duck-type check for promises. | ||
Returns `true` if p is an object with `.then` function property. | ||
*/ | ||
function isPromise(p) { | ||
@@ -55,2 +101,9 @@ return new Object(p) === p && typeof p.then === "function"; | ||
/** | ||
#### withPromise (Functor f) (p : f a) (f : a -> b) : f b | ||
This is functor map, `fmap`, with arguments flipped. | ||
Essentially `f(p)`. If `p` is promise, returns new promise. | ||
Using `withPromise` makes code look very much [CPS-style](http://en.wikipedia.org/wiki/Continuation-passing_style). | ||
*/ | ||
function withPromise(p, f) { | ||
@@ -64,2 +117,33 @@ if (isPromise(p)) { | ||
/** | ||
#### getRandomArbitrary (min max : number) : number | ||
Returns random number from `[min, max)` range. | ||
*/ | ||
function getRandomArbitrary(min, max) { | ||
return Math.random() * (max - min) + min; | ||
} | ||
/** | ||
#### getRandomInt (min max : int) : int | ||
Returns random int from `[min, max]` range inclusively. | ||
```js | ||
getRandomInt(2, 3) // either 2 or 3 | ||
``` | ||
*/ | ||
function getRandomInt(min, max) { | ||
return Math.floor(Math.random() * (max - min + 1) + min); | ||
} | ||
/** | ||
### Properties | ||
*/ | ||
/** | ||
#### forall (gen : generator a) (prop : a -> property_rec) : property | ||
Property constructor | ||
*/ | ||
function forall(generator, property) { | ||
@@ -139,2 +223,7 @@ assert(typeof property === "function", "property should be a function"); | ||
/** | ||
#### check (prop : property) : promise result + result | ||
Run random checks for given `prop`. If `prop` is promise based, result is also wrapped in promise. | ||
*/ | ||
function check(property) { | ||
@@ -165,13 +254,11 @@ var size = 5; | ||
// Random helpers | ||
/* | ||
function getRandomArbitrary(min, max) { | ||
return Math.random() * (max - min) + min; | ||
} | ||
/** | ||
### Primitive generators | ||
*/ | ||
function getRandomInt(min, max) { | ||
return Math.floor(Math.random() * (max - min + 1) + min); | ||
} | ||
// Generators | ||
/** | ||
#### integer (maxsize : nat) : generator integer | ||
Integers, ℤ | ||
*/ | ||
function integer(maxsize) { | ||
@@ -191,2 +278,3 @@ maxsize = maxsize || 1000; | ||
} else { | ||
// TODO: redo | ||
return [0, -i+1, i-1]; | ||
@@ -198,64 +286,86 @@ } | ||
function pair(a, b) { | ||
/** | ||
#### nat (maxsize : nat) : generator nat | ||
Natural numbers, ℕ (0, 1, 2...) | ||
*/ | ||
function nat(maxsize) { | ||
maxsize = maxsize || 1000; | ||
return { | ||
arbitrary: function (size) { | ||
return [a.arbitrary(size), b.arbitrary(size)]; | ||
size = Math.min(maxsize, size); | ||
return getRandomInt(0, size); | ||
}, | ||
shrink: function (i) { | ||
if (i === 0) { | ||
return []; | ||
} else { | ||
// TODO: redo | ||
return [0, Math.floor(i/2)]; | ||
} | ||
}, | ||
}; | ||
} | ||
shrink: function (p) { | ||
var x = p[0]; | ||
var y = p[1]; | ||
/** | ||
#### number (maxsize : number) : generator number | ||
return [].concat( | ||
a.shrink(x).map(function (xp) { return [xp, y]; }), | ||
b.shrink(y).map(function (yp) { return [x, yp]; }) | ||
); | ||
JavaScript numbers, "doubles", ℝ. `NaN` and `Infinity` are not included. | ||
*/ | ||
function number(maxsize) { | ||
maxsize = maxsize || 1000; | ||
return { | ||
arbitrary: function (size) { | ||
size = Math.min(maxsize, size); | ||
return getRandomArbitrary(-size, size); | ||
}, | ||
shrink: function () { return []; }, | ||
}; | ||
} | ||
function oneof(args) { | ||
assert(args.length !== 0, "oneof: at least one parameter expected"); | ||
/** | ||
#### bool () : generator bool | ||
return { | ||
arbitrary: function (size) { | ||
var i = getRandomInt(0, args.length-1); | ||
return args[i]; | ||
}, | ||
Booleans, `true` or `false`. | ||
*/ | ||
function bool() { | ||
return { | ||
arbitrary: function (size) { | ||
var i = getRandomInt(0, 1); | ||
return i === 0 ? false : true; | ||
}, | ||
shrink: function () { return []; }, | ||
}; | ||
} | ||
shrink: function (b) { | ||
return b === true ? [false] : []; | ||
}, | ||
}; | ||
} | ||
function suchthat(generator, predicate) { | ||
return { | ||
arbitrary: function (size) { | ||
while (true) { | ||
var x = generator.arbitrary(size); | ||
if (predicate(x)) { | ||
return x; | ||
} | ||
} | ||
}, | ||
/** | ||
#### oneof (args : array any) : generator any | ||
shrink: function (x) { | ||
return generator.shrink(x).filter(predicate); | ||
}, | ||
}; | ||
} | ||
Random element of `args` array. | ||
*/ | ||
function oneof(args) { | ||
assert(args.length !== 0, "oneof: at least one parameter expected"); | ||
function bool() { | ||
return { | ||
arbitrary: function (size) { | ||
var i = getRandomInt(0, 1); | ||
return i === 0 ? false : true; | ||
}, | ||
return { | ||
arbitrary: function (size) { | ||
var i = getRandomInt(0, args.length-1); | ||
return args[i]; | ||
}, | ||
shrink: function (b) { | ||
return b === true ? [false] : []; | ||
}, | ||
}; | ||
} | ||
// TODO: make shrink | ||
shrink: function () { return []; }, | ||
}; | ||
} | ||
function list(generator) { | ||
/** | ||
#### array (gen : generator a) : generator (array a) | ||
*/ | ||
function array(generator) { | ||
generator = generator || integer(); | ||
@@ -293,3 +403,3 @@ | ||
function nonshrinklist(generator) { | ||
function nonshrinkarray(generator) { | ||
generator = generator || integer(); | ||
@@ -313,2 +423,54 @@ | ||
/** | ||
### Generator combinators | ||
*/ | ||
/** | ||
#### pair (a : generator A) (b : generator B) : generator (A * B) | ||
If not specified `a` and `b` are equal to `integer()`. | ||
*/ | ||
function pair(a, b) { | ||
a = a || integer(); | ||
b = b || integer(); | ||
return { | ||
arbitrary: function (size) { | ||
return [a.arbitrary(size), b.arbitrary(size)]; | ||
}, | ||
shrink: function (p) { | ||
var x = p[0]; | ||
var y = p[1]; | ||
return [].concat( | ||
a.shrink(x).map(function (xp) { return [xp, y]; }), | ||
b.shrink(y).map(function (yp) { return [x, yp]; }) | ||
); | ||
}, | ||
}; | ||
} | ||
/** | ||
#### suchthat (gen : generator a) (p : a -> bool) : generator {a | p a == true} | ||
Generator of values that satisfy `p` predicate. It's adviced that `p`'s accept rate is high. | ||
*/ | ||
function suchthat(generator, predicate) { | ||
return { | ||
arbitrary: function (size) { | ||
while (true) { | ||
var x = generator.arbitrary(size); | ||
if (predicate(x)) { | ||
return x; | ||
} | ||
} | ||
}, | ||
shrink: function (x) { | ||
return generator.shrink(x).filter(predicate); | ||
}, | ||
}; | ||
} | ||
// Export | ||
@@ -320,9 +482,11 @@ var jsc = { | ||
// generators | ||
nat: nat, | ||
integer: integer, | ||
bool: bool, | ||
number : number, | ||
bool: bool, | ||
pair: pair, | ||
list: list, | ||
nonshrinklist: nonshrinklist, | ||
oneof: oneof, | ||
suchthat: suchthat, | ||
array: array, | ||
nonshrinkarray: nonshrinkarray, | ||
oneof: oneof, | ||
suchthat: suchthat, | ||
@@ -334,2 +498,4 @@ // internal utility lib | ||
withPromise: withPromise, | ||
getRandomInt: getRandomInt, | ||
getRandomArbitrary: getRandomArbitrary, | ||
}, | ||
@@ -360,3 +526,4 @@ }; | ||
- 0.0.0 Initial preview | ||
- 0.0.2 Documented preview | ||
- 0.0.1 Initial preview | ||
@@ -369,2 +536,4 @@ ## License | ||
### JavaScript | ||
- [JSCheck](http://www.jscheck.org/) | ||
@@ -375,2 +544,11 @@ - [claire](https://npmjs.org/package/claire) | ||
- [quickcheck](https://npmjs.org/package/quickcheck) | ||
- [qc.js](https://bitbucket.org/darrint/qc.js/) | ||
### Others | ||
- [Wikipedia - QuickCheck](http://en.wikipedia.org/wiki/QuickCheck) | ||
- [Haskell - QuickCheck](http://hackage.haskell.org/package/QuickCheck) [Introduction](http://www.haskell.org/haskellwiki/Introduction_to_QuickCheck1) | ||
- [Erlang - QuviQ](http://www.quviq.com/index.html) | ||
- [Erlang - triq](https://github.com/krestenkrab/triq) | ||
- [Scala - ScalaCheck](https://github.com/rickynils/scalacheck) | ||
*/ |
{ | ||
"name": "jsverify", | ||
"description": "Property-based testing for JavaScript.", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"homepage": "https://github.com/phadej/jsverify", | ||
@@ -6,0 +6,0 @@ "author": { |
119
README.md
@@ -38,2 +38,106 @@ | ||
### Use with [jasmine](http://pivotal.github.io/jasmine/) 1.3.x | ||
Check [jasmineHelpers.js](speclib/jasmineHelpers.js) file. | ||
## API | ||
> _Testing shows the presence, not the absence of bugs._ | ||
> | ||
> Edsger W. Dijkstra | ||
To show that propositions hold, we need to construct proofs. | ||
There are two extremes: proof by example (unit tests) and formal (machine-checked) proof. | ||
Property-based testing is something in between. | ||
We formulate propositions, invariants or other properties we believe to hold, but | ||
only test it to hold for numerous (random generated) values. | ||
Types and function signatures are written in [Coq](http://coq.inria.fr/)/[Haskell](http://www.haskell.org/haskellwiki/Haskell) influented style: | ||
C# -style `List<T> filter(List<T> v, Func<T, bool> predicate)` is represented by | ||
`filter (v : array T) (predicate : T -> bool) : array T` in our style. | ||
`jsverify` can operate with both synchronous and asynchronous-promise properties. | ||
Generally every property can be wrapped inside [functor](http://learnyouahaskell.com/functors-applicative-functors-and-monoids), | ||
for now in either identity or promise functor, for synchronous and promise properties respectively. | ||
Some type definitions to keep developers sane: | ||
- Functor f => property (size : nat) : f result | ||
- result := true | { counterexample: any } | ||
- Functor f => property_rec := f (result | property) | ||
- generator a := { arbitrary : a, shrink : a -> [a] } | ||
### jsc._ - miscellaneous utilities | ||
#### assert (exp : bool) (message : string) : void | ||
Throw an error with `message` if `exp` is falsy. | ||
#### isPromise p : bool | ||
Optimistic duck-type check for promises. | ||
Returns `true` if p is an object with `.then` function property. | ||
#### withPromise (Functor f) (p : f a) (f : a -> b) : f b | ||
This is functor map, `fmap`, with arguments flipped. | ||
Essentially `f(p)`. If `p` is promise, returns new promise. | ||
Using `withPromise` makes code look very much [CPS-style](http://en.wikipedia.org/wiki/Continuation-passing_style). | ||
#### getRandomArbitrary (min max : number) : number | ||
Returns random number from `[min, max)` range. | ||
#### getRandomInt (min max : int) : int | ||
Returns random int from `[min, max]` range inclusively. | ||
```js | ||
getRandomInt(2, 3) // either 2 or 3 | ||
``` | ||
### Properties | ||
#### forall (gen : generator a) (prop : a -> property_rec) : property | ||
Property constructor | ||
#### check (prop : property) : promise result + result | ||
Run random checks for given `prop`. If `prop` is promise based, result is also wrapped in promise. | ||
### Primitive generators | ||
#### integer (maxsize : nat) : generator integer | ||
Integers, ℤ | ||
#### nat (maxsize : nat) : generator nat | ||
Natural numbers, ℕ (0, 1, 2...) | ||
#### number (maxsize : number) : generator number | ||
JavaScript numbers, "doubles", ℝ. `NaN` and `Infinity` are not included. | ||
#### bool () : generator bool | ||
Booleans, `true` or `false`. | ||
#### oneof (args : array any) : generator any | ||
Random element of `args` array. | ||
#### array (gen : generator a) : generator (array a) | ||
### Generator combinators | ||
#### pair (a : generator A) (b : generator B) : generator (A * B) | ||
If not specified `a` and `b` are equal to `integer()`. | ||
#### suchthat (gen : generator a) (p : a -> bool) : generator {a | p a == true} | ||
Generator of values that satisfy `p` predicate. It's adviced that `p`'s accept rate is high. | ||
## Contributing | ||
@@ -44,2 +148,3 @@ | ||
- You can use `grunt jasmine-build` to generate `_SpecRunner.html` to run tests in your browser of choice. | ||
- Use tabs for indentation | ||
@@ -52,3 +157,4 @@ ### Preparing for release | ||
- 0.0.0 Initial preview | ||
- 0.0.2 Documented preview | ||
- 0.0.1 Initial preview | ||
@@ -61,2 +167,4 @@ ## License | ||
### JavaScript | ||
- [JSCheck](http://www.jscheck.org/) | ||
@@ -67,1 +175,10 @@ - [claire](https://npmjs.org/package/claire) | ||
- [quickcheck](https://npmjs.org/package/quickcheck) | ||
- [qc.js](https://bitbucket.org/darrint/qc.js/) | ||
### Others | ||
- [Wikipedia - QuickCheck](http://en.wikipedia.org/wiki/QuickCheck) | ||
- [Haskell - QuickCheck](http://hackage.haskell.org/package/QuickCheck) [Introduction](http://www.haskell.org/haskellwiki/Introduction_to_QuickCheck1) | ||
- [Erlang - QuviQ](http://www.quviq.com/index.html) | ||
- [Erlang - triq](https://github.com/krestenkrab/triq) | ||
- [Scala - ScalaCheck](https://github.com/rickynils/scalacheck) |
@@ -1,25 +0,5 @@ | ||
/* global jsc, _, describe, it, expect, beforeEach */ | ||
/* global jsc, _, Q, describe, it, expect, waitsFor, runs */ | ||
(function () { | ||
"use strict"; | ||
beforeEach(function () { | ||
this.addMatchers({ | ||
// Expects that property is synchronous | ||
toHold: function () { | ||
var actual = this.actual; | ||
var notText = this.isNot ? " not" : ""; | ||
var r = jsc.check(actual); | ||
var counterExampleText = r === true ? "" : "Counter example found: " + JSON.stringify(r.counterexample); | ||
this.message = function() { | ||
return "Expected property to " + notText + " to not hold." + counterExampleText; | ||
}; | ||
return r === true; | ||
}, | ||
}); | ||
}); | ||
describe("examples", function () { | ||
@@ -50,2 +30,27 @@ it("failing inc", function () { | ||
it("fixed inc - promise", function () { | ||
function inc(i) { | ||
return i + 1; | ||
} | ||
var propPromise = Q.delay(100).then(function () { | ||
return jsc.forall(jsc.integer(), function (i) { | ||
return inc(i) === i + 1; | ||
}); | ||
}); | ||
// TODO: how to make matcher on promise? | ||
var done = false; | ||
propPromise.fin(function () { done = true; }); | ||
waitsFor(function () { return done; }); | ||
runs(function () { | ||
propPromise.then(function (prop) { | ||
expect(prop).toHold(); | ||
}, function (e) { | ||
expect(false).toBe(true); // should be never executed | ||
}); | ||
}); | ||
}); | ||
it("failing add", function () { | ||
@@ -67,3 +72,3 @@ function add(i, j) { | ||
function add(i, j) { | ||
return i + (j && 1); | ||
return i + (j && 1); | ||
} | ||
@@ -121,4 +126,4 @@ | ||
var prop = jsc.forall(jsc.nonshrinklist(), function (a) { | ||
return jsc.forall(jsc.nonshrinklist(), function (b) { | ||
var prop = jsc.forall(jsc.nonshrinkarray(), function (a) { | ||
return jsc.forall(jsc.nonshrinkarray(), function (b) { | ||
return intersects(a, b) === (_.intersection(a, b) !== []); | ||
@@ -130,4 +135,4 @@ }); | ||
var prop2 = jsc.forall(jsc.list(), function (a) { | ||
return jsc.forall(jsc.list(), function (b) { | ||
var prop2 = jsc.forall(jsc.array(), function (a) { | ||
return jsc.forall(jsc.array(), function (b) { | ||
return intersects(a, b) === (_.intersection(a, b) !== []); | ||
@@ -139,4 +144,4 @@ }); | ||
var prop3 = jsc.forall(jsc.list(), function (a) { | ||
return jsc.forall(jsc.list(), function (b) { | ||
var prop3 = jsc.forall(jsc.array(), function (a) { | ||
return jsc.forall(jsc.array(), function (b) { | ||
return intersects(a, b) === (_.intersection(a, b).length !== 0); | ||
@@ -149,4 +154,4 @@ }); | ||
/* | ||
var prop4 = jsc.forall(jsc.list(), function (a) { | ||
return jsc.forall(jsc.list(), function (b) { | ||
var prop4 = jsc.forall(jsc.array(), function (a) { | ||
return jsc.forall(jsc.array(), function (b) { | ||
return q.delay(10).then(function () { | ||
@@ -165,6 +170,2 @@ return intersects(a, b) === (_.intersection(a, b).length !== 0); | ||
it("booleans", function () { | ||
var true_and_right_prop = jsc.forall(jsc.bool(), function (x) { | ||
return x && true === x; | ||
}); | ||
var true_and_left_prop = jsc.forall(jsc.bool(), function (x) { | ||
@@ -174,5 +175,11 @@ return true && x === x; | ||
expect(true_and_right_prop).not.toHold(); // be careful! | ||
expect(true_and_left_prop).toHold(); | ||
var true_and_right_prop = jsc.forall(jsc.bool(), function (x) { | ||
return x && true === x; | ||
}); | ||
expect(true_and_right_prop).not.toHold(); // be careful! | ||
var true_and_right_fixed_prop = jsc.forall(jsc.bool(), function (x) { | ||
@@ -222,7 +229,7 @@ return (x && true) === x; | ||
it("array indexOf", function () { | ||
var nonemptylist = jsc.suchthat(jsc.list(), function (l) { | ||
var nonemptyarray = jsc.suchthat(jsc.array(), function (l) { | ||
return l.length !== 0; | ||
}); | ||
var prop = jsc.forall(nonemptylist, function (l) { | ||
var prop = jsc.forall(nonemptyarray, function (l) { | ||
return jsc.forall(jsc.oneof(l), function (x) { | ||
@@ -235,3 +242,47 @@ return l.indexOf(x) !== -1; | ||
}); | ||
it("numbers", function () { | ||
var nat_nonnegative_prop = jsc.forall(jsc.nat(), function (n) { | ||
return n >= 0; | ||
}); | ||
expect(nat_nonnegative_prop).toHold(); | ||
var integer_round_noop_property = jsc.forall(jsc.integer(), function (n) { | ||
return Math.round(n) === n; | ||
}); | ||
expect(integer_round_noop_property).toHold(); | ||
var number_round_noop_property = jsc.forall(jsc.number(), function (n) { | ||
return Math.round(n) === n; | ||
}); | ||
expect(number_round_noop_property).not.toHold(); | ||
}); | ||
it("_.sortBy idempotent", function () { | ||
var prop1 = jsc.forall(jsc.array(), function (l) { | ||
return _.isEqual(_.sortBy(l), l); | ||
}); | ||
expect(prop1).toHold(); | ||
function sort(l) { | ||
return _.sortBy(l, _.identity); | ||
} | ||
var prop2 = jsc.forall(jsc.array(), function (l) { | ||
return _.isEqual(sort(l), l); | ||
}); | ||
expect(prop2).not.toHold(); | ||
var prop3 = jsc.forall(jsc.array(), function (l) { | ||
return _.isEqual(sort(sort(l)), sort(l)); | ||
}); | ||
expect(prop3).toHold(); | ||
}); | ||
}); | ||
}()); |
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
134343
13
3691
180
1