--!strict
--!optimize 2

local Spek = require(script.Parent.Parent.Spek)
local Bitbuf = require(script.Parent)

-- Reset Buffer content. Depends on implementation.
local function resetBuf(self: _Buffer, l: number, i: number, ones: boolean)
	self.len = l
	self.i = i
	table.clear(self.buf)
	if ones then
		-- Fill with ones.
		local n = math.ceil(l/32)
		for i = 1, n-1 do
			self.buf[i] = 0xFFFFFFFF
		end
		self.buf[n] = 2^((l-1)%32+1)-1
	end
end

local function errorf(fmt: string, ...: any)
	error(string.format(fmt, ...))
end

local function join<T>(a: {T}, b: {T}): {T}
	local c = table.create(#a + #b)
	table.move(a, 1, #a, 1, c)
	table.move(b, 1, #b, #a+1, c)
	return c
end

type _Buffer = Bitbuf.Buffer & {
	buf: {number},
	len: number,
	i: number,
	writeUnit: (self: _Buffer, size: number, v: number) -> (),
	readUnit: (self: _Buffer, size: number) -> number,
}

-- Produce string having *size* 1 bits, with trailing zeros.
local ones: (size: number) -> string do
	local pow2 = {1, 3, 7, 15, 31, 63, 127}
	function ones(size: number): string
		local r = size % 8
		if r == 0 then
			return string.rep("\255", math.floor(size/8))
		end
		return string.rep("\255", math.floor(size/8)) .. string.char(pow2[r])
	end
end

local explodeByte = {
	[0x00]="00000000",[0x01]="10000000",[0x02]="01000000",[0x03]="11000000",
	[0x04]="00100000",[0x05]="10100000",[0x06]="01100000",[0x07]="11100000",
	[0x08]="00010000",[0x09]="10010000",[0x0A]="01010000",[0x0B]="11010000",
	[0x0C]="00110000",[0x0D]="10110000",[0x0E]="01110000",[0x0F]="11110000",
	[0x10]="00001000",[0x11]="10001000",[0x12]="01001000",[0x13]="11001000",
	[0x14]="00101000",[0x15]="10101000",[0x16]="01101000",[0x17]="11101000",
	[0x18]="00011000",[0x19]="10011000",[0x1A]="01011000",[0x1B]="11011000",
	[0x1C]="00111000",[0x1D]="10111000",[0x1E]="01111000",[0x1F]="11111000",
	[0x20]="00000100",[0x21]="10000100",[0x22]="01000100",[0x23]="11000100",
	[0x24]="00100100",[0x25]="10100100",[0x26]="01100100",[0x27]="11100100",
	[0x28]="00010100",[0x29]="10010100",[0x2A]="01010100",[0x2B]="11010100",
	[0x2C]="00110100",[0x2D]="10110100",[0x2E]="01110100",[0x2F]="11110100",
	[0x30]="00001100",[0x31]="10001100",[0x32]="01001100",[0x33]="11001100",
	[0x34]="00101100",[0x35]="10101100",[0x36]="01101100",[0x37]="11101100",
	[0x38]="00011100",[0x39]="10011100",[0x3A]="01011100",[0x3B]="11011100",
	[0x3C]="00111100",[0x3D]="10111100",[0x3E]="01111100",[0x3F]="11111100",
	[0x40]="00000010",[0x41]="10000010",[0x42]="01000010",[0x43]="11000010",
	[0x44]="00100010",[0x45]="10100010",[0x46]="01100010",[0x47]="11100010",
	[0x48]="00010010",[0x49]="10010010",[0x4A]="01010010",[0x4B]="11010010",
	[0x4C]="00110010",[0x4D]="10110010",[0x4E]="01110010",[0x4F]="11110010",
	[0x50]="00001010",[0x51]="10001010",[0x52]="01001010",[0x53]="11001010",
	[0x54]="00101010",[0x55]="10101010",[0x56]="01101010",[0x57]="11101010",
	[0x58]="00011010",[0x59]="10011010",[0x5A]="01011010",[0x5B]="11011010",
	[0x5C]="00111010",[0x5D]="10111010",[0x5E]="01111010",[0x5F]="11111010",
	[0x60]="00000110",[0x61]="10000110",[0x62]="01000110",[0x63]="11000110",
	[0x64]="00100110",[0x65]="10100110",[0x66]="01100110",[0x67]="11100110",
	[0x68]="00010110",[0x69]="10010110",[0x6A]="01010110",[0x6B]="11010110",
	[0x6C]="00110110",[0x6D]="10110110",[0x6E]="01110110",[0x6F]="11110110",
	[0x70]="00001110",[0x71]="10001110",[0x72]="01001110",[0x73]="11001110",
	[0x74]="00101110",[0x75]="10101110",[0x76]="01101110",[0x77]="11101110",
	[0x78]="00011110",[0x79]="10011110",[0x7A]="01011110",[0x7B]="11011110",
	[0x7C]="00111110",[0x7D]="10111110",[0x7E]="01111110",[0x7F]="11111110",
	[0x80]="00000001",[0x81]="10000001",[0x82]="01000001",[0x83]="11000001",
	[0x84]="00100001",[0x85]="10100001",[0x86]="01100001",[0x87]="11100001",
	[0x88]="00010001",[0x89]="10010001",[0x8A]="01010001",[0x8B]="11010001",
	[0x8C]="00110001",[0x8D]="10110001",[0x8E]="01110001",[0x8F]="11110001",
	[0x90]="00001001",[0x91]="10001001",[0x92]="01001001",[0x93]="11001001",
	[0x94]="00101001",[0x95]="10101001",[0x96]="01101001",[0x97]="11101001",
	[0x98]="00011001",[0x99]="10011001",[0x9A]="01011001",[0x9B]="11011001",
	[0x9C]="00111001",[0x9D]="10111001",[0x9E]="01111001",[0x9F]="11111001",
	[0xA0]="00000101",[0xA1]="10000101",[0xA2]="01000101",[0xA3]="11000101",
	[0xA4]="00100101",[0xA5]="10100101",[0xA6]="01100101",[0xA7]="11100101",
	[0xA8]="00010101",[0xA9]="10010101",[0xAA]="01010101",[0xAB]="11010101",
	[0xAC]="00110101",[0xAD]="10110101",[0xAE]="01110101",[0xAF]="11110101",
	[0xB0]="00001101",[0xB1]="10001101",[0xB2]="01001101",[0xB3]="11001101",
	[0xB4]="00101101",[0xB5]="10101101",[0xB6]="01101101",[0xB7]="11101101",
	[0xB8]="00011101",[0xB9]="10011101",[0xBA]="01011101",[0xBB]="11011101",
	[0xBC]="00111101",[0xBD]="10111101",[0xBE]="01111101",[0xBF]="11111101",
	[0xC0]="00000011",[0xC1]="10000011",[0xC2]="01000011",[0xC3]="11000011",
	[0xC4]="00100011",[0xC5]="10100011",[0xC6]="01100011",[0xC7]="11100011",
	[0xC8]="00010011",[0xC9]="10010011",[0xCA]="01010011",[0xCB]="11010011",
	[0xCC]="00110011",[0xCD]="10110011",[0xCE]="01110011",[0xCF]="11110011",
	[0xD0]="00001011",[0xD1]="10001011",[0xD2]="01001011",[0xD3]="11001011",
	[0xD4]="00101011",[0xD5]="10101011",[0xD6]="01101011",[0xD7]="11101011",
	[0xD8]="00011011",[0xD9]="10011011",[0xDA]="01011011",[0xDB]="11011011",
	[0xDC]="00111011",[0xDD]="10111011",[0xDE]="01111011",[0xDF]="11111011",
	[0xE0]="00000111",[0xE1]="10000111",[0xE2]="01000111",[0xE3]="11000111",
	[0xE4]="00100111",[0xE5]="10100111",[0xE6]="01100111",[0xE7]="11100111",
	[0xE8]="00010111",[0xE9]="10010111",[0xEA]="01010111",[0xEB]="11010111",
	[0xEC]="00110111",[0xED]="10110111",[0xEE]="01110111",[0xEF]="11110111",
	[0xF0]="00001111",[0xF1]="10001111",[0xF2]="01001111",[0xF3]="11001111",
	[0xF4]="00101111",[0xF5]="10101111",[0xF6]="01101111",[0xF7]="11101111",
	[0xF8]="00011111",[0xF9]="10011111",[0xFA]="01011111",[0xFB]="11011111",
	[0xFC]="00111111",[0xFD]="10111111",[0xFE]="01111111",[0xFF]="11111111",
}

-- Return the bits of *v* having *size* bits.
local explode: (v: string, size: number?) -> string do
	local a = table.create(512*8)
	function explode(v: string, size: number?): string
		local size = size or #v*8
		if size == 0 then
			return ""
		end
		local n = math.ceil(size/8)
		for i = 1, n do
			a[i] = explodeByte[string.byte(v, i)]
		end
		a[n] = string.sub(a[n], 1, (size-1)%8+1)
		return table.concat(a, "", 1, n)
	end
end

-- Return the bits of a Buffer.
local function explodeBuf(buf: Bitbuf.Buffer): string
	return explode(buf:String(), buf:Len())
end

-- Return string comparing *want* and *got* by printing bytes side by side.
-- Unprintable bytes are escaped.
local function compError(want: string, got: string, fn: ((string, ...any)->string)?): string
	local unprint = "[^\32-\126]"
	if string.match(want, unprint) or string.match(got, unprint) then
		want = "*" .. string.gsub(want, ".", function(c) return string.format("\\x%02X", string.byte(c)) end)
		got = "*" .. string.gsub(got, ".", function(c) return string.format("\\x%02X", string.byte(c)) end)
	end
	if fn then
		return fn("unexpected buffer content:\n\twant: %s\n\t got: %s", want, got)
	end
	return "unexpected buffer content:\n\twant: %s\n\t got: %s", want, got
end

-- If two strings aren't equal, return an error comparing their bytes.
local function compare(want: string, got: string): string?
	if got == want then
		return nil
	end
	return compError(want, got)
end

-- Explode buffer into string of bits.
local function bits(buf: Bitbuf.Buffer, bits: string)
	if buf:Len() ~= #bits then
		errorf("expected length %d, got %d", #bits, buf:Len())
		return false
	end
	local got = explodeBuf(buf)
	if got ~= bits then
		errorf(compError(bits, got))
		return false
	end
	return true
end

type SuiteDef = {
	lengths: {number},
	sizes: {number},
	values: {{
		value: number,
		bytes: string,
		bits: string,
	}},
	ones: boolean?,
}

type Suite = {
	lengths: {number} | number | nil,
	sizes: {number} | number | nil,
	values: {SuiteValue} | number | nil,
	ones: boolean?,
}

type SuiteValue = {
	value: number,
	bytes: string,
	bits: string,
}

local _: Suite = (nil::any)::SuiteDef

-- Tests numeric data by generating values at various bit boundaries.
local unitSuite: SuiteDef = {
	lengths = {
		00, 01, 02,
		15, 16, 17,
		31, 32, 33,
		47, 48, 49,
		63, 64, 65,
	},
	sizes = {
		00, 01, 02, 03,
		07, 08, 09,
		15, 16, 17,
		23, 24, 25,
		30, 31, 32,
	},
	values = {
		{ value = -8589934594 --[[-2^33-2]] , bytes = "\xFE\xFF\xFF\xFF" , bits = "0111111111111111111111111111111110111111111111111111111111111111" },
		{ value = -8589934593 --[[-2^33-1]] , bytes = "\xFF\xFF\xFF\xFF" , bits = "1111111111111111111111111111111110111111111111111111111111111111" },
		{ value = -8589934592 --[[-2^33+0]] , bytes = "\x00\x00\x00\x00" , bits = "0000000000000000000000000000000001111111111111111111111111111111" },
		{ value = -8589934591 --[[-2^33+1]] , bytes = "\x01\x00\x00\x00" , bits = "1000000000000000000000000000000001111111111111111111111111111111" },
		{ value = -4294967298 --[[-2^32-2]] , bytes = "\xFE\xFF\xFF\xFF" , bits = "0111111111111111111111111111111101111111111111111111111111111111" },
		{ value = -4294967297 --[[-2^32-1]] , bytes = "\xFF\xFF\xFF\xFF" , bits = "1111111111111111111111111111111101111111111111111111111111111111" },
		{ value = -4294967296 --[[-2^32+0]] , bytes = "\x00\x00\x00\x00" , bits = "0000000000000000000000000000000011111111111111111111111111111111" },
		{ value = -4294967295 --[[-2^32+1]] , bytes = "\x01\x00\x00\x00" , bits = "1000000000000000000000000000000011111111111111111111111111111111" },
		{ value = -2147483650 --[[-2^31-2]] , bytes = "\xFE\xFF\xFF\x7F" , bits = "0111111111111111111111111111111011111111111111111111111111111111" },
		{ value = -2147483649 --[[-2^31-1]] , bytes = "\xFF\xFF\xFF\x7F" , bits = "1111111111111111111111111111111011111111111111111111111111111111" },
		{ value = -2147483648 --[[-2^31+0]] , bytes = "\x00\x00\x00\x80" , bits = "0000000000000000000000000000000111111111111111111111111111111111" },
		{ value = -2147483647 --[[-2^31+1]] , bytes = "\x01\x00\x00\x80" , bits = "1000000000000000000000000000000111111111111111111111111111111111" },
		{ value =   -33554434 --[[-2^25-2]] , bytes = "\xFE\xFF\xFF\xFD" , bits = "0111111111111111111111111011111111111111111111111111111111111111" },
		{ value =   -33554433 --[[-2^25-1]] , bytes = "\xFF\xFF\xFF\xFD" , bits = "1111111111111111111111111011111111111111111111111111111111111111" },
		{ value =   -33554432 --[[-2^25+0]] , bytes = "\x00\x00\x00\xFE" , bits = "0000000000000000000000000111111111111111111111111111111111111111" },
		{ value =   -33554431 --[[-2^25+1]] , bytes = "\x01\x00\x00\xFE" , bits = "1000000000000000000000000111111111111111111111111111111111111111" },
		{ value =   -16777218 --[[-2^24-2]] , bytes = "\xFE\xFF\xFF\xFE" , bits = "0111111111111111111111110111111111111111111111111111111111111111" },
		{ value =   -16777217 --[[-2^24-1]] , bytes = "\xFF\xFF\xFF\xFE" , bits = "1111111111111111111111110111111111111111111111111111111111111111" },
		{ value =   -16777216 --[[-2^24+0]] , bytes = "\x00\x00\x00\xFF" , bits = "0000000000000000000000001111111111111111111111111111111111111111" },
		{ value =   -16777215 --[[-2^24+1]] , bytes = "\x01\x00\x00\xFF" , bits = "1000000000000000000000001111111111111111111111111111111111111111" },
		{ value =    -8388610 --[[-2^23-2]] , bytes = "\xFE\xFF\x7F\xFF" , bits = "0111111111111111111111101111111111111111111111111111111111111111" },
		{ value =    -8388609 --[[-2^23-1]] , bytes = "\xFF\xFF\x7F\xFF" , bits = "1111111111111111111111101111111111111111111111111111111111111111" },
		{ value =    -8388608 --[[-2^23+0]] , bytes = "\x00\x00\x80\xFF" , bits = "0000000000000000000000011111111111111111111111111111111111111111" },
		{ value =    -8388607 --[[-2^23+1]] , bytes = "\x01\x00\x80\xFF" , bits = "1000000000000000000000011111111111111111111111111111111111111111" },
		{ value =     -131074 --[[-2^17-2]] , bytes = "\xFE\xFF\xFD\xFF" , bits = "0111111111111111101111111111111111111111111111111111111111111111" },
		{ value =     -131073 --[[-2^17-1]] , bytes = "\xFF\xFF\xFD\xFF" , bits = "1111111111111111101111111111111111111111111111111111111111111111" },
		{ value =     -131072 --[[-2^17+0]] , bytes = "\x00\x00\xFE\xFF" , bits = "0000000000000000011111111111111111111111111111111111111111111111" },
		{ value =     -131071 --[[-2^17+1]] , bytes = "\x01\x00\xFE\xFF" , bits = "1000000000000000011111111111111111111111111111111111111111111111" },
		{ value =      -65538 --[[-2^16-2]] , bytes = "\xFE\xFF\xFE\xFF" , bits = "0111111111111111011111111111111111111111111111111111111111111111" },
		{ value =      -65537 --[[-2^16-1]] , bytes = "\xFF\xFF\xFE\xFF" , bits = "1111111111111111011111111111111111111111111111111111111111111111" },
		{ value =      -65536 --[[-2^16+0]] , bytes = "\x00\x00\xFF\xFF" , bits = "0000000000000000111111111111111111111111111111111111111111111111" },
		{ value =      -65535 --[[-2^16+1]] , bytes = "\x01\x00\xFF\xFF" , bits = "1000000000000000111111111111111111111111111111111111111111111111" },
		{ value =      -32770 --[[-2^15-2]] , bytes = "\xFE\x7F\xFF\xFF" , bits = "0111111111111110111111111111111111111111111111111111111111111111" },
		{ value =      -32769 --[[-2^15-1]] , bytes = "\xFF\x7F\xFF\xFF" , bits = "1111111111111110111111111111111111111111111111111111111111111111" },
		{ value =      -32768 --[[-2^15+0]] , bytes = "\x00\x80\xFF\xFF" , bits = "0000000000000001111111111111111111111111111111111111111111111111" },
		{ value =      -32767 --[[-2^15+1]] , bytes = "\x01\x80\xFF\xFF" , bits = "1000000000000001111111111111111111111111111111111111111111111111" },
		{ value =        -514 --[[-2^ 9-2]] , bytes = "\xFE\xFD\xFF\xFF" , bits = "0111111110111111111111111111111111111111111111111111111111111111" },
		{ value =        -513 --[[-2^ 9-1]] , bytes = "\xFF\xFD\xFF\xFF" , bits = "1111111110111111111111111111111111111111111111111111111111111111" },
		{ value =        -512 --[[-2^ 9+0]] , bytes = "\x00\xFE\xFF\xFF" , bits = "0000000001111111111111111111111111111111111111111111111111111111" },
		{ value =        -511 --[[-2^ 9+1]] , bytes = "\x01\xFE\xFF\xFF" , bits = "1000000001111111111111111111111111111111111111111111111111111111" },
		{ value =        -258 --[[-2^ 8-2]] , bytes = "\xFE\xFE\xFF\xFF" , bits = "0111111101111111111111111111111111111111111111111111111111111111" },
		{ value =        -257 --[[-2^ 8-1]] , bytes = "\xFF\xFE\xFF\xFF" , bits = "1111111101111111111111111111111111111111111111111111111111111111" },
		{ value =        -256 --[[-2^ 8+0]] , bytes = "\x00\xFF\xFF\xFF" , bits = "0000000011111111111111111111111111111111111111111111111111111111" },
		{ value =        -255 --[[-2^ 8+1]] , bytes = "\x01\xFF\xFF\xFF" , bits = "1000000011111111111111111111111111111111111111111111111111111111" },
		{ value =        -130 --[[-2^ 7-2]] , bytes = "\x7E\xFF\xFF\xFF" , bits = "0111111011111111111111111111111111111111111111111111111111111111" },
		{ value =        -129 --[[-2^ 7-1]] , bytes = "\x7F\xFF\xFF\xFF" , bits = "1111111011111111111111111111111111111111111111111111111111111111" },
		{ value =        -128 --[[-2^ 7+0]] , bytes = "\x80\xFF\xFF\xFF" , bits = "0000000111111111111111111111111111111111111111111111111111111111" },
		{ value =        -127 --[[-2^ 7+1]] , bytes = "\x81\xFF\xFF\xFF" , bits = "1000000111111111111111111111111111111111111111111111111111111111" },
		{ value =          -4 --[[-2^ 1-2]] , bytes = "\xFC\xFF\xFF\xFF" , bits = "0011111111111111111111111111111111111111111111111111111111111111" },
		{ value =          -3 --[[-2^ 1-1]] , bytes = "\xFD\xFF\xFF\xFF" , bits = "1011111111111111111111111111111111111111111111111111111111111111" },
		{ value =          -2 --[[-2^ 1+0]] , bytes = "\xFE\xFF\xFF\xFF" , bits = "0111111111111111111111111111111111111111111111111111111111111111" },
		{ value =          -1 --[[-2^ 1+1]] , bytes = "\xFF\xFF\xFF\xFF" , bits = "1111111111111111111111111111111111111111111111111111111111111111" },
		{ value =           0 --[[ 2^ 1-2]] , bytes = "\x00\x00\x00\x00" , bits = "0000000000000000000000000000000000000000000000000000000000000000" },
		{ value =           1 --[[ 2^ 1-1]] , bytes = "\x01\x00\x00\x00" , bits = "1000000000000000000000000000000000000000000000000000000000000000" },
		{ value =           2 --[[ 2^ 1+0]] , bytes = "\x02\x00\x00\x00" , bits = "0100000000000000000000000000000000000000000000000000000000000000" },
		{ value =           3 --[[ 2^ 1+1]] , bytes = "\x03\x00\x00\x00" , bits = "1100000000000000000000000000000000000000000000000000000000000000" },
		{ value =         126 --[[ 2^ 7-2]] , bytes = "\x7E\x00\x00\x00" , bits = "0111111000000000000000000000000000000000000000000000000000000000" },
		{ value =         127 --[[ 2^ 7-1]] , bytes = "\x7F\x00\x00\x00" , bits = "1111111000000000000000000000000000000000000000000000000000000000" },
		{ value =         128 --[[ 2^ 7+0]] , bytes = "\x80\x00\x00\x00" , bits = "0000000100000000000000000000000000000000000000000000000000000000" },
		{ value =         129 --[[ 2^ 7+1]] , bytes = "\x81\x00\x00\x00" , bits = "1000000100000000000000000000000000000000000000000000000000000000" },
		{ value =         254 --[[ 2^ 8-2]] , bytes = "\xFE\x00\x00\x00" , bits = "0111111100000000000000000000000000000000000000000000000000000000" },
		{ value =         255 --[[ 2^ 8-1]] , bytes = "\xFF\x00\x00\x00" , bits = "1111111100000000000000000000000000000000000000000000000000000000" },
		{ value =         256 --[[ 2^ 8+0]] , bytes = "\x00\x01\x00\x00" , bits = "0000000010000000000000000000000000000000000000000000000000000000" },
		{ value =         257 --[[ 2^ 8+1]] , bytes = "\x01\x01\x00\x00" , bits = "1000000010000000000000000000000000000000000000000000000000000000" },
		{ value =         510 --[[ 2^ 9-2]] , bytes = "\xFE\x01\x00\x00" , bits = "0111111110000000000000000000000000000000000000000000000000000000" },
		{ value =         511 --[[ 2^ 9-1]] , bytes = "\xFF\x01\x00\x00" , bits = "1111111110000000000000000000000000000000000000000000000000000000" },
		{ value =         512 --[[ 2^ 9+0]] , bytes = "\x00\x02\x00\x00" , bits = "0000000001000000000000000000000000000000000000000000000000000000" },
		{ value =         513 --[[ 2^ 9+1]] , bytes = "\x01\x02\x00\x00" , bits = "1000000001000000000000000000000000000000000000000000000000000000" },
		{ value =       32766 --[[ 2^15-2]] , bytes = "\xFE\x7F\x00\x00" , bits = "0111111111111110000000000000000000000000000000000000000000000000" },
		{ value =       32767 --[[ 2^15-1]] , bytes = "\xFF\x7F\x00\x00" , bits = "1111111111111110000000000000000000000000000000000000000000000000" },
		{ value =       32768 --[[ 2^15+0]] , bytes = "\x00\x80\x00\x00" , bits = "0000000000000001000000000000000000000000000000000000000000000000" },
		{ value =       32769 --[[ 2^15+1]] , bytes = "\x01\x80\x00\x00" , bits = "1000000000000001000000000000000000000000000000000000000000000000" },
		{ value =       65534 --[[ 2^16-2]] , bytes = "\xFE\xFF\x00\x00" , bits = "0111111111111111000000000000000000000000000000000000000000000000" },
		{ value =       65535 --[[ 2^16-1]] , bytes = "\xFF\xFF\x00\x00" , bits = "1111111111111111000000000000000000000000000000000000000000000000" },
		{ value =       65536 --[[ 2^16+0]] , bytes = "\x00\x00\x01\x00" , bits = "0000000000000000100000000000000000000000000000000000000000000000" },
		{ value =       65537 --[[ 2^16+1]] , bytes = "\x01\x00\x01\x00" , bits = "1000000000000000100000000000000000000000000000000000000000000000" },
		{ value =      131070 --[[ 2^17-2]] , bytes = "\xFE\xFF\x01\x00" , bits = "0111111111111111100000000000000000000000000000000000000000000000" },
		{ value =      131071 --[[ 2^17-1]] , bytes = "\xFF\xFF\x01\x00" , bits = "1111111111111111100000000000000000000000000000000000000000000000" },
		{ value =      131072 --[[ 2^17+0]] , bytes = "\x00\x00\x02\x00" , bits = "0000000000000000010000000000000000000000000000000000000000000000" },
		{ value =      131073 --[[ 2^17+1]] , bytes = "\x01\x00\x02\x00" , bits = "1000000000000000010000000000000000000000000000000000000000000000" },
		{ value =     8388606 --[[ 2^23-2]] , bytes = "\xFE\xFF\x7F\x00" , bits = "0111111111111111111111100000000000000000000000000000000000000000" },
		{ value =     8388607 --[[ 2^23-1]] , bytes = "\xFF\xFF\x7F\x00" , bits = "1111111111111111111111100000000000000000000000000000000000000000" },
		{ value =     8388608 --[[ 2^23+0]] , bytes = "\x00\x00\x80\x00" , bits = "0000000000000000000000010000000000000000000000000000000000000000" },
		{ value =     8388609 --[[ 2^23+1]] , bytes = "\x01\x00\x80\x00" , bits = "1000000000000000000000010000000000000000000000000000000000000000" },
		{ value =    16777214 --[[ 2^24-2]] , bytes = "\xFE\xFF\xFF\x00" , bits = "0111111111111111111111110000000000000000000000000000000000000000" },
		{ value =    16777215 --[[ 2^24-1]] , bytes = "\xFF\xFF\xFF\x00" , bits = "1111111111111111111111110000000000000000000000000000000000000000" },
		{ value =    16777216 --[[ 2^24+0]] , bytes = "\x00\x00\x00\x01" , bits = "0000000000000000000000001000000000000000000000000000000000000000" },
		{ value =    16777217 --[[ 2^24+1]] , bytes = "\x01\x00\x00\x01" , bits = "1000000000000000000000001000000000000000000000000000000000000000" },
		{ value =    33554430 --[[ 2^25-2]] , bytes = "\xFE\xFF\xFF\x01" , bits = "0111111111111111111111111000000000000000000000000000000000000000" },
		{ value =    33554431 --[[ 2^25-1]] , bytes = "\xFF\xFF\xFF\x01" , bits = "1111111111111111111111111000000000000000000000000000000000000000" },
		{ value =    33554432 --[[ 2^25+0]] , bytes = "\x00\x00\x00\x02" , bits = "0000000000000000000000000100000000000000000000000000000000000000" },
		{ value =    33554433 --[[ 2^25+1]] , bytes = "\x01\x00\x00\x02" , bits = "1000000000000000000000000100000000000000000000000000000000000000" },
		{ value =  2147483646 --[[ 2^31-2]] , bytes = "\xFE\xFF\xFF\x7F" , bits = "0111111111111111111111111111111000000000000000000000000000000000" },
		{ value =  2147483647 --[[ 2^31-1]] , bytes = "\xFF\xFF\xFF\x7F" , bits = "1111111111111111111111111111111000000000000000000000000000000000" },
		{ value =  2147483648 --[[ 2^31+0]] , bytes = "\x00\x00\x00\x80" , bits = "0000000000000000000000000000000100000000000000000000000000000000" },
		{ value =  2147483649 --[[ 2^31+1]] , bytes = "\x01\x00\x00\x80" , bits = "1000000000000000000000000000000100000000000000000000000000000000" },
		{ value =  4294967294 --[[ 2^32-2]] , bytes = "\xFE\xFF\xFF\xFF" , bits = "0111111111111111111111111111111100000000000000000000000000000000" },
		{ value =  4294967295 --[[ 2^32-1]] , bytes = "\xFF\xFF\xFF\xFF" , bits = "1111111111111111111111111111111100000000000000000000000000000000" },
		{ value =  4294967296 --[[ 2^32+0]] , bytes = "\x00\x00\x00\x00" , bits = "0000000000000000000000000000000010000000000000000000000000000000" },
		{ value =  4294967297 --[[ 2^32+1]] , bytes = "\x01\x00\x00\x00" , bits = "1000000000000000000000000000000010000000000000000000000000000000" },
		{ value =  8589934590 --[[ 2^33-2]] , bytes = "\xFE\xFF\xFF\xFF" , bits = "0111111111111111111111111111111110000000000000000000000000000000" },
		{ value =  8589934591 --[[ 2^33-1]] , bytes = "\xFF\xFF\xFF\xFF" , bits = "1111111111111111111111111111111110000000000000000000000000000000" },
		{ value =  8589934592 --[[ 2^33+0]] , bytes = "\x00\x00\x00\x00" , bits = "0000000000000000000000000000000001000000000000000000000000000000" },
		{ value =  8589934593 --[[ 2^33+1]] , bytes = "\x01\x00\x00\x00" , bits = "1000000000000000000000000000000001000000000000000000000000000000" },
		{ value = -3713140662               , bytes = "\x4A\xF8\xAD\x22" , bits = "0101001000011111101101010100010011111111111111111111111111111111" },
		{ value = -3373259426               , bytes = "\x5E\x25\xF0\x36" , bits = "0111101010100100000011110110110011111111111111111111111111111111" },
		{ value = -3131314028               , bytes = "\x94\xF0\x5B\x45" , bits = "0010100100001111110110101010001011111111111111111111111111111111" },
		{ value = -2608337582               , bytes = "\x52\xED\x87\x64" , bits = "0100101010110111111000010010011011111111111111111111111111111111" },
		{ value = -2451551556               , bytes = "\xBC\x4A\xE0\x6D" , bits = "0011110101010010000001111011011011111111111111111111111111111111" },
		{ value = -2327306534               , bytes = "\xDA\x1E\x48\x75" , bits = "0101101101111000000100101010111011111111111111111111111111111111" },
		{ value = -1967660761               , bytes = "\x27\xE1\xB7\x8A" , bits = "1110010010000111111011010101000111111111111111111111111111111111" },
		{ value = -1843415739               , bytes = "\x45\xB5\x1F\x92" , bits = "1010001010101101111110000100100111111111111111111111111111111111" },
		{ value = -1686629713               , bytes = "\xAF\x12\x78\x9B" , bits = "1111010101001000000111101101100111111111111111111111111111111111" },
		{ value = -1163653267               , bytes = "\x6D\x0F\xA4\xBA" , bits = "1011011011110000001001010101110111111111111111111111111111111111" },
		{ value =  -921707869               , bytes = "\xA3\xDA\x0F\xC9" , bits = "1100010101011011111100001001001111111111111111111111111111111111" },
		{ value =  -581826633               , bytes = "\xB7\x07\x52\xDD" , bits = "1110110111100000010010101011101111111111111111111111111111111111" },
		{ value =   581826633               , bytes = "\x49\xF8\xAD\x22" , bits = "1001001000011111101101010100010000000000000000000000000000000000" },
		{ value =   921707869               , bytes = "\x5D\x25\xF0\x36" , bits = "1011101010100100000011110110110000000000000000000000000000000000" },
		{ value =  1163653267               , bytes = "\x93\xF0\x5B\x45" , bits = "1100100100001111110110101010001000000000000000000000000000000000" },
		{ value =  1686629713               , bytes = "\x51\xED\x87\x64" , bits = "1000101010110111111000010010011000000000000000000000000000000000" },
		{ value =  1843415739               , bytes = "\xBB\x4A\xE0\x6D" , bits = "1101110101010010000001111011011000000000000000000000000000000000" },
		{ value =  1967660761               , bytes = "\xD9\x1E\x48\x75" , bits = "1001101101111000000100101010111000000000000000000000000000000000" },
		{ value =  2327306534               , bytes = "\x26\xE1\xB7\x8A" , bits = "0110010010000111111011010101000100000000000000000000000000000000" },
		{ value =  2451551556               , bytes = "\x44\xB5\x1F\x92" , bits = "0010001010101101111110000100100100000000000000000000000000000000" },
		{ value =  2608337582               , bytes = "\xAE\x12\x78\x9B" , bits = "0111010101001000000111101101100100000000000000000000000000000000" },
		{ value =  3131314028               , bytes = "\x6C\x0F\xA4\xBA" , bits = "0011011011110000001001010101110100000000000000000000000000000000" },
		{ value =  3373259426               , bytes = "\xA2\xDA\x0F\xC9" , bits = "0100010101011011111100001001001100000000000000000000000000000000" },
		{ value =  3713140662               , bytes = "\xB6\x07\x52\xDD" , bits = "0110110111100000010010101011101100000000000000000000000000000000" },
	},
}

-- Supplements unitSuite with values above 32 bits.
local intSuite: SuiteDef = {
	lengths = {},
	sizes = {
		39, 40, 41,
		47, 48, 49,
		51, 52, 53,
	},
	values = {
--		{ value = -9007199254740994 --[[-2^53-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFF\xDF\xFF" , bits = "0111111111111111111111111111111111111111111111111111101111111111" },
--		{ value = -9007199254740993 --[[-2^53-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFF\xDF\xFF" , bits = "1111111111111111111111111111111111111111111111111111101111111111" },
		{ value = -9007199254740992 --[[-2^53+0]] , bytes = "\x00\x00\x00\x00\x00\x00\xE0\xFF" , bits = "0000000000000000000000000000000000000000000000000000011111111111" },
		{ value = -9007199254740991 --[[-2^53+1]] , bytes = "\x01\x00\x00\x00\x00\x00\xE0\xFF" , bits = "1000000000000000000000000000000000000000000000000000011111111111" },
		{ value = -4503599627370498 --[[-2^52-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFF\xEF\xFF" , bits = "0111111111111111111111111111111111111111111111111111011111111111" },
		{ value = -4503599627370497 --[[-2^52-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFF\xEF\xFF" , bits = "1111111111111111111111111111111111111111111111111111011111111111" },
		{ value = -4503599627370496 --[[-2^52+0]] , bytes = "\x00\x00\x00\x00\x00\x00\xF0\xFF" , bits = "0000000000000000000000000000000000000000000000000000111111111111" },
		{ value = -4503599627370495 --[[-2^52+1]] , bytes = "\x01\x00\x00\x00\x00\x00\xF0\xFF" , bits = "1000000000000000000000000000000000000000000000000000111111111111" },
		{ value = -2251799813685250 --[[-2^51-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFF\xF7\xFF" , bits = "0111111111111111111111111111111111111111111111111110111111111111" },
		{ value = -2251799813685249 --[[-2^51-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFF\xF7\xFF" , bits = "1111111111111111111111111111111111111111111111111110111111111111" },
		{ value = -2251799813685248 --[[-2^51+0]] , bytes = "\x00\x00\x00\x00\x00\x00\xF8\xFF" , bits = "0000000000000000000000000000000000000000000000000001111111111111" },
		{ value = -2251799813685247 --[[-2^51+1]] , bytes = "\x01\x00\x00\x00\x00\x00\xF8\xFF" , bits = "1000000000000000000000000000000000000000000000000001111111111111" },
		{ value =  -562949953421314 --[[-2^49-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFF\xFD\xFF" , bits = "0111111111111111111111111111111111111111111111111011111111111111" },
		{ value =  -562949953421313 --[[-2^49-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFF\xFD\xFF" , bits = "1111111111111111111111111111111111111111111111111011111111111111" },
		{ value =  -562949953421312 --[[-2^49+0]] , bytes = "\x00\x00\x00\x00\x00\x00\xFE\xFF" , bits = "0000000000000000000000000000000000000000000000000111111111111111" },
		{ value =  -562949953421311 --[[-2^49+1]] , bytes = "\x01\x00\x00\x00\x00\x00\xFE\xFF" , bits = "1000000000000000000000000000000000000000000000000111111111111111" },
		{ value =  -281474976710658 --[[-2^48-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFF\xFE\xFF" , bits = "0111111111111111111111111111111111111111111111110111111111111111" },
		{ value =  -281474976710657 --[[-2^48-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFF\xFE\xFF" , bits = "1111111111111111111111111111111111111111111111110111111111111111" },
		{ value =  -281474976710656 --[[-2^48+0]] , bytes = "\x00\x00\x00\x00\x00\x00\xFF\xFF" , bits = "0000000000000000000000000000000000000000000000001111111111111111" },
		{ value =  -281474976710655 --[[-2^48+1]] , bytes = "\x01\x00\x00\x00\x00\x00\xFF\xFF" , bits = "1000000000000000000000000000000000000000000000001111111111111111" },
		{ value =  -140737488355330 --[[-2^47-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\x7F\xFF\xFF" , bits = "0111111111111111111111111111111111111111111111101111111111111111" },
		{ value =  -140737488355329 --[[-2^47-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\x7F\xFF\xFF" , bits = "1111111111111111111111111111111111111111111111101111111111111111" },
		{ value =  -140737488355328 --[[-2^47+0]] , bytes = "\x00\x00\x00\x00\x00\x80\xFF\xFF" , bits = "0000000000000000000000000000000000000000000000011111111111111111" },
		{ value =  -140737488355327 --[[-2^47+1]] , bytes = "\x01\x00\x00\x00\x00\x80\xFF\xFF" , bits = "1000000000000000000000000000000000000000000000011111111111111111" },
		{ value =    -2199023255554 --[[-2^41-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFD\xFF\xFF" , bits = "0111111111111111111111111111111111111111101111111111111111111111" },
		{ value =    -2199023255553 --[[-2^41-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFD\xFF\xFF" , bits = "1111111111111111111111111111111111111111101111111111111111111111" },
		{ value =    -2199023255552 --[[-2^41+0]] , bytes = "\x00\x00\x00\x00\x00\xFE\xFF\xFF" , bits = "0000000000000000000000000000000000000000011111111111111111111111" },
		{ value =    -2199023255551 --[[-2^41+1]] , bytes = "\x01\x00\x00\x00\x00\xFE\xFF\xFF" , bits = "1000000000000000000000000000000000000000011111111111111111111111" },
		{ value =    -1099511627778 --[[-2^40-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFE\xFF\xFF" , bits = "0111111111111111111111111111111111111111011111111111111111111111" },
		{ value =    -1099511627777 --[[-2^40-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFE\xFF\xFF" , bits = "1111111111111111111111111111111111111111011111111111111111111111" },
		{ value =    -1099511627776 --[[-2^40+0]] , bytes = "\x00\x00\x00\x00\x00\xFF\xFF\xFF" , bits = "0000000000000000000000000000000000000000111111111111111111111111" },
		{ value =    -1099511627775 --[[-2^40+1]] , bytes = "\x01\x00\x00\x00\x00\xFF\xFF\xFF" , bits = "1000000000000000000000000000000000000000111111111111111111111111" },
		{ value =     -549755813890 --[[-2^39-2]] , bytes = "\xFE\xFF\xFF\xFF\x7F\xFF\xFF\xFF" , bits = "0111111111111111111111111111111111111110111111111111111111111111" },
		{ value =     -549755813889 --[[-2^39-1]] , bytes = "\xFF\xFF\xFF\xFF\x7F\xFF\xFF\xFF" , bits = "1111111111111111111111111111111111111110111111111111111111111111" },
		{ value =     -549755813888 --[[-2^39+0]] , bytes = "\x00\x00\x00\x00\x80\xFF\xFF\xFF" , bits = "0000000000000000000000000000000000000001111111111111111111111111" },
		{ value =     -549755813887 --[[-2^39+1]] , bytes = "\x01\x00\x00\x00\x80\xFF\xFF\xFF" , bits = "1000000000000000000000000000000000000001111111111111111111111111" },
		{ value =      549755813886 --[[ 2^39-2]] , bytes = "\xFE\xFF\xFF\xFF\x7F\x00\x00\x00" , bits = "0111111111111111111111111111111111111110000000000000000000000000" },
		{ value =      549755813887 --[[ 2^39-1]] , bytes = "\xFF\xFF\xFF\xFF\x7F\x00\x00\x00" , bits = "1111111111111111111111111111111111111110000000000000000000000000" },
		{ value =      549755813888 --[[ 2^39+0]] , bytes = "\x00\x00\x00\x00\x80\x00\x00\x00" , bits = "0000000000000000000000000000000000000001000000000000000000000000" },
		{ value =      549755813889 --[[ 2^39+1]] , bytes = "\x01\x00\x00\x00\x80\x00\x00\x00" , bits = "1000000000000000000000000000000000000001000000000000000000000000" },
		{ value =     1099511627774 --[[ 2^40-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\x00\x00\x00" , bits = "0111111111111111111111111111111111111111000000000000000000000000" },
		{ value =     1099511627775 --[[ 2^40-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\x00\x00\x00" , bits = "1111111111111111111111111111111111111111000000000000000000000000" },
		{ value =     1099511627776 --[[ 2^40+0]] , bytes = "\x00\x00\x00\x00\x00\x01\x00\x00" , bits = "0000000000000000000000000000000000000000100000000000000000000000" },
		{ value =     1099511627777 --[[ 2^40+1]] , bytes = "\x01\x00\x00\x00\x00\x01\x00\x00" , bits = "1000000000000000000000000000000000000000100000000000000000000000" },
		{ value =     2199023255550 --[[ 2^41-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\x01\x00\x00" , bits = "0111111111111111111111111111111111111111100000000000000000000000" },
		{ value =     2199023255551 --[[ 2^41-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\x01\x00\x00" , bits = "1111111111111111111111111111111111111111100000000000000000000000" },
		{ value =     2199023255552 --[[ 2^41+0]] , bytes = "\x00\x00\x00\x00\x00\x02\x00\x00" , bits = "0000000000000000000000000000000000000000010000000000000000000000" },
		{ value =     2199023255553 --[[ 2^41+1]] , bytes = "\x01\x00\x00\x00\x00\x02\x00\x00" , bits = "1000000000000000000000000000000000000000010000000000000000000000" },
		{ value =   140737488355326 --[[ 2^47-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\x7F\x00\x00" , bits = "0111111111111111111111111111111111111111111111100000000000000000" },
		{ value =   140737488355327 --[[ 2^47-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\x7F\x00\x00" , bits = "1111111111111111111111111111111111111111111111100000000000000000" },
		{ value =   140737488355328 --[[ 2^47+0]] , bytes = "\x00\x00\x00\x00\x00\x80\x00\x00" , bits = "0000000000000000000000000000000000000000000000010000000000000000" },
		{ value =   140737488355329 --[[ 2^47+1]] , bytes = "\x01\x00\x00\x00\x00\x80\x00\x00" , bits = "1000000000000000000000000000000000000000000000010000000000000000" },
		{ value =   281474976710654 --[[ 2^48-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFF\x00\x00" , bits = "0111111111111111111111111111111111111111111111110000000000000000" },
		{ value =   281474976710655 --[[ 2^48-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00" , bits = "1111111111111111111111111111111111111111111111110000000000000000" },
		{ value =   281474976710656 --[[ 2^48+0]] , bytes = "\x00\x00\x00\x00\x00\x00\x01\x00" , bits = "0000000000000000000000000000000000000000000000001000000000000000" },
		{ value =   281474976710657 --[[ 2^48+1]] , bytes = "\x01\x00\x00\x00\x00\x00\x01\x00" , bits = "1000000000000000000000000000000000000000000000001000000000000000" },
		{ value =   562949953421310 --[[ 2^49-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFF\x01\x00" , bits = "0111111111111111111111111111111111111111111111111000000000000000" },
		{ value =   562949953421311 --[[ 2^49-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFF\x01\x00" , bits = "1111111111111111111111111111111111111111111111111000000000000000" },
		{ value =   562949953421312 --[[ 2^49+0]] , bytes = "\x00\x00\x00\x00\x00\x00\x02\x00" , bits = "0000000000000000000000000000000000000000000000000100000000000000" },
		{ value =   562949953421313 --[[ 2^49+1]] , bytes = "\x01\x00\x00\x00\x00\x00\x02\x00" , bits = "1000000000000000000000000000000000000000000000000100000000000000" },
		{ value =  2251799813685246 --[[ 2^51-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFF\x07\x00" , bits = "0111111111111111111111111111111111111111111111111110000000000000" },
		{ value =  2251799813685247 --[[ 2^51-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFF\x07\x00" , bits = "1111111111111111111111111111111111111111111111111110000000000000" },
		{ value =  2251799813685248 --[[ 2^51+0]] , bytes = "\x00\x00\x00\x00\x00\x00\x08\x00" , bits = "0000000000000000000000000000000000000000000000000001000000000000" },
		{ value =  2251799813685249 --[[ 2^51+1]] , bytes = "\x01\x00\x00\x00\x00\x00\x08\x00" , bits = "1000000000000000000000000000000000000000000000000001000000000000" },
		{ value =  4503599627370494 --[[ 2^52-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFF\x0F\x00" , bits = "0111111111111111111111111111111111111111111111111111000000000000" },
		{ value =  4503599627370495 --[[ 2^52-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFF\x0F\x00" , bits = "1111111111111111111111111111111111111111111111111111000000000000" },
		{ value =  4503599627370496 --[[ 2^52+0]] , bytes = "\x00\x00\x00\x00\x00\x00\x10\x00" , bits = "0000000000000000000000000000000000000000000000000000100000000000" },
		{ value =  4503599627370497 --[[ 2^52+1]] , bytes = "\x01\x00\x00\x00\x00\x00\x10\x00" , bits = "1000000000000000000000000000000000000000000000000000100000000000" },
		{ value =  9007199254740990 --[[ 2^53-2]] , bytes = "\xFE\xFF\xFF\xFF\xFF\xFF\x1F\x00" , bits = "0111111111111111111111111111111111111111111111111111100000000000" },
		{ value =  9007199254740991 --[[ 2^53-1]] , bytes = "\xFF\xFF\xFF\xFF\xFF\xFF\x1F\x00" , bits = "1111111111111111111111111111111111111111111111111111100000000000" },
		{ value =  9007199254740992 --[[ 2^53+0]] , bytes = "\x00\x00\x00\x00\x00\x00\x20\x00" , bits = "0000000000000000000000000000000000000000000000000000010000000000" },
--		{ value =  9007199254740993 --[[ 2^53+1]] , bytes = "\x01\x00\x00\x00\x00\x00\x20\x00" , bits = "1000000000000000000000000000000000000000000000000000010000000000" },
	},
}

intSuite = {
	lengths = join(unitSuite.lengths, intSuite.lengths),
	sizes = join(unitSuite.sizes, intSuite.sizes),
	values = join(unitSuite.values, intSuite.values),
}

type SuiteCallbackBuf = (buf: _Buffer, length: number, index: number, size: number, value: SuiteValue) -> (string?, ...any)
type SuiteCallbackNil = (buf: nil, length: number, index: number, size: number, value: SuiteValue) -> (string?, ...any)
type SuiteCallback = (buf: _Buffer?, length: number, index: number, size: number, value: SuiteValue) -> (string?, ...any)

type TestSuiteBuf = (expect: Spek.Clause<Spek.Assertion>, bb: typeof(Bitbuf), suite: Suite, cb: SuiteCallbackBuf) -> ()
type TestSuiteNil = (expect: Spek.Clause<Spek.Assertion>, bb: nil, suite: Suite, cb: SuiteCallbackNil) -> ()
type TestSuite = TestSuiteBuf & TestSuiteNil

local testSuite: TestSuite =
	function (expect: Spek.Clause<Spek.Assertion>, bb: typeof(Bitbuf)?, suite: Suite, cb: SuiteCallback)
		local lengths = suite.lengths
		if not lengths then
			lengths = {0}
		elseif type(lengths) == "number" then
			local a = table.create(lengths+1, 0)
			for i in a do
				a[i] = i-1
			end
			lengths = a
		end
		local lengths: {number} = lengths :: any

		local sizes = suite.sizes
		if not sizes then
			sizes = {0}
		elseif type(sizes) == "number" then
			local a = table.create(sizes+1, 0)
			for i in a do
				a[i] = i-1
			end
			sizes = a
		end
		local sizes: {number} = sizes :: any

		local values = suite.values
		if not values then
			values = {{value=0, bytes="\0", bits="0"}}
		elseif type(values) == "number" then
			local a: {SuiteValue} = table.create(values+1, 0) :: any
			for i in a do
				a[i] = {value = i-1, bytes = "TODO", bits = explodeByte[i-1]}
			end
			values = a
		end
		local values: {SuiteValue} = values :: any

		local buf: _Buffer?
		if Bitbuf then
			buf = Bitbuf.new(sizes[#sizes]) :: _Buffer
		end

		local errors = {}
		pcall(function()
			-- Number of seconds before forcing a yield.
			local unitTestInterval = 1

			local time = os.clock()
			for l, length in lengths do
				for i = 1, l do
					local index = lengths[i]
					for _, size in sizes do
						for _, value in values do
							if buf then
								resetBuf(buf, length, index, not not suite.ones)
							end
							local r = {cb(buf, length, index, size, value)}
							if r[1] then
								table.insert(errors, string.format("[%d:%d:%d:%d]: " .. r[1]::any,
									length, index, size, value.value, table.unpack(r, 2)))
							end
							if #errors >= 10 then
								-- Jump out of all loops.
								error("too many errors")
							end
							if os.clock()-time >= unitTestInterval then
								task.wait()
								time = os.clock()
							end
						end
					end
				end
			end
		end)
		expect(function()
			if #errors == 0 then
				return true
			end
			return false, table.concat(errors, "\n")
		end)
	end :: any

-- Returns a function that produces an expect of bits.
local function bit_expector(expect: Spek.Clause<Spek.Assertion>): (buf: Bitbuf.Buffer, b: string) -> ()
	return function(buf: Bitbuf.Buffer, b: string)
		expect(function()
			return bits(buf, b)
		end)
	end
end

local pi64Bits = string.pack("<d", math.pi)
local pi32Bits = string.pack("<f", math.pi)

return function(t: Spek.T)
	local describe = t.describe
	local before_each = t.before_each
	local it = t.it
	local expect = t.expect
	local expect_error = t.expect_error
	local parameter = t.parameter
	local measure = t.measure
	local operation = t.operation
	local expect_bits = bit_expector(expect)

	describe "new" (function()
		it "should create a zero-length buffer with no arguments" (function()
			expect(function()
				return Bitbuf.new():Len() == 0
			end)
		end)
		it "should set buffer length from argument" (function()
			expect(function()
				return Bitbuf.new(42):Len() == 42
			end)
		end)
		it "initialize the index to zero" (function()
			expect(function()
				return Bitbuf.new(42):Index() == 0
			end)
		end)
		it "initialize bits with zeros" (function()
			expect_bits(Bitbuf.new(42), "000000000000000000000000000000000000000000")
		end)
	end)

	describe "fromString" (function()
		it "should produce correct bits for 8 bytes" (function() expect_bits(Bitbuf.fromString(string.sub(pi64Bits, 1, 8)), "0001100010110100001000100010101011011111100001001001000000000010") end)
		it "should produce correct bits for 7 bytes" (function() expect_bits(Bitbuf.fromString(string.sub(pi64Bits, 1, 7)), "00011000101101000010001000101010110111111000010010010000") end)
		it "should produce correct bits for 6 bytes" (function() expect_bits(Bitbuf.fromString(string.sub(pi64Bits, 1, 6)), "000110001011010000100010001010101101111110000100") end)
		it "should produce correct bits for 5 bytes" (function() expect_bits(Bitbuf.fromString(string.sub(pi64Bits, 1, 5)), "0001100010110100001000100010101011011111") end)
		it "should produce correct bits for 4 bytes" (function() expect_bits(Bitbuf.fromString(string.sub(pi64Bits, 1, 4)), "00011000101101000010001000101010") end)
		it "should produce correct bits for 3 bytes" (function() expect_bits(Bitbuf.fromString(string.sub(pi64Bits, 1, 3)), "000110001011010000100010") end)
		it "should produce correct bits for 2 bytes" (function() expect_bits(Bitbuf.fromString(string.sub(pi64Bits, 1, 2)), "0001100010110100") end)
		it "should produce correct bits for 1 bytes" (function() expect_bits(Bitbuf.fromString(string.sub(pi64Bits, 1, 1)), "00011000") end)
		it "should produce correct bits for 0 bytes" (function() expect_bits(Bitbuf.fromString(""), "") end)
	end)

	describe "Buffer.String" (function()
		it "should produce round-trip strings" (function()
			local bytes = "\x18\x2D\x44\x54\xFB\x21\x09\x40"
			for i = 0, #bytes, 1 do
				expect(function()
					local want = string.sub(bytes, 1, i)
					local buf = Bitbuf.fromString(want)
					local got = buf:String()
					if got ~= want then
						errorf(compError(want, buf:String()))
					end
					return true
				end)
			end
		end)
		it "should produce zeroed trailing bits" (function()
			local buf = Bitbuf.new()
			for l = 0, 64 do
				expect(function()
					resetBuf(buf::_Buffer, l, 0, true)
					local want = string.rep("1", l) .. string.rep("0", math.ceil(l/8)*8-l)
					local got = explode(buf:String())
					if got ~= want then
						errorf(compError(want, got))
					end
					return true
				end)
			end
		end)
	end)

	describe "Buffer.writeUnit" (function()
		it "should pass the unit test suite" (function()
			testSuite(expect, Bitbuf, unitSuite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:writeUnit(s, v.value)

				local expi = i + s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("0", i) .. string.sub(v.bits, 1, s) .. string.rep("0", l-i-s)
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.readUnit" (function()
		it "should pass the unit test suite" (function()
			testSuite(expect, Bitbuf, unitSuite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:writeUnit(s, v.value)
				buf:SetIndex(i)
				local value = buf:readUnit(s)

				local expi = i + s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = bit32.band(v.value, 2^s-1)
				if value ~= want then
					return "expected value %d, got %d", want, value
				end
				return
			end)
		end)
	end)

	describe "Buffer.Len" (function()
		it "should return the number of bits of a new buffer" (function()
			for i = 0, 256 do
				expect(function()
					local n = Bitbuf.new(i):Len()
					if n ~= i then
						return false, string.format("length %d expected, got %d", i, n)
					end
					return true
				end)
			end
		end)
	end)

	describe "Buffer.SetLen" (function()
		it "should pass the test suite" (function()
			testSuite(expect, nil, {lengths = unitSuite.lengths}, function(_, l: number, i: number, _, _): (string?, ...any)
				local buf = Bitbuf.new()
				buf:SetIndex(i)
				buf:SetLen(l)
				if buf:Len() ~= l then
					return "expected length %d, got %d", l, buf:Len()
				end
				local expi = math.min(i, l)
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("0", l)
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.Index" (function()
		it "should return 0 for a new buffer" (function()
			expect(function()
				return Bitbuf.new(42):Index() == 0
			end)
		end)

		it "should equal value passed to SetIndex" (function()
			local buf = Bitbuf.new()

			expect "buffer index to be 0" (function() return buf:Index() == 0 end)
			expect "buffer length to be 0" (function() return buf:Len() == 0 end)
			expect_bits(buf, "")

			buf:SetIndex(10)
			expect "buffer index to be set to 10" (function() return buf:Index() == 10 end)
			expect "buffer length to grow to 10" (function() return buf:Len() == 10 end)
			expect_bits(buf, "0000000000")

			buf:SetIndex(5)
			expect "buffer index to be set to 5" (function() return buf:Index() == 5 end)
			expect "buffer length to still be 10" (function() return buf:Len() == 10 end)
			expect_bits(buf, "0000000000")

			buf:SetIndex(202)
			expect "buffer index to be set to 202" (function() return buf:Index() == 202 end)
			expect "buffer length to grow to 202" (function() return buf:Len() == 202 end)
			expect_bits(buf, string.rep("0", 202))

			buf:SetIndex(20)
			expect "buffer index to be set to 20" (function() return buf:Index() == 20 end)
			expect "buffer length to still be 202" (function() return buf:Len() == 202 end)
			expect_bits(buf, string.rep("0", 202))

			buf:SetIndex(-10)
			expect "setting negative buffer index to clamp to 0" (function() return buf:Index() == 0 end)
			expect_bits(buf, string.rep("0", 202))
		end)

		it "should automatically grow to set index" (function()
			local buf = Bitbuf.new()
			for i = 0, 256 do
				buf:SetIndex(i)
				expect(function()
					if buf:Index() ~= i then
						return false, string.format("[%d]: got index %d", i, buf:Index())
					end
					return true
				end)
				expect(function()
					if buf:Len() ~= i then
						return false, string.format("[%d]: got length %d", i, buf:Len())
					end
					return true
				end)
				expect(function()
					local want = string.rep("0", i)
					local got = explodeBuf(buf)
					if got ~= want then
						local r = {compError(want, got)}
						r[1] = string.format("[%d]: %s", i, r[1])
						return false, string.format(unpack(r))
					end
					return true
				end)
			end
		end)
	end)

	describe "Buffer.Fits" (function()
		it "should pass test suite" (function()
			local suite = {
				lengths = unitSuite.lengths,
				sizes = unitSuite.sizes,
			}
			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				if buf:Fits(s) ~= (i+s <= l) then
					return "%d+%d > %d", i, s, l
				end
				return
			end)
		end)
	end)

	describe "Buffer.WritePad" (function()
		it "should pass test suite" (function()
			local suite = {
				lengths = unitSuite.lengths,
				sizes = unitSuite.sizes,
				ones = true,
			}
			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:WritePad(s)

				local expi = i + s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("1", i) .. string.rep("0", s) .. string.rep("1", l-i-s)
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.ReadPad" (function()
		it "should pass test suite" (function()
			local suite = {
				lengths = unitSuite.lengths,
				sizes = unitSuite.sizes,
				ones = true,
			}
			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:ReadPad(s)

				local expi = i + s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("1", l) .. string.rep("0", explen-l)
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.WriteAlign" (function()
		it "should pass test suite" (function()
			local suite = {
				lengths = unitSuite.lengths,
				sizes = unitSuite.sizes,
				ones = true,
			}
			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:WriteAlign(s)

				local expi = s == 0 and i or math.ceil(i/s)*s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("1", i) .. string.rep("0", expi-i) .. string.rep("1", l-i-(expi-i))
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.ReadAlign" (function()
		it "should pass test suite" (function()
			local suite = {
				lengths = unitSuite.lengths,
				sizes = unitSuite.sizes,
				ones = true,
			}

			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:ReadAlign(s)

				local expi = s == 0 and i or math.ceil(i/s)*s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("1", l) .. string.rep("0", explen-l)
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.Reset" (function()
		local buf
		before_each(function()
			buf = Bitbuf.fromString(pi64Bits)
			buf:Reset()
		end)
		it "should reset the length to 0" (function()
			expect(function()
				return buf:Len() == 0
			end)
		end)
		it "should reset the index to 0" (function()
			expect(function()
				return buf:Index() == 0
			end)
		end)
		it "should reset to an empty buffer" (function()
			expect(function()
				return buf:String() == ""
			end)
		end)
	end)

	describe "Buffer.WriteBytes" (function()
		it "should pass test suite" (function()
			local suite = {
				lengths = unitSuite.lengths,
				sizes = unitSuite.sizes,
			}

			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				local b = ones(s)
				buf:WriteBytes(b)

				local expi = i + #b*8
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("0", i) .. string.rep("1", s) .. string.rep("0", explen-i-s)
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.ReadBytes" (function()
		it "should pass test suite" (function()
			local suite = {
				lengths = unitSuite.lengths,
				sizes = unitSuite.sizes,
			}

			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				local a = ones(s)
				buf:WriteBytes(a)
				buf:SetIndex(i)
				local exps = math.ceil(s/8)
				local b = buf:ReadBytes(exps)

				local expi = i + #a*8
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("1", s) .. string.rep("0", exps*8-s)
				local got = explode(b)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.WriteUint" (function()
		it "should fail when size is 54" (function()
			expect_error(function()
				Bitbuf.new():WriteUint(54, 0)
			end)
		end)
		it "should fail when size is -1" (function()
			expect_error(function()
				Bitbuf.new():WriteUint(-1, 0)
			end)
		end)
		it "should pass test suite" (function()
			testSuite(expect, Bitbuf, intSuite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:WriteUint(s, v.value)

				local expi = i + s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("0", i) .. string.sub(v.bits, 1, s) .. string.rep("0", explen-i-s)
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.ReadUint" (function()
		it "should pass test suite" (function()
			testSuite(expect, Bitbuf, intSuite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:WriteUint(s, v.value)
				buf:SetIndex(i)
				local value = buf:ReadUint(s)

				local expi = i + s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = v.value % 2^s
				if value ~= want then
					return "expected value %d, got %d", want, value
				end
				return
			end)
		end)
	end)

	describe "Buffer.WriteBool" (function()
		it "should pass test suite" (function()
			local suite = {
				lengths = unitSuite.lengths,
				sizes = unitSuite.sizes,
				values = {
					{value = 0, bytes="", bits=""},
					{value = 1, bytes="", bits=""},
				},
			}

			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				for i = 1, s do
					buf:WriteBool(v.value == 1)
				end

				local expi = i + s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("0", i) .. string.rep(v.value == 1 and "1" or "0", s) .. string.rep("0", explen-i-s)
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.ReadBool" (function()
		it "should pass test suite with false" (function()
			local suite = {
				lengths = unitSuite.lengths,
				sizes = unitSuite.sizes,
			}
			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				for j = 1, s do
					if buf:ReadBool() ~= false then
						return "expected false"
					end
					local expi = i + j
					local explen = math.max(expi, l)
					if buf:Len() ~= explen then
						return "expected length %d, got %d", explen, buf:Len()
					end
					if buf:Index() ~= expi then
						return "expected index %d, got %d", expi, buf:Index()
					end
				end
				return
			end)
		end)

		it "should pass test suite with true" (function()
			local suite = {
				lengths = unitSuite.lengths,
				sizes = unitSuite.sizes,
				ones = true,
			}
			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				for j = 1, s do
					local v = i+j <= l
					if buf:ReadBool() ~= v then
						return "expected " .. (v and "true" or "false")
					end
					local expi = i + j
					local explen = math.max(expi, l)
					if buf:Len() ~= explen then
						return "expected length %d, got %d", explen, buf:Len()
					end
					if buf:Index() ~= expi then
						return "expected index %d, got %d", expi, buf:Index()
					end
				end
				return
			end)
		end)
	end)

	describe "Buffer.WriteByte" (function()
		it "should pass test suite" (function()
			local suite = {
				lengths = unitSuite.lengths,
				values = 255,
			}
			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:WriteByte(v.value)

				local expi = i + 8
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("0", i) .. v.bits .. string.rep("0", explen-i-8)
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	describe "Buffer.ReadByte" (function()
		it "should pass test suite" (function()
			local suite = {
				lengths = unitSuite.lengths,
				values = 255,
			}
			testSuite(expect, Bitbuf, suite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:WriteByte(v.value)
				buf:SetIndex(i)
				local value = buf:ReadByte()

				local expi = i + 8
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = v.value
				if value ~= want then
					return "expected value %d, got %d", want, value
				end
				return
			end)
		end)
	end)

	describe "Buffer.WriteInt" (function()
		it "should fail when size is 54" (function()
			expect_error(function()
				Bitbuf.new():WriteInt(54, 0)
			end)
		end)
		it "should fail when size is -1" (function()
			expect_error(function()
				Bitbuf.new():WriteInt(-1, 0)
			end)
		end)
		it "should pass test suite" (function()
			testSuite(expect, Bitbuf, intSuite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:WriteInt(s, v.value)

				local expi = i + s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = string.rep("0", i) .. string.sub(v.bits, 1, s) .. string.rep("0", explen-i-s)
				local got = explodeBuf(buf)
				return compare(want, got)
			end)
		end)
	end)

	local function uint2int(size: number, v: number): number
		local n = 2^size
		v = v % n
		if v >= n/2 then
			return v - n
		end
		return v
	end

	describe "Buffer.ReadInt" (function()
		it "should pass test suite" (function()
			testSuite(expect, Bitbuf, intSuite, function(buf: _Buffer, l: number, i: number, s: number, v: SuiteValue): (string?, ...any)
				buf:WriteInt(s, v.value)
				buf:SetIndex(i)
				local value = buf:ReadInt(s)

				local expi = i + s
				local explen = math.max(expi, l)
				if buf:Len() ~= explen then
					return "expected length %d, got %d", explen, buf:Len()
				end
				if buf:Index() ~= expi then
					return "expected index %d, got %d", expi, buf:Index()
				end
				local want = uint2int(s, v.value)
				if value ~= want then
					return "expected value %d, got %d", want, value
				end
				return
			end)
		end)
	end)

	describe "Buffer.WriteFloat" (function()
		it "should write 32 bit numbers" (function()
			local buf = Bitbuf.new()
			buf:WriteFloat(32, math.pi)
			expect "length to be 32" (function()
				return buf:Len() == 32
			end)
			expect "index to be 32" (function()
				return buf:Index() == 32
			end)
			expect "correct string to be produced" (function()
				return buf:String() == pi32Bits
			end)
		end)

		it "should write 64 bit numbers" (function()
			local buf = Bitbuf.new()
			buf:WriteFloat(64, math.pi)
			expect "length to be 64" (function()
				return buf:Len() == 64
			end)
			expect "index to be 64" (function()
				return buf:Index() == 64
			end)
			expect "correct string to be produced" (function()
				return buf:String() == pi64Bits
			end)
		end)
	end)

	describe "Buffer.ReadFloat" (function()
		it "should read 32 bit numbers" (function()
			local pi32 = string.unpack("<f", string.pack("<f", math.pi))
			local buf = Bitbuf.fromString(pi32Bits)
			expect "32 bit value" (function()
				return buf:ReadFloat(32) == pi32
			end)
			expect "index to be 32" (function()
				return buf:Index() == 32
			end)
		end)
		it "should read 32 bit numbers" (function()
			local buf = Bitbuf.fromString(pi64Bits)
			expect "64 bit value" (function()
				return buf:ReadFloat(64) == math.pi
			end)
			expect "index to be 64" (function()
				return buf:Index() == 64
			end)
		end)
		it "should fail when receiving invald size" (function()
			local buf = Bitbuf.fromString(pi64Bits)
			expect_error (function()
				buf:ReadFloat(1)
			end)
		end)
	end)

	describe "Buffer.WriteUfixed" (function()
		-- t.TODO()
	end)

	describe "Buffer.ReadUfixed" (function()
		-- t.TODO()
	end)

	describe "Buffer.WriteFixed" (function()
		-- t.TODO()
	end)

	describe "Buffer.ReadFixed" (function()
		-- t.TODO()
	end)

	describe "isBuffer" (function()
		it "should return false when receiving a non-Buffer" (function()
			expect (function()
				return Bitbuf.isBuffer(42) == false
			end)
		end)
		it "should return true when receiving from Buffer.new" (function()
			expect (function()
				return Bitbuf.isBuffer(Bitbuf.new()) == true
			end)
		end)
		it "should return true when receiving from Buffer.fromString" (function()
			expect (function()
				return Bitbuf.isBuffer(Bitbuf.fromString("")) == true
			end)
		end)
	end)

	local sample = {
		unsigned = 3634604713,
		signed = -1487121065,
		float32 = math.pi,
		float64 = math.pi,
		string = "",
		bool = {},
	}

	do
		local n = 100000
		local t = table.create(n)
		for i = 1, n do
			t[i] = string.char(math.random(0, 255))
		end
		sample.string = table.concat(t)
	end

	do
		local t = table.create(1000)
		for i = 1, 1000 do
			t[i] = math.random(0, 1) == 1
		end
		sample.bool = t
	end

	local count = parameter "count" (
		1,
		10,
		100,
		1000
	)

	local length = parameter "length" (
		1,
		10,
		100,
		1000,
		10000,
		100000
	)

	measure "Bitbuf.new" (function()
		operation(function()
			local _ = Bitbuf.new()
		end)
	end)

	measure "Buffer.WriteBool (N=%d)" (function(count)
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			for i = 1, count do
				buf:WriteBool(sample.bool[i])
			end
		end)
	end, "WriteBool (N=%d)", count)

	measure "Buffer.ReadBool (N=%d)" (function(count)
		local buf = Bitbuf.new()
		for i = 1, count do
			buf:WriteBool(sample.bool[i])
		end
		operation(function()
			buf:SetIndex(0)
			for i = 1, count do
				local _ = buf:ReadBool()
			end
		end)
	end, "ReadBool (N=%d)", count)

	measure "Buffer.WriteUint(32) (N=%d)" (function(count)
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			for i = 1, count do
				buf:WriteUint(32, sample.unsigned)
			end
		end)
	end, "WriteUint (N=%d)", count)

	measure "Buffer.WriteUint(32) (N=%d, unaligned)" (function(count)
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			buf:WriteBool(true)
			for j = 1, count do
				buf:WriteUint(32, sample.unsigned)
			end
		end)
	end, "WriteUint (N=%d)", count)

	measure "Buffer.ReadUint(32) (N=%d)" (function(count)
		local buf = Bitbuf.new()
		for j = 1, count do
			buf:WriteUint(32, sample.unsigned)
		end
		operation(function()
			buf:SetIndex(0)
			for j = 1, count do
				local _ = buf:ReadUint(32)
			end
		end)
	end, "ReadUint (N=%d)", count)

	measure "Buffer.ReadUint(32) (N=%d, unaligned)" (function(count)
		local buf = Bitbuf.new()
		buf:WriteBool(true)
		for j = 1, count do
			buf:WriteUint(32, sample.unsigned)
		end
		operation(function()
			buf:SetIndex(0)
			local _ = buf:ReadBool()
			for j = 1, count do
				local _ = buf:ReadUint(32)
			end
		end)
	end, "ReadUint (N=%d)", count)

	measure "Buffer.WriteInt(32)" (function()
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			buf:WriteInt(32, sample.signed)
		end)
	end, "WriteInt")

	measure "Buffer.ReadInt(32)" (function()
		local buf = Bitbuf.new()
		buf:WriteInt(32, sample.signed)
		operation(function()
			buf:SetIndex(0)
			local _ = buf:ReadInt(32)
		end)
	end, "ReadInt")

	measure "Buffer.WriteUint(8) (N=%d)" (function(count)
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			for i = 1, count do
				buf:WriteUint(8, sample.unsigned)
			end
		end)
	end, "WriteUint (N=%d)", count)

	measure "Buffer.WriteUint(8) (N=%d, unaligned)" (function(count)
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			buf:WriteBool(true)
			for j = 1, count do
				buf:WriteUint(8, sample.unsigned)
			end
		end)
	end, "WriteUint (N=%d)", count)

	measure "Buffer.ReadUint(8) (N=%d)" (function(count)
		local buf = Bitbuf.new()
		for j = 1, count do
			buf:WriteUint(8, sample.unsigned)
		end
		operation(function()
			buf:SetIndex(0)
			for j = 1, count do
				local _ = buf:ReadUint(8)
			end
		end)
	end, "ReadUint (N=%d)", count)

	measure "Buffer.ReadUint(8) (N=%d, unaligned)" (function(count)
		local buf = Bitbuf.new()
		buf:WriteBool(true)
		for j = 1, count do
			buf:WriteUint(8, sample.unsigned)
		end
		operation(function()
			buf:SetIndex(0)
			local _ = buf:ReadBool()
			for j = 1, count do
				local _ = buf:ReadUint(8)
			end
		end)
	end, "ReadUint (N=%d)", count)

	measure "Buffer.WriteInt(8)" (function()
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			buf:WriteInt(8, sample.signed)
		end)
	end, "WriteInt")

	measure "Buffer.ReadInt(8)" (function()
		local buf = Bitbuf.new()
		buf:WriteInt(8, sample.signed)
		operation(function()
			buf:SetIndex(0)
			local _ = buf:ReadInt(8)
		end)
	end, "ReadInt")

	measure "Buffer.WriteFloat32" (function()
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			buf:WriteFloat(32, sample.float32)
		end)
	end, "WriteFloat32")

	measure "Buffer.ReadFloat32" (function()
	local buf = Bitbuf.new()
		buf:WriteFloat(32, sample.float32)
		operation(function()
			buf:SetIndex(0)
			local _ = buf:ReadFloat(32)
		end)
	end, "ReadFloat32")

	measure "Buffer.WriteFloat64" (function()
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			buf:WriteFloat(64, sample.float64)
		end)
	end, "WriteFloat64")

	measure "Buffer.ReadFloat64" (function()
		local buf = Bitbuf.new()
		buf:WriteFloat(64, sample.float64)
		operation(function()
			buf:SetIndex(0)
			buf:ReadFloat(64)
		end)
	end, "ReadFloat64")

	measure "Buffer.WriteString (L=%d)" (function(length)
		local s = string.sub(sample.string, 1, length)
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			buf:WriteUint(32, #s)
			buf:WriteBytes(s)
		end)
	end, "WriteString (L=%d)", length)

	measure "Buffer.ReadString (L=%d)" (function(length)
		local s = string.sub(sample.string, 1, length)
		local buf = Bitbuf.new()
		buf:WriteUint(32, #s)
		buf:WriteBytes(s)
		operation(function()
			buf:SetIndex(0)
			local n = buf:ReadUint(32)
			local _ = buf:ReadBytes(n)
		end)
	end, "ReadString (L=%d)", length)

	measure "Buffer.WriteString (L=%d, unaligned)" (function(length)
		local s = string.sub(sample.string, 1, length)
		local buf = Bitbuf.new()
		operation(function()
			buf:SetIndex(0)
			buf:WriteBool(true)
			buf:WriteUint(32, #s)
			buf:WriteBytes(s)
		end)
	end, "WriteString (L=%d)", length)

	measure "Buffer.ReadString (L=%d, unaligned)" (function(length)
		local s = string.sub(sample.string, 1, length)
		local buf = Bitbuf.new()
		buf:WriteBool(true)
		buf:WriteUint(32, #s)
		buf:WriteBytes(s)
		operation(function()
			buf:SetIndex(0)
			buf:ReadBool()
			local n = buf:ReadUint(32)
			local _ = buf:ReadBytes(n)
		end)
	end, "ReadString (L=%d)", length)

	measure "Buffer.String (L=%d)" (function(length)
		local s = string.sub(sample.string, 1, length)
		local buf = Bitbuf.new()
		buf:WriteBytes(s)
		operation(function()
			local _ = buf:String()
		end)
	end, "String (L=%d)", length)

	measure "Bitbuf.fromString (L=%d)" (function(length)
		local s = string.sub(sample.string, 1, length)
		operation(function()
			local _ = Bitbuf.fromString(s)
		end)
	end, "fromString (L=%d)", length)
end
