jsonbinpack
Version:
A space-efficient schema-driven binary JSON serialization format based on JSON Schema
762 lines (676 loc) • 21 kB
text/typescript
/*
* Copyright 2021 Juan Cruz Viotti
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import tap from 'tap'
import * as fc from 'fast-check'
import * as util from 'util'
import {
JSONObject
} from '../../lib/json'
import {
getEncoding
} from '../../lib/mapper'
import {
TypedKeysOptions,
RequiredUnboundedTypedOptions,
OptionalUnboundedTypedOptions,
BoundedTypedOptions,
UnboundedTypedOptions,
RequiredBoundedTypedOptions,
OptionalBoundedTypedOptions,
PackedUnboundedOptions,
PackedRequiredBoundedOptions
} from '../../lib/encoder/object/options'
import {
ARBITRARY_TYPED_KEYS_OBJECT as ENCODE_ARBITRARY_TYPED_KEYS_OBJECT,
NON_REQUIRED_BOUNDED_TYPED_OBJECT as ENCODE_NON_REQUIRED_BOUNDED_TYPED_OBJECT,
REQUIRED_ONLY_BOUNDED_TYPED_OBJECT as ENCODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT,
MIXED_BOUNDED_TYPED_OBJECT as ENCODE_MIXED_BOUNDED_TYPED_OBJECT,
REQUIRED_UNBOUNDED_TYPED_OBJECT as ENCODE_REQUIRED_UNBOUNDED_TYPED_OBJECT,
OPTIONAL_UNBOUNDED_TYPED_OBJECT as ENCODE_OPTIONAL_UNBOUNDED_TYPED_OBJECT,
MIXED_UNBOUNDED_TYPED_OBJECT as ENCODE_MIXED_UNBOUNDED_TYPED_OBJECT,
PACKED_UNBOUNDED_OBJECT as ENCODE_PACKED_UNBOUNDED_OBJECT,
PACKED_BOUNDED_REQUIRED_OBJECT as ENCODE_PACKED_BOUNDED_REQUIRED_OBJECT
} from '../../lib/encoder/object/encode'
import {
ObjectResult,
ARBITRARY_TYPED_KEYS_OBJECT as DECODE_ARBITRARY_TYPED_KEYS_OBJECT,
NON_REQUIRED_BOUNDED_TYPED_OBJECT as DECODE_NON_REQUIRED_BOUNDED_TYPED_OBJECT,
REQUIRED_ONLY_BOUNDED_TYPED_OBJECT as DECODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT,
MIXED_BOUNDED_TYPED_OBJECT as DECODE_MIXED_BOUNDED_TYPED_OBJECT,
REQUIRED_UNBOUNDED_TYPED_OBJECT as DECODE_REQUIRED_UNBOUNDED_TYPED_OBJECT,
OPTIONAL_UNBOUNDED_TYPED_OBJECT as DECODE_OPTIONAL_UNBOUNDED_TYPED_OBJECT,
MIXED_UNBOUNDED_TYPED_OBJECT as DECODE_MIXED_UNBOUNDED_TYPED_OBJECT,
PACKED_UNBOUNDED_OBJECT as DECODE_PACKED_UNBOUNDED_OBJECT,
PACKED_BOUNDED_REQUIRED_OBJECT as DECODE_PACKED_BOUNDED_REQUIRED_OBJECT
} from '../../lib/encoder/object/decode'
import {
StringEncoding,
getStringEncoding
} from '../../lib/mapper/string'
import {
ResizableBuffer,
EncodingContext,
getDefaultEncodingContext,
EncodingType
} from '../../lib/encoder'
tap.test('ARBITRARY_TYPED_KEYS_OBJECT: scalars values', (test) => {
const options: TypedKeysOptions = {
keyEncoding: getStringEncoding({
type: 'string'
}, 1),
encoding: {
type: EncodingType.Any,
encoding: 'ANY_PACKED_TYPE_TAG_BYTE_PREFIX',
options: {}
}
}
fc.assert(fc.property(fc.nat(10), fc.dictionary(fc.string(), fc.oneof(
fc.constant(null),
fc.boolean(),
fc.integer(),
fc.float(),
fc.double(),
fc.string({
maxLength: 10
})
)), (offset: number, value: JSONObject): boolean => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(2048))
const bytesWritten: number = ENCODE_ARBITRARY_TYPED_KEYS_OBJECT(
buffer, offset, value, options, context)
const result: ObjectResult = DECODE_ARBITRARY_TYPED_KEYS_OBJECT(
buffer, offset, options)
return bytesWritten > 0 && result.bytes === bytesWritten &&
util.isDeepStrictEqual(result.value, value)
}), {
verbose: false
})
test.end()
})
tap.test('ARBITRARY_TYPED_KEYS_OBJECT: untyped {foo:"bar",baz:1}', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(16))
const value: JSONObject = {
foo: 'bar',
baz: 1
}
const keyEncoding: StringEncoding = getStringEncoding({
type: 'string'
}, 1)
const bytesWritten: number = ENCODE_ARBITRARY_TYPED_KEYS_OBJECT(
buffer, 0, value, {
keyEncoding,
encoding: {
type: EncodingType.Any,
encoding: 'ANY_PACKED_TYPE_TAG_BYTE_PREFIX',
options: {}
}
}, context)
const result: ObjectResult = DECODE_ARBITRARY_TYPED_KEYS_OBJECT(
buffer, 0, {
keyEncoding,
encoding: {
type: EncodingType.Any,
encoding: 'ANY_PACKED_TYPE_TAG_BYTE_PREFIX',
options: {}
}
})
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('ARBITRARY_TYPED_KEYS_OBJECT: typed {foo:"bar",baz:1}', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(16))
const value: JSONObject = {
foo: 'bar',
baz: 1
}
const keyEncoding: StringEncoding = getStringEncoding({
type: 'string',
minLength: 3
}, 1)
const bytesWritten: number = ENCODE_ARBITRARY_TYPED_KEYS_OBJECT(
buffer, 0, value, {
keyEncoding,
encoding: {
type: EncodingType.Any,
encoding: 'ANY_PACKED_TYPE_TAG_BYTE_PREFIX',
options: {}
}
}, context)
const result: ObjectResult = DECODE_ARBITRARY_TYPED_KEYS_OBJECT(
buffer, 0, {
keyEncoding,
encoding: {
type: EncodingType.Any,
encoding: 'ANY_PACKED_TYPE_TAG_BYTE_PREFIX',
options: {}
}
})
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('NON_REQUIRED_BOUNDED_TYPED_OBJECT: typed {foo:"bar",baz:1}', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(7))
const value: JSONObject = {
foo: 'bar',
baz: 1
}
const options: OptionalBoundedTypedOptions = {
optionalProperties: [ 'baz', 'bar', 'foo', 'qux' ],
propertyEncodings: {
foo: getEncoding({
type: 'string'
}, 1),
baz: getEncoding({
type: 'integer',
minimum: 0
}, 1),
bar: getEncoding({}, 1),
qux: getEncoding({}, 1)
}
}
const bytesWritten: number = ENCODE_NON_REQUIRED_BOUNDED_TYPED_OBJECT(
buffer, 0, value, options, context)
const result: ObjectResult = DECODE_NON_REQUIRED_BOUNDED_TYPED_OBJECT(
buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('REQUIRED_ONLY_BOUNDED_TYPED_OBJECT: typed {foo:"bar",baz:1}', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(5))
const value: JSONObject = {
foo: 'bar',
baz: 1
}
const options: RequiredBoundedTypedOptions = {
requiredProperties: [ 'baz', 'foo' ],
booleanRequiredProperties: [],
propertyEncodings: {
foo: getEncoding({
type: 'string'
}, 1),
baz: getEncoding({
type: 'integer',
minimum: 0
}, 1)
}
}
const bytesWritten: number = ENCODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, 0, value, options, context)
const result: ObjectResult = DECODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('MIXED_BOUNDED_TYPED_OBJECT: typed {foo:"bar",baz:1} with one required', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(7))
const value: JSONObject = {
foo: 'bar',
baz: 1
}
const options: BoundedTypedOptions = {
requiredProperties: [ 'foo' ],
booleanRequiredProperties: [],
optionalProperties: [ 'baz' ],
propertyEncodings: {
foo: getEncoding({
type: 'string'
}, 1),
baz: getEncoding({
type: 'integer',
minimum: 0
}, 1)
}
}
const bytesWritten: number = ENCODE_MIXED_BOUNDED_TYPED_OBJECT(buffer, 0, value, options, context)
const result: ObjectResult = DECODE_MIXED_BOUNDED_TYPED_OBJECT(buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('MIXED_BOUNDED_TYPED_OBJECT: {foo:"bar",baz:1} with one missing optional', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(6))
const value: JSONObject = {
foo: 'bar'
}
const options: BoundedTypedOptions = {
requiredProperties: [ 'foo' ],
booleanRequiredProperties: [],
optionalProperties: [ 'baz' ],
propertyEncodings: {
foo: getEncoding({
type: 'string'
}, 1),
baz: getEncoding({
type: 'integer',
minimum: 0
}, 1)
}
}
const bytesWritten: number = ENCODE_MIXED_BOUNDED_TYPED_OBJECT(buffer, 0, value, options, context)
const result: ObjectResult = DECODE_MIXED_BOUNDED_TYPED_OBJECT(buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('REQUIRED_UNBOUNDED_TYPED_OBJECT: semityped {foo:"bar",baz:1}', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(11))
const value: JSONObject = {
foo: 'bar',
baz: 1
}
const options: RequiredUnboundedTypedOptions = {
requiredProperties: [ 'foo' ],
booleanRequiredProperties: [],
propertyEncodings: {
foo: getEncoding({
type: 'string'
}, 1)
},
keyEncoding: getStringEncoding({
type: 'string'
}, 1),
encoding: {
type: EncodingType.Any,
encoding: 'ANY_PACKED_TYPE_TAG_BYTE_PREFIX',
options: {}
}
}
const bytesWritten: number = ENCODE_REQUIRED_UNBOUNDED_TYPED_OBJECT(buffer, 0, value, options, context)
const result: ObjectResult = DECODE_REQUIRED_UNBOUNDED_TYPED_OBJECT(buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('REQUIRED_UNBOUNDED_TYPED_OBJECT: typed {foo:"bar"}', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(5))
const value: JSONObject = {
foo: 'bar'
}
const options: RequiredUnboundedTypedOptions = {
requiredProperties: [ 'foo' ],
booleanRequiredProperties: [],
propertyEncodings: {
foo: getEncoding({
type: 'string'
}, 1)
},
keyEncoding: getStringEncoding({
type: 'string'
}, 1),
encoding: {
type: EncodingType.Any,
encoding: 'ANY_PACKED_TYPE_TAG_BYTE_PREFIX',
options: {}
}
}
const bytesWritten: number = ENCODE_REQUIRED_UNBOUNDED_TYPED_OBJECT(buffer, 0, value, options, context)
const result: ObjectResult = DECODE_REQUIRED_UNBOUNDED_TYPED_OBJECT(buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('OPTIONAL_UNBOUNDED_TYPED_OBJECT: semityped {foo:"bar",baz:1}', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(13))
const value: JSONObject = {
foo: 'bar',
baz: 1
}
const options: OptionalUnboundedTypedOptions = {
optionalProperties: [ 'foo' ],
propertyEncodings: {
foo: getEncoding({
type: 'string'
}, 1)
},
keyEncoding: getStringEncoding({
type: 'string'
}, 1),
encoding: {
type: EncodingType.Any,
encoding: 'ANY_PACKED_TYPE_TAG_BYTE_PREFIX',
options: {}
}
}
const bytesWritten: number = ENCODE_OPTIONAL_UNBOUNDED_TYPED_OBJECT(
buffer, 0, value, options, context)
const result: ObjectResult = DECODE_OPTIONAL_UNBOUNDED_TYPED_OBJECT(
buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('MIXED_UNBOUNDED_TYPED_OBJECT: mixed {foo:"bar",baz:1,qux:null}', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(13))
const value: JSONObject = {
foo: 'bar',
baz: 1,
qux: null
}
const options: UnboundedTypedOptions = {
requiredProperties: [ 'foo' ],
booleanRequiredProperties: [],
optionalProperties: [ 'baz' ],
keyEncoding: getStringEncoding({
type: 'string'
}, 1),
encoding: {
type: EncodingType.Any,
encoding: 'ANY_PACKED_TYPE_TAG_BYTE_PREFIX',
options: {}
},
propertyEncodings: {
foo: getEncoding({
type: 'string'
}, 1),
baz: getEncoding({
type: 'integer',
minimum: 0
}, 1)
}
}
const bytesWritten: number = ENCODE_MIXED_UNBOUNDED_TYPED_OBJECT(
buffer, 0, value, options, context)
const result: ObjectResult = DECODE_MIXED_UNBOUNDED_TYPED_OBJECT(
buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('REQUIRED_ONLY_BOUNDED_TYPED_OBJECT: typed {foo:"bar",baz:1,baz:true,qux:false}', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(6))
const value: JSONObject = {
foo: 'bar',
bar: 1,
baz: true,
qux: false
}
const options: RequiredBoundedTypedOptions = {
requiredProperties: [ 'bar', 'foo' ],
booleanRequiredProperties: [ 'baz', 'qux' ],
propertyEncodings: {
foo: getEncoding({
type: 'string'
}, 1),
bar: getEncoding({
type: 'integer',
minimum: 0
}, 1),
baz: getEncoding({
type: 'boolean'
}, 1),
qux: getEncoding({
type: 'boolean'
}, 1)
}
}
const bytesWritten: number = ENCODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, 0, value, options, context)
const result: ObjectResult = DECODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('REQUIRED_ONLY_BOUNDED_TYPED_OBJECT: three boolean properties', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(1))
const value: JSONObject = {
foo: true,
bar: false,
baz: true
}
const options: RequiredBoundedTypedOptions = {
requiredProperties: [],
booleanRequiredProperties: [ 'foo', 'bar', 'baz' ],
propertyEncodings: {
foo: getEncoding({
type: 'boolean'
}, 1),
bar: getEncoding({
type: 'boolean'
}, 1),
baz: getEncoding({
type: 'boolean'
}, 1)
}
}
const bytesWritten: number = ENCODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, 0, value, options, context)
const result: ObjectResult = DECODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('REQUIRED_ONLY_BOUNDED_TYPED_OBJECT: eight boolean properties', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(1))
const value: JSONObject = {
foo: true,
bar: false,
baz: true,
qux: true,
xxx: false,
yyy: false,
zzz: true,
qqq: true
}
const options: RequiredBoundedTypedOptions = {
requiredProperties: [],
booleanRequiredProperties: [ 'foo', 'bar', 'baz', 'qux', 'xxx', 'yyy', 'zzz', 'qqq' ],
propertyEncodings: {
foo: getEncoding({
type: 'boolean'
}, 1),
bar: getEncoding({
type: 'boolean'
}, 1),
baz: getEncoding({
type: 'boolean'
}, 1),
qux: getEncoding({
type: 'boolean'
}, 1),
xxx: getEncoding({
type: 'boolean'
}, 1),
yyy: getEncoding({
type: 'boolean'
}, 1),
zzz: getEncoding({
type: 'boolean'
}, 1),
qqq: getEncoding({
type: 'boolean'
}, 1)
}
}
const bytesWritten: number = ENCODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, 0, value, options, context)
const result: ObjectResult = DECODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('REQUIRED_ONLY_BOUNDED_TYPED_OBJECT: nine boolean properties', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(2))
const value: JSONObject = {
foo: true,
bar: false,
baz: true,
qux: true,
xxx: false,
yyy: false,
zzz: true,
qqq: true,
ppp: false
}
const options: RequiredBoundedTypedOptions = {
requiredProperties: [],
booleanRequiredProperties: [ 'foo', 'bar', 'baz', 'qux', 'xxx', 'yyy', 'zzz', 'qqq', 'ppp' ],
propertyEncodings: {
foo: getEncoding({
type: 'boolean'
}, 1),
bar: getEncoding({
type: 'boolean'
}, 1),
baz: getEncoding({
type: 'boolean'
}, 1),
qux: getEncoding({
type: 'boolean'
}, 1),
xxx: getEncoding({
type: 'boolean'
}, 1),
yyy: getEncoding({
type: 'boolean'
}, 1),
zzz: getEncoding({
type: 'boolean'
}, 1),
qqq: getEncoding({
type: 'boolean'
}, 1),
ppp: getEncoding({
type: 'boolean'
}, 1)
}
}
const bytesWritten: number = ENCODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, 0, value, options, context)
const result: ObjectResult = DECODE_REQUIRED_ONLY_BOUNDED_TYPED_OBJECT(
buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('PACKED_UNBOUNDED_OBJECT: complex object', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(10))
const value: JSONObject = {
foo: 1,
bar: 2,
baz: 0,
qux: 2,
extra: 1,
name: 'john',
flag: true,
random: 1
}
const options: PackedUnboundedOptions = {
packedRequiredProperties: [ 'bar', 'baz', 'extra', 'foo', 'qux' ],
packedEncoding: {
type: EncodingType.Integer,
encoding: 'BOUNDED_MULTIPLE_8BITS_ENUM_FIXED',
options: {
minimum: 0,
maximum: 2,
multiplier: 1
}
},
encoding: {
type: EncodingType.Any,
encoding: 'ANY_PACKED_TYPE_TAG_BYTE_PREFIX',
options: {}
},
propertyEncodings: {
name: getEncoding({
type: 'string'
}, 1),
age: getEncoding({
type: 'integer',
minimum: 0
}, 1),
flag: getEncoding({
type: 'boolean'
}, 1)
},
optionalProperties: [ 'age' ],
requiredProperties: [ 'name' ],
booleanRequiredProperties: [ 'flag' ],
keyEncoding: getStringEncoding({
type: 'string'
}, 1)
}
const bytesWritten: number = ENCODE_PACKED_UNBOUNDED_OBJECT(
buffer, 0, value, options, context)
const result: ObjectResult = DECODE_PACKED_UNBOUNDED_OBJECT(
buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})
tap.test('PACKED_BOUNDED_REQUIRED_OBJECT: complex object', (test) => {
const context: EncodingContext = getDefaultEncodingContext()
const buffer: ResizableBuffer = new ResizableBuffer(Buffer.allocUnsafe(9))
const value: JSONObject = {
foo: 1,
bar: 2,
baz: 0,
qux: 2,
extra: 1,
name: 'john',
flag: true
}
const options: PackedRequiredBoundedOptions = {
packedRequiredProperties: [ 'bar', 'baz', 'extra', 'foo', 'qux' ],
packedEncoding: {
type: EncodingType.Integer,
encoding: 'BOUNDED_MULTIPLE_8BITS_ENUM_FIXED',
options: {
minimum: 0,
maximum: 2,
multiplier: 1
}
},
propertyEncodings: {
name: getEncoding({
type: 'string'
}, 1),
flag: getEncoding({
type: 'boolean'
}, 1)
},
requiredProperties: [ 'name' ],
booleanRequiredProperties: [ 'flag' ]
}
const bytesWritten: number = ENCODE_PACKED_BOUNDED_REQUIRED_OBJECT(
buffer, 0, value, options, context)
const result: ObjectResult = DECODE_PACKED_BOUNDED_REQUIRED_OBJECT(
buffer, 0, options)
test.is(bytesWritten, result.bytes)
test.strictSame(result.value, value)
test.end()
})