You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
6.5 KiB
232 lines
6.5 KiB
// Property-based testing representations of various things in AMQP |
|
|
|
'use strict'; |
|
|
|
var C = require('claire'); |
|
|
|
var forAll = C.forAll; |
|
var arb = C.data; |
|
var transform = C.transform; |
|
var repeat = C.repeat; |
|
var label = C.label; |
|
var sequence = C.sequence; |
|
var asGenerator = C.asGenerator; |
|
var sized = C.sized; |
|
var recursive = C.recursive; |
|
var choice = C.choice; |
|
var Undefined = C.Undefined; |
|
|
|
// Stub these out so we can use outside tests |
|
// if (!suite) var suite = function() {} |
|
// if (!test) var test = function() {} |
|
|
|
// These aren't exported in claire/index. so I could have to reproduce |
|
// them I guess. |
|
function choose(a, b) { |
|
return Math.random() * (b - a) + a; |
|
} |
|
|
|
function chooseInt(a, b) { |
|
return Math.floor(choose(a, b)); |
|
} |
|
|
|
function rangeInt(name, a, b) { |
|
return label(name, |
|
asGenerator(function(_) { return chooseInt(a, b); })); |
|
} |
|
|
|
function toFloat32(i) { |
|
var b = new Buffer(4); |
|
b.writeFloatBE(i, 0); |
|
return b.readFloatBE(0); |
|
} |
|
|
|
function floatChooser(maxExp) { |
|
return function() { |
|
var n = Number.NaN; |
|
while (isNaN(n)) { |
|
var mantissa = Math.random() * 2 - 1; |
|
var exponent = chooseInt(0, maxExp); |
|
n = Math.pow(mantissa, exponent); |
|
} |
|
return n; |
|
} |
|
} |
|
|
|
// FIXME null, byte array, others? |
|
|
|
var Octet = rangeInt('octet', 0, 255); |
|
var ShortStr = label('shortstr', |
|
transform(function(s) { |
|
return s.substr(0, 255); |
|
}, arb.Str)); |
|
|
|
var LongStr = label('longstr', |
|
transform( |
|
function(bytes) { return new Buffer(bytes); }, |
|
repeat(Octet))); |
|
|
|
var UShort = rangeInt('short-uint', 0, 0xffff); |
|
var ULong = rangeInt('long-uint', 0, 0xffffffff); |
|
var ULongLong = rangeInt('longlong-uint', 0, 0xffffffffffffffff); |
|
var Short = rangeInt('short-int', -0x8000, 0x7fff); |
|
var Long = rangeInt('long-int', -0x80000000, 0x7fffffff); |
|
var LongLong = rangeInt('longlong-int', -0x8000000000000000, |
|
0x7fffffffffffffff); |
|
var Bit = label('bit', arb.Bool); |
|
var Double = label('double', asGenerator(floatChooser(308))); |
|
var Float = label('float', transform(toFloat32, floatChooser(38))); |
|
var Timestamp = label('timestamp', transform( |
|
function(n) { |
|
return {'!': 'timestamp', value: n}; |
|
}, ULongLong)); |
|
var Decimal = label('decimal', transform( |
|
function(args) { |
|
return {'!': 'decimal', value: {places: args[1], digits: args[0]}}; |
|
}, sequence(arb.UInt, Octet))); |
|
|
|
var FieldArray = label('field-array', recursive(function() { |
|
return arb.Array( |
|
arb.Null, |
|
LongStr, ShortStr, Octet, |
|
UShort, ULong, ULongLong, |
|
Short, Long, LongLong, |
|
Bit, Float, Double, FieldTable, FieldArray) |
|
})); |
|
|
|
var FieldTable = label('table', recursive(function() { |
|
return sized(function() { return 5; }, |
|
arb.Object( |
|
arb.Null, |
|
LongStr, ShortStr, Octet, |
|
UShort, ULong, ULongLong, |
|
Short, Long, LongLong, |
|
Bit, Float, Double, FieldArray, FieldTable)) |
|
})); |
|
|
|
// Internal tests of our properties |
|
var domainProps = [ |
|
[Octet, function(n) { return n >= 0 && n < 256; }], |
|
[ShortStr, function(s) { return typeof s === 'string' && s.length < 256; }], |
|
[LongStr, function(s) { return Buffer.isBuffer(s); }], |
|
[UShort, function(n) { return n >= 0 && n <= 0xffff; }], |
|
[ULong, function(n) { return n >= 0 && n <= 0xffffffff; }], |
|
[ULongLong, function(n) { |
|
return n >= 0 && n <= 0xffffffffffffffff; }], |
|
[Short, function(n) { return n >= -0x8000 && n <= 0x8000; }], |
|
[Long, function(n) { return n >= -0x80000000 && n < 0x80000000; }], |
|
[LongLong, function(n) { return n >= -0x8000000000000000 && n < 0x8000000000000000; }], |
|
[Bit, function(b) { return typeof b === 'boolean'; }], |
|
[Double, function(f) { return !isNaN(f) && isFinite(f); }], |
|
[Float, function(f) { return !isNaN(f) && isFinite(f) && (Math.log(Math.abs(f)) * Math.LOG10E) < 309; }], |
|
[Decimal, function(d) { |
|
return d['!'] === 'decimal' && |
|
d.value['places'] <= 255 && |
|
d.value['digits'] <= 0xffffffff; |
|
}], |
|
[Timestamp, function(t) { return t['!'] === 'timestamp'; }], |
|
[FieldTable, function(t) { return typeof t === 'object'; }], |
|
[FieldArray, function(a) { return Array.isArray(a); }] |
|
]; |
|
|
|
suite("Domains", function() { |
|
domainProps.forEach(function(p) { |
|
test(p[0] + ' domain', |
|
forAll(p[0]).satisfy(p[1]).asTest({times: 500})); |
|
}); |
|
}); |
|
|
|
// For methods and properties (as opposed to field table values) it's |
|
// easier just to accept and produce numbers for timestamps. |
|
var ArgTimestamp = label('timestamp', ULongLong); |
|
|
|
// These are the domains used in method arguments |
|
var ARG_TYPES = { |
|
'octet': Octet, |
|
'shortstr': ShortStr, |
|
'longstr': LongStr, |
|
'short': UShort, |
|
'long': ULong, |
|
'longlong': ULongLong, |
|
'bit': Bit, |
|
'table': FieldTable, |
|
'timestamp': ArgTimestamp |
|
}; |
|
|
|
function argtype(thing) { |
|
if (thing.default === undefined) { |
|
return ARG_TYPES[thing.type]; |
|
} |
|
else { |
|
return choice(ARG_TYPES[thing.type], Undefined); |
|
} |
|
} |
|
|
|
function zipObject(vals, names) { |
|
var obj = {}; |
|
vals.forEach(function(v, i) { obj[names[i]] = v; }); |
|
return obj; |
|
} |
|
|
|
function name(arg) { return arg.name; } |
|
|
|
var defs = require('../lib/defs'); |
|
|
|
function method(info) { |
|
var domain = sequence.apply(null, info.args.map(argtype)); |
|
var names = info.args.map(name); |
|
return label(info.name, transform(function(fieldVals) { |
|
return {id: info.id, |
|
fields: zipObject(fieldVals, names)}; |
|
}, domain)); |
|
} |
|
|
|
function properties(info) { |
|
var types = info.args.map(argtype); |
|
types.unshift(ULongLong); // size |
|
var domain = sequence.apply(null, types); |
|
var names = info.args.map(name); |
|
return label(info.name, transform(function(fieldVals) { |
|
return {id: info.id, |
|
size: fieldVals[0], |
|
fields: zipObject(fieldVals.slice(1), names)}; |
|
}, domain)); |
|
} |
|
|
|
var methods = []; |
|
var propertieses = []; |
|
|
|
for (var k in defs) { |
|
if (k.substr(0, 10) === 'methodInfo') { |
|
methods.push(method(defs[k])); |
|
methods[defs[k].name] = method(defs[k]); |
|
} |
|
else if (k.substr(0, 14) === 'propertiesInfo') { |
|
propertieses.push(properties(defs[k])); |
|
propertieses[defs[k].name] = properties(defs[k]); |
|
} |
|
}; |
|
|
|
module.exports = { |
|
Octet: Octet, |
|
ShortStr: ShortStr, |
|
LongStr: LongStr, |
|
UShort: UShort, |
|
ULong: ULong, |
|
ULongLong: ULongLong, |
|
Short: Short, |
|
Long: Long, |
|
LongLong: LongLong, |
|
Bit: Bit, |
|
Double: Double, |
|
Float: Float, |
|
Timestamp: Timestamp, |
|
Decimal: Decimal, |
|
FieldArray: FieldArray, |
|
FieldTable: FieldTable, |
|
|
|
methods: methods, |
|
properties: propertieses |
|
}; |
|
|
|
module.exports.rangeInt = rangeInt;
|
|
|