files/examples/vstruct/basic.lua
-- we need to test all the different formats, at minimum
-- this means bimPpsuxz for now
-- Copyright � 2008 Ben "ToxicFrog" Kelly; see COPYING

-- test cases
-- simple: read and write of each format, seeks, endianness controls
-- complex: naming, tables, repetition, combinations of all of these,
--		nested tables, multi formats per string
-- non-obvious: seek past end of file
-- error handling: seek past start, read past end, invalid widths,
--		non-isomorphic input tables

local c = string.char
local require,math,ipairs,string,tostring,print,os,pairs
	= require,math,ipairs,string,tostring,print,os,pairs

module((...))
local struct = require(_NAME:match("^[^%.]+"))
local name = (...):gsub('%.[^%.]+$', '')

local function check_bm(m)
	m = m.m
	return m[8] and m[7] and m[6] and m[5]
	and m[4] and not m[3] and m[2] and not m[1]
end

local tests = {
	-- booleans
	{ raw = "\0\0\0\0\0\0\0\1"; format = "b8"; val = true; },
	{ raw = "\1\0\0\0\0\0\0\0"; format = "(b8)*1"; val = true; },
	{ raw = "\0\0\0\0\0\0\0\0"; format = "1*(b8)"; val = false; },
	-- signed integers
	{ raw = "\254\255\255"; format = "< i3"; val = -2; },
	-- unsigned integers
	{ raw = "\254\255\255"; format = "< u3"; val = 2^24-2; },
	-- bitmasks
	{ raw = "\250"; format = "{m:m1}"; test = check_bm; },
	-- fixed point
	{ raw = "\1\128"; format = "> P8.8"; val = 1.5; },
	{ raw = "\2\192"; format = "> p1.1"; val = 2.75; },
	-- plain strings
	{ raw = "foobar"; format = "s4"; val = "foob"; },
	-- counted strings
	{ raw = "\006\000\000\000foobar"; format = "< c4"; val = "foobar"; },
	-- null terminated strings
	{ raw = "foobar\0baz"; format = "z"; val = "foobar"; },
	{ raw = "foobar\0baz"; format = "z10"; val = "foobar"; },
	-- floats
	{ raw = c(0x00, 0x00, 0x00, 0x00); format = "< f4"; val = 0.0; },
	{ raw = c(0x3f, 0x80, 0x00, 0x00); format = "> f4"; val = 1.0; },
	{ raw = c(0x00, 0x00, 0x80, 0x3f); format = "< f4"; val = 1.0; },
	{ raw = c(0x00, 0x00, 0x80, 0xbf); format = "< f4"; val = -1.0; },
	{ raw = c(0x00, 0x00, 0x80, 0x7f); format = "< f4"; val = math.huge; },
	{ raw = c(0x00, 0x00, 0x80, 0xff); format = "< f4"; val = -math.huge; },
	{ raw = c(0x00, 0x00, 0xc0, 0x7f); format = "< f4"; val = 0/0; test = function(v) return v ~= v end },
	{ raw = c(0x00, 0x00, 0xc0, 0xff); format = "< f4"; val = 0/0; test = function(v) return v ~= v end },
	-- doubles
	{ raw = c(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00); format = "< f8"; val = 0.0; },
	{ raw = c(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f); format = "< f8"; val = 1.0; },
	{ raw = c(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xbf); format = "< f8"; val = -1.0; },
	{ raw = c(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x7f); format = "< f8"; val = math.huge; },
	{ raw = c(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff); format = "< f8"; val = -math.huge; },
	{ raw = c(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x7f); format = "< f8"; val = 0/0; test = function(v) return v ~= v end },
	{ raw = c(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0xff); format = "< f8"; val = 0/0; test = function(v) return v ~= v end },
}

function od(str)
	return str:gsub('.', function(c) return string.format("%02X ", c:byte()) end)
end

function check(test)
	local val, pass, raw
	local fmt = "%7.7s %42.42s%2.2s %17.17s %4.4s %3.3s"

	val = struct.unpack(test.format, test.raw)[1]
	pass = test.test
		and test.test(val)
		or val == test.val
	print(fmt:format(test.format, od(test.raw), "=>", tostring(val), pass and "PASS" or "FAIL", pass and "" or "!!!"):sub(1,79))
	if not pass then return end

	raw = struct.pack(test.format, {val})
	pass = raw == test.raw
	-- if we have a failure, it might be because there are multiple valid on-disk forms
	-- for example, a boolean can be any non-zero value, but we always write it back out as 1
	-- so, re-read it using the same format and see if it matches
	if not pass then
		local new_val = struct.unpack(test.format, raw)[1]
		pass = test.test and test.test(val) or new_val == test.val
	end
	print(fmt:format(test.format, od(raw), "<=", tostring(val), pass and "PASS" or "FAIL", pass and "" or " !!!"):sub(1,79))
end

for i,test in ipairs(tests) do
	check(test)
end

os.exit(0)
