Comparing version 0.0.2 to 0.1.0
@@ -11,6 +11,34 @@ /* jshint node: true */ | ||
options: { | ||
specs: 'spec/*Spec.js', | ||
helpers: 'speclib/*.js' | ||
} | ||
} | ||
specs: 'spec/jsverify/*Spec.js', | ||
helpers: 'helpers/*.js' | ||
}, | ||
}, | ||
q: { | ||
src: [ 'lib/**/*.js', 'dep/q.js' ], | ||
options: { | ||
specs: 'spec/q/*Spec.js', | ||
helpers: 'helpers/*.js' | ||
}, | ||
}, | ||
underscore: { | ||
src: [ 'lib/**/*.js', 'dep/underscore.js' ], | ||
options: { | ||
specs: 'spec/underscore/*Spec.js', | ||
helpers: 'helpers/*.js' | ||
}, | ||
}, | ||
lodash: { | ||
src: [ 'lib/**/*.js', 'dep/lodash.underscore.js' ], | ||
options: { | ||
specs: 'spec/underscore/*Spec.js', | ||
helpers: 'helpers/*.js' | ||
}, | ||
}, | ||
all: { | ||
src: [ 'lib/**/*.js', 'dep/underscore.js', 'dep/q.js' ], | ||
options: { | ||
specs: 'spec/**/*Spec.js', | ||
helpers: 'helpers/*.js' | ||
}, | ||
}, | ||
}, | ||
@@ -53,4 +81,4 @@ jshint: { | ||
grunt.registerTask('default', ['jshint']); | ||
grunt.registerTask('test', ['jshint', 'jasmine']); | ||
grunt.registerTask('jasmine-build', ['jasmine:jsverify:build']); | ||
grunt.registerTask('test', ['jshint', 'jasmine:all', 'jasmine:lodash']); | ||
grunt.registerTask('jasmine-build', ['jasmine:all:build']); | ||
@@ -57,0 +85,0 @@ // use esprima to generate README.md from source |
@@ -13,24 +13,12 @@ /** | ||
var jsc = require("jsverify"); | ||
``` | ||
Example output of `node example.js`: | ||
// forall (f : bool -> bool) (b : bool), f (f (f b)) = f(b). | ||
var bool_fn_applied_thrice = | ||
jsc.forall(jsc.fun(jsc.bool()), jsc.bool(), function (f, b) { | ||
return f(f(f(b))) === f(b); | ||
}); | ||
jsc.check(bool_fn_applied_thrice); | ||
// OK, passed 100 tests | ||
``` | ||
Propery doesn't hold, counterexample with undefined | ||
inc failing: false | ||
inc fixed: true | ||
Propery doesn't hold, counterexample with [ 0 ] | ||
add failing: { counterexample: [ 0 ] } | ||
add fixed: true | ||
Propery doesn't hold, counterexample with [ 0, -1 ] | ||
add3 failing: { counterexample: [ 0, -1 ] } | ||
intersects([1, 2], [1, 3]) true | ||
intersects([1, 2], [3, 4]) false | ||
Propery doesn't hold, counterexample with [ [] ] | ||
intersects try 1: { counterexample: [ [] ] } | ||
Propery doesn't hold, counterexample with [ [] ] | ||
intersects try 2: { counterexample: [ [] ] } | ||
intersects try 3: true | ||
intersects try 4: true | ||
``` | ||
*/ | ||
@@ -91,2 +79,95 @@ | ||
/** | ||
#### id (x : any) : any | ||
Identity function. | ||
*/ | ||
function id(x) { | ||
return x; | ||
} | ||
var isArray = Array.isArray; | ||
function isObject(o) { | ||
return new Object(o) === o; | ||
} | ||
/** | ||
#### isEqual (a b : value) : bool | ||
Equality test for `value` objects. See `value` generator. | ||
*/ | ||
function isEqual(a, b) { | ||
var i; | ||
if (a === b) { | ||
return true; | ||
} else if (isArray(a) && isArray(b) && a.length === b.length) { | ||
for (i = 0; i < a.length; i++) { | ||
if (!isEqual(a[i], b[i])) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} else if (isObject(a) && isObject(b) && !isArray(a) && !isArray(b)) { | ||
var akeys = Object.keys(a); | ||
var bkeys = Object.keys(b); | ||
if (!isEqual(akeys, bkeys)) { | ||
return false; | ||
} | ||
for (i = 0; i < akeys.length; i++) { | ||
if (!isEqual(a[akeys[i]], b[akeys[i]])) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
return false; | ||
} | ||
/** | ||
#### FMap (eq : a -> a -> bool) : FMap a | ||
Finite map, with any object a key. | ||
Short summary of member functions: | ||
- FMap.insert (key : a) (value : any) : void | ||
- FMap.get (key : a) : any | ||
- FMap.contains (key : a) : obool | ||
*/ | ||
function FMap(eq) { | ||
this.eq = eq || isEqual; | ||
this.data = []; | ||
} | ||
FMap.prototype.contains = function FMap_contains(key) { | ||
for (var i = 0; i < this.data.length; i++) { | ||
if (this.eq(this.data[i][0], key)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
}; | ||
FMap.prototype.insert = function FMap_insert(key, value) { | ||
for (var i = 0; i < this.data.length; i++) { | ||
if (this.eq(this.data[i][0], key)) { | ||
this.data[i] = [key, value]; | ||
return; | ||
} | ||
} | ||
this.data.push([key, value]); | ||
}; | ||
FMap.prototype.get = function FMap_get(key) { | ||
for (var i = 0; i < this.data.length; i++) { | ||
if (this.eq(this.data[i][0], key)) { | ||
return this.data[i][1]; | ||
} | ||
} | ||
}; | ||
/** | ||
#### isPromise p : bool | ||
@@ -142,8 +223,29 @@ | ||
function shrinkTuple(generators, tuple) { | ||
assert(generators.length === tuple.length, "there should be as much generators as values in the tuple"); | ||
var shrinked = new Array(tuple.length); | ||
for (var i = 0; i < tuple.length; i++) { | ||
/* jshint -W083 */ | ||
shrinked[i] = generators[i].shrink(tuple[i]).map(function (x) { | ||
var c = tuple.slice(); // clone array | ||
c[i] = x; | ||
return c; | ||
}); | ||
/* jshint +W083 */ | ||
} | ||
return Array.prototype.concat.apply([], shrinked); | ||
} | ||
/** | ||
#### forall (gen : generator a) (prop : a -> property_rec) : property | ||
#### forall (gens : generator a ...) (prop : a -> property_rec) : property | ||
Property constructor | ||
*/ | ||
function forall(generator, property) { | ||
function forall() { | ||
var gens = Array.prototype.slice.call(arguments, 0, -1); | ||
var property = arguments[arguments.length - 1]; | ||
assert(typeof property === "function", "property should be a function"); | ||
@@ -154,3 +256,3 @@ | ||
var r = property(x); | ||
var r = property.apply(undefined, x); | ||
return withPromise(r, function(r) { | ||
@@ -164,3 +266,3 @@ if (r === true) { return true; } | ||
} else { | ||
var shrinked = generator.shrink(x); | ||
var shrinked = shrinkTuple(gens, x); | ||
@@ -195,3 +297,3 @@ var shrinkP = shrinked.reduce(function (res, y) { | ||
// TODO: copypaste, cleanup | ||
var shrinked = generator.shrink(x); | ||
var shrinked = shrinkTuple(gens, x); | ||
@@ -219,3 +321,3 @@ var shrinkP = shrinked.reduce(function (res, y) { | ||
return function (size) { | ||
var x = generator.arbitrary(size); | ||
var x = gens.map(function (gen) { return gen.arbitrary(size); }); | ||
var r = test(size, x); | ||
@@ -227,8 +329,16 @@ return r; | ||
/** | ||
#### check (prop : property) : promise result + result | ||
#### check (prop : property) (opts : checkoptions) : promise result + result | ||
Run random checks for given `prop`. If `prop` is promise based, result is also wrapped in promise. | ||
Options: | ||
- `opts.tests` - test count to run, default 100 | ||
- `opts.size` - maximum size of generated values, default 5 | ||
- `opts.quiet` - do not `console.log` | ||
*/ | ||
function check(property) { | ||
var size = 5; | ||
function check(property, opts) { | ||
opts = opts || {}; | ||
opts.size = opts.size || 5; | ||
opts.tests = opts.tests || 100; | ||
opts.quiet = opts.quiet || false; | ||
@@ -238,13 +348,18 @@ assert(typeof property === "function", "property should be a function"); | ||
function loop(i) { | ||
if (i === 0) { | ||
if (i > opts.tests) { | ||
return true; | ||
} | ||
var size = i % (opts.size + 1); | ||
var r = property(size); | ||
return withPromise(r, function (r) { | ||
if (r === true) { | ||
return loop(i-1); | ||
return loop(i + 1); | ||
} else { | ||
/* global console */ | ||
console.error("Propery doesn't hold, counterexample with", r.counterexample); | ||
if (!opts.quiet) { | ||
console.error("Failed after " + i + " tests"); | ||
console.error("Propery doesn't hold, counterexample with", r.counterexample); | ||
} | ||
return r; | ||
@@ -255,3 +370,8 @@ } | ||
return loop(100); | ||
return withPromise(loop(1), function (r) { | ||
if (r === true) { | ||
if (!opts.quiet) { console.info("OK, passed " + opts.tests + " tests"); } | ||
} | ||
return r; | ||
}); | ||
} | ||
@@ -263,2 +383,6 @@ | ||
function shrinkNoop() { | ||
return []; | ||
} | ||
/** | ||
@@ -329,3 +453,3 @@ #### integer (maxsize : nat) : generator integer | ||
}, | ||
shrink: function () { return []; }, | ||
shrink: shrinkNoop, | ||
}; | ||
@@ -367,7 +491,43 @@ } | ||
// TODO: make shrink | ||
shrink: function () { return []; }, | ||
shrink: shrinkNoop, | ||
}; | ||
} | ||
function arbitraryArray(arbitrary, size) { | ||
var arrsize = getRandomInt(0, size); | ||
var arr = new Array(arrsize); | ||
for (var i = 0; i < arrsize; i++) { | ||
arr[i] = arbitrary(size); | ||
} | ||
return arr; | ||
} | ||
function arbitraryString(size) { | ||
return arbitraryArray(function () { | ||
return String.fromCharCode(getRandomInt(0, 0xff)); | ||
}, size).join(""); | ||
} | ||
function arbitraryObject(arbitrary, size) { | ||
var objsize = getRandomInt(0, size); | ||
var obj = {}; | ||
for (var i = 0; i < objsize; i++) { | ||
obj[arbitraryString(size)] = arbitrary(size); | ||
} | ||
return obj; | ||
} | ||
/** | ||
#### string () : generator string | ||
Strings | ||
*/ | ||
function string() { | ||
return { | ||
arbitrary: arbitraryString, | ||
shrink: shrinkNoop, // TODO: | ||
}; | ||
} | ||
/** | ||
#### array (gen : generator a) : generator (array a) | ||
@@ -380,8 +540,3 @@ */ | ||
arbitrary: function (size) { | ||
var arrsize = getRandomInt(0, size); | ||
var arr = new Array(arrsize); | ||
for (var i = 0; i < arrsize; i++) { | ||
arr[i] = generator.arbitrary(size); | ||
} | ||
return arr; | ||
return arbitraryArray(generator.arbitrary, size); | ||
}, | ||
@@ -409,2 +564,3 @@ | ||
function nonshrinkarray(generator) { | ||
@@ -415,13 +571,43 @@ generator = generator || integer(); | ||
arbitrary: function (size) { | ||
var arrsize = getRandomInt(0, size); | ||
var arr = new Array(arrsize); | ||
for (var i = 0; i < arrsize; i++) { | ||
arr[i] = generator.arbitrary(size); | ||
} | ||
return arr; | ||
return arbitraryArray(generator.arbitrary, size); | ||
}, | ||
shrink: function() { | ||
return []; | ||
shrink: shrinkNoop, | ||
}; | ||
} | ||
/** | ||
#### value : generator value | ||
JavaScript value: boolean, number, string, array of values or object with `value` values. | ||
*/ | ||
function value() { | ||
function arbitraryValue(size) { | ||
var type = getRandomInt(0, 5); | ||
if (size === 0) { | ||
switch (type) { | ||
case 0: return 0; | ||
case 1: return 0; | ||
case 2: return getRandomInt(0, 1) === 0; | ||
case 3: return ""; | ||
case 4: return []; | ||
case 5: return {}; | ||
} | ||
} | ||
size = size - 1; | ||
switch (type) { | ||
case 0: return getRandomInt(-size, size); | ||
case 1: return getRandomArbitrary(-size, size); | ||
case 2: return getRandomInt(0, 1) === 0; | ||
case 3: return arbitraryString(size); | ||
case 4: return arbitraryArray(arbitraryValue, size); | ||
case 5: return arbitraryObject(arbitraryValue, size); | ||
} | ||
} | ||
return { | ||
arbitrary: arbitraryValue, | ||
shrink: shrinkNoop, | ||
}; | ||
@@ -431,2 +617,28 @@ } | ||
/** | ||
#### fun (gen : generator a) : generator (b -> a) | ||
Unary functions. | ||
*/ | ||
function fun(gen) { | ||
gen = gen || value(); | ||
return { | ||
arbitrary: function (size) { | ||
var m = new FMap(); | ||
return function (arg) { | ||
if (!m.contains(arg)) { | ||
var value = gen.arbitrary(size); | ||
m.insert(arg, value); | ||
} | ||
return m.get(arg); | ||
}; | ||
}, | ||
shrink: shrinkNoop, | ||
}; | ||
} | ||
/** | ||
### Generator combinators | ||
@@ -450,9 +662,3 @@ */ | ||
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]; }) | ||
); | ||
return shrinkTuple([a, b], p); | ||
}, | ||
@@ -470,3 +676,9 @@ }; | ||
arbitrary: function (size) { | ||
while (true) { | ||
for (var i = 0; ; i++) { | ||
// if 5 tries failed, increase size | ||
if (i > 5) { | ||
i = 0; | ||
size += 1; | ||
} | ||
var x = generator.arbitrary(size); | ||
@@ -497,3 +709,6 @@ if (predicate(x)) { | ||
array: array, | ||
string: string, | ||
value: value, | ||
nonshrinkarray: nonshrinkarray, | ||
fun: fun, | ||
oneof: oneof, | ||
@@ -505,2 +720,5 @@ suchthat: suchthat, | ||
assert: assert, | ||
id: id, | ||
isEqual: isEqual, | ||
FMap: FMap, | ||
isPromise: isPromise, | ||
@@ -535,2 +753,3 @@ withPromise: withPromise, | ||
- 0.1.0 Usable library | ||
- 0.0.2 Documented preview | ||
@@ -537,0 +756,0 @@ - 0.0.1 Initial preview |
{ | ||
"name": "jsverify", | ||
"description": "Property-based testing for JavaScript.", | ||
"version": "0.0.2", | ||
"version": "0.1.0", | ||
"homepage": "https://github.com/phadej/jsverify", | ||
@@ -6,0 +6,0 @@ "author": { |
@@ -13,24 +13,12 @@ | ||
var jsc = require("jsverify"); | ||
``` | ||
Example output of `node example.js`: | ||
// forall (f : bool -> bool) (b : bool), f (f (f b)) = f(b). | ||
var bool_fn_applied_thrice = | ||
jsc.forall(jsc.fun(jsc.bool()), jsc.bool(), function (f, b) { | ||
return f(f(f(b))) === f(b); | ||
}); | ||
jsc.check(bool_fn_applied_thrice); | ||
// OK, passed 100 tests | ||
``` | ||
Propery doesn't hold, counterexample with undefined | ||
inc failing: false | ||
inc fixed: true | ||
Propery doesn't hold, counterexample with [ 0 ] | ||
add failing: { counterexample: [ 0 ] } | ||
add fixed: true | ||
Propery doesn't hold, counterexample with [ 0, -1 ] | ||
add3 failing: { counterexample: [ 0, -1 ] } | ||
intersects([1, 2], [1, 3]) true | ||
intersects([1, 2], [3, 4]) false | ||
Propery doesn't hold, counterexample with [ [] ] | ||
intersects try 1: { counterexample: [ [] ] } | ||
Propery doesn't hold, counterexample with [ [] ] | ||
intersects try 2: { counterexample: [ [] ] } | ||
intersects try 3: true | ||
intersects try 4: true | ||
``` | ||
@@ -76,2 +64,20 @@ ## Documentation | ||
#### id (x : any) : any | ||
Identity function. | ||
#### isEqual (a b : value) : bool | ||
Equality test for `value` objects. See `value` generator. | ||
#### FMap (eq : a -> a -> bool) : FMap a | ||
Finite map, with any object a key. | ||
Short summary of member functions: | ||
- FMap.insert (key : a) (value : any) : void | ||
- FMap.get (key : a) : any | ||
- FMap.contains (key : a) : obool | ||
#### isPromise p : bool | ||
@@ -102,10 +108,15 @@ | ||
#### forall (gen : generator a) (prop : a -> property_rec) : property | ||
#### forall (gens : generator a ...) (prop : a -> property_rec) : property | ||
Property constructor | ||
#### check (prop : property) : promise result + result | ||
#### check (prop : property) (opts : checkoptions) : promise result + result | ||
Run random checks for given `prop`. If `prop` is promise based, result is also wrapped in promise. | ||
Options: | ||
- `opts.tests` - test count to run, default 100 | ||
- `opts.size` - maximum size of generated values, default 5 | ||
- `opts.quiet` - do not `console.log` | ||
### Primitive generators | ||
@@ -133,4 +144,16 @@ | ||
#### string () : generator string | ||
Strings | ||
#### array (gen : generator a) : generator (array a) | ||
#### value : generator value | ||
JavaScript value: boolean, number, string, array of values or object with `value` values. | ||
#### fun (gen : generator a) : generator (b -> a) | ||
Unary functions. | ||
### Generator combinators | ||
@@ -159,2 +182,3 @@ | ||
- 0.1.0 Usable library | ||
- 0.0.2 Documented preview | ||
@@ -161,0 +185,0 @@ - 0.0.1 Initial preview |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
297600
19
8556
204
1