22fb4c6e8c
Workaround for TypesettingTools/Aegisub#162
813 lines
20 KiB
Lua
813 lines
20 KiB
Lua
-- ----------------------------------------------------------------------------
|
|
-- test.lua
|
|
-- Luabins test suite
|
|
-- See copyright notice in luabins.h
|
|
-- ----------------------------------------------------------------------------
|
|
|
|
package.cpath = "./?.so;"..package.cpath
|
|
|
|
local randomseed = 1235134892
|
|
--local randomseed = os.time()
|
|
|
|
print("===== BEGIN LUABINS TEST SUITE (seed " .. randomseed .. ") =====")
|
|
math.randomseed(randomseed)
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- Utility functions
|
|
-- ----------------------------------------------------------------------------
|
|
|
|
local invariant = function(v)
|
|
return function()
|
|
return v
|
|
end
|
|
end
|
|
|
|
local escape_string = function(str)
|
|
return str:gsub(
|
|
"[^0-9A-Za-z_%- :]",
|
|
function(c)
|
|
return ("%%%02X"):format(c:byte())
|
|
end
|
|
)
|
|
end
|
|
|
|
local ensure_equals = function(msg, actual, expected)
|
|
if actual ~= expected then
|
|
error(
|
|
msg..":\n actual: `"..escape_string(tostring(actual))
|
|
.."`\nexpected: `"..escape_string(tostring(expected)).."'"
|
|
)
|
|
end
|
|
end
|
|
|
|
local ensure_equals_permute
|
|
do
|
|
-- Based on MIT-licensed
|
|
-- http://snippets.luacode.org/sputnik.lua?p=snippets/ \
|
|
-- Iterator_over_Permutations_of_a_Table_62
|
|
-- Which is based on PiL
|
|
local function permgen(a, n, fn)
|
|
if n == 0 then
|
|
fn(a)
|
|
else
|
|
for i = 1, n do
|
|
-- put i-th element as the last one
|
|
a[n], a[i] = a[i], a[n]
|
|
|
|
-- generate all permutations of the other elements
|
|
permgen(a, n - 1, fn)
|
|
|
|
-- restore i-th element
|
|
a[n], a[i] = a[i], a[n]
|
|
end
|
|
end
|
|
end
|
|
|
|
--- an iterator over all permutations of the elements of a list.
|
|
-- Please note that the same list is returned each time,
|
|
-- so do not keep references!
|
|
-- @param a list-like table
|
|
-- @return an iterator which provides the next permutation as a list
|
|
local function permute_iter(a, n)
|
|
local n = n or #a
|
|
local co = coroutine.create(function() permgen(a, n, coroutine.yield) end)
|
|
return function() -- iterator
|
|
local code, res = coroutine.resume(co)
|
|
return res
|
|
end
|
|
end
|
|
|
|
ensure_equals_permute = function(
|
|
msg,
|
|
actual,
|
|
expected_prefix,
|
|
expected_body,
|
|
expected_suffix,
|
|
expected_body_size
|
|
)
|
|
expected_body_size = expected_body_size or #expected_body
|
|
|
|
local expected
|
|
for t in permute_iter(expected_body, expected_body_size) do
|
|
expected = expected_prefix .. table.concat(t) .. expected_suffix
|
|
if actual == expected then
|
|
return actual
|
|
end
|
|
end
|
|
|
|
error(
|
|
msg..":\nactual: `"..escape_string(tostring(actual))
|
|
.."`\nexpected one of permutations: `"
|
|
..escape_string(tostring(expected)).."'"
|
|
)
|
|
end
|
|
end
|
|
|
|
local function deepequals(lhs, rhs)
|
|
if type(lhs) ~= "table" or type(rhs) ~= "table" then
|
|
return lhs == rhs
|
|
end
|
|
|
|
local checked_keys = {}
|
|
|
|
for k, v in pairs(lhs) do
|
|
checked_keys[k] = true
|
|
if not deepequals(v, rhs[k]) then
|
|
return false
|
|
end
|
|
end
|
|
|
|
for k, v in pairs(rhs) do
|
|
if not checked_keys[k] then
|
|
return false -- extra key
|
|
end
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
local nargs = function(...)
|
|
return select("#", ...), ...
|
|
end
|
|
|
|
local pack = function(...)
|
|
return select("#", ...), { ... }
|
|
end
|
|
|
|
local eat_true = function(t, ...)
|
|
if t == nil then
|
|
error("failed: " .. (...))
|
|
end
|
|
return ...
|
|
end
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- Test helper functions
|
|
-- ----------------------------------------------------------------------------
|
|
|
|
local luabins_local = require 'luabins'
|
|
assert(luabins_local == luabins)
|
|
|
|
assert(type(luabins.save) == "function")
|
|
assert(type(luabins.load) == "function")
|
|
|
|
local check_load_fn_ok = function(eq, saved, ...)
|
|
local expected = { nargs(...) }
|
|
local loaded = { nargs(eat_true(luabins.load(saved))) }
|
|
|
|
ensure_equals("num arguments match", loaded[1], expected[1])
|
|
for i = 2, expected[1] do
|
|
assert(eq(loaded[i], expected[i]))
|
|
end
|
|
|
|
return saved
|
|
end
|
|
|
|
local check_load_ok = function(saved, ...)
|
|
return check_load_fn_ok(deepequals, saved, ...)
|
|
end
|
|
|
|
local check_fn_ok = function(eq, ...)
|
|
local saved = assert(luabins.save(...))
|
|
|
|
assert(type(saved) == "string")
|
|
|
|
print("saved length", #saved, "(display truncated to 70 chars)")
|
|
print(escape_string(saved):sub(1, 70))
|
|
|
|
return check_load_fn_ok(eq, saved, ...)
|
|
end
|
|
|
|
local check_ok = function(...)
|
|
print("check_ok")
|
|
return check_fn_ok(deepequals, ...)
|
|
end
|
|
|
|
local check_fail_save = function(msg, ...)
|
|
print("check_fail_save")
|
|
local res, err = luabins.save(...)
|
|
ensure_equals("result", res, nil)
|
|
ensure_equals("error message", err, msg)
|
|
-- print("/check_fail_save")
|
|
end
|
|
|
|
local check_fail_load = function(msg, v)
|
|
print("check_fail_load")
|
|
local res, err = luabins.load(v)
|
|
ensure_equals("result", res, nil)
|
|
ensure_equals("error message", err, msg)
|
|
-- print("/check_fail_load")
|
|
end
|
|
|
|
print("===== BEGIN LARGE DATA OK =====")
|
|
|
|
-- Based on actual bug.
|
|
-- This dataset triggered Lua C data stack overflow.
|
|
-- (Note that bug is not triggered if check_ok is used)
|
|
-- Update data with
|
|
-- $ lua etc/toluabins.lua test/large_data.lua>test/large_data.luabins
|
|
-- WARNING: Keep this test above other tests, so Lua stack is small.
|
|
assert(
|
|
luabins.load(
|
|
assert(io.open("test/large_data.luabins", "r"):read("*a"))
|
|
)
|
|
)
|
|
|
|
print("===== LARGE DATA OK =====")
|
|
|
|
-- ----------------------------------------------------------------------------
|
|
-- Basic tests
|
|
-- ----------------------------------------------------------------------------
|
|
|
|
print("===== BEGIN BASIC TESTS =====")
|
|
|
|
print("---> basic corrupt data tests")
|
|
|
|
check_fail_load("can't load: corrupt data", "")
|
|
check_fail_load("can't load: corrupt data", "bad data")
|
|
|
|
print("---> basic extra data tests")
|
|
do
|
|
local s
|
|
|
|
s = check_ok()
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
s = check_ok(nil)
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
s = check_ok(true)
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
s = check_ok(false)
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
s = check_ok(42)
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
s = check_ok(math.pi)
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
s = check_ok(1/0)
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
s = check_ok(-1/0)
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
s = check_ok("Luabins")
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
s = check_ok({ })
|
|
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
s = check_ok({ a = 1, 2 })
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
end
|
|
|
|
print("---> basic type tests")
|
|
|
|
-- This is the way to detect NaN
|
|
check_fn_ok(function(lhs, rhs) return lhs ~= lhs and rhs ~= rhs end, 0/0)
|
|
|
|
check_ok("")
|
|
|
|
check_ok("Embedded\0Zero")
|
|
|
|
check_ok(("longstring"):rep(1024000))
|
|
|
|
check_fail_save("can't save: unsupported type detected", function() end)
|
|
check_fail_save(
|
|
"can't save: unsupported type detected",
|
|
coroutine.create(function() end)
|
|
)
|
|
check_fail_save("can't save: unsupported type detected", newproxy())
|
|
|
|
print("---> basic table tests")
|
|
|
|
check_ok({ 1 })
|
|
check_ok({ a = 1 })
|
|
check_ok({ a = 1, 2, [42] = true, [math.pi] = math.huge })
|
|
check_ok({ { } })
|
|
check_ok({ a = {}, b = { c = 7 } })
|
|
check_ok({ 1, 2, 3 })
|
|
check_ok({ [1] = 1, [1.5] = 2, [2] = 3 })
|
|
check_ok({ 1, nil, 3 })
|
|
check_ok({ 1, nil, 3, [{ 1, nil, 3 }] = { 1, nil, 3 } })
|
|
|
|
print("---> basic tuple tests")
|
|
|
|
check_ok(nil, nil)
|
|
|
|
do
|
|
local s = check_ok(nil, false, true, 42, "Embedded\0Zero", { { [{3}] = 54 } })
|
|
check_fail_load("can't load: extra data at end", s .. "-")
|
|
|
|
check_ok(check_ok(s)) -- Save data string couple of times more
|
|
end
|
|
|
|
print("---> basic table tuple tests")
|
|
|
|
check_ok({ a = {}, b = { c = 7 } }, nil, { { } }, 42)
|
|
|
|
check_ok({ ["1"] = "str", [1] = "num" })
|
|
|
|
check_ok({ [true] = true })
|
|
check_ok({ [true] = true, [false] = false, 1 })
|
|
|
|
print("---> basic fail save tests")
|
|
|
|
check_fail_save(
|
|
"can't save: unsupported type detected",
|
|
{ { function() end } }
|
|
)
|
|
|
|
check_fail_save(
|
|
"can't save: unsupported type detected",
|
|
nil, false, true, 42, "Embedded\0Zero", function() end,
|
|
{ { [{3}] = 54 } }
|
|
)
|
|
|
|
print("---> recursive table test")
|
|
|
|
local t = {}; t[1] = t
|
|
check_fail_save("can't save: nesting is too deep", t)
|
|
|
|
print("---> metatable test")
|
|
|
|
check_ok(setmetatable({}, {__index = function(t, k) return k end}))
|
|
|
|
print("===== BASIC TESTS OK =====")
|
|
|
|
print("===== BEGIN FORMAT SANITY TESTS =====")
|
|
|
|
-- Format sanity checks for LJ2 compatibility tests.
|
|
-- These tests are intended to help debugging actual problems
|
|
-- of test suite, and are not feature complete.
|
|
-- What is not checked here, checked in the rest of suite.
|
|
|
|
do
|
|
do
|
|
local saved = check_ok(1)
|
|
local expected =
|
|
"\001".."N"
|
|
.. "\000\000\000\000\000\000\240\063" -- Note number is a double
|
|
|
|
ensure_equals(
|
|
"1 as number",
|
|
expected,
|
|
saved
|
|
)
|
|
end
|
|
|
|
do
|
|
local saved = check_ok({ [true] = 1 })
|
|
local expected =
|
|
"\001".."T"
|
|
.. "\000\000\000\000".."\001\000\000\000"
|
|
.. "1"
|
|
.. "N\000\000\000\000\000\000\240\063" -- Note number is a double
|
|
|
|
ensure_equals(
|
|
"1 as value",
|
|
expected,
|
|
saved
|
|
)
|
|
end
|
|
|
|
do
|
|
local saved = check_ok({ [1] = true })
|
|
local expected =
|
|
"\001".."T"
|
|
.. "\001\000\000\000".."\000\000\000\000"
|
|
.. "N\000\000\000\000\000\000\240\063" -- Note number is a double
|
|
.. "1"
|
|
|
|
ensure_equals(
|
|
"1 as key",
|
|
expected,
|
|
saved
|
|
)
|
|
end
|
|
end
|
|
|
|
print("===== FORMAT SANITY TESTS OK =====")
|
|
|
|
print("===== BEGIN AUTOCOLLAPSE TESTS =====")
|
|
|
|
-- Note: those are ad-hoc tests, tuned for old implementation
|
|
-- which generated save data on Lua stack.
|
|
-- These tests are kept here for performance comparisons.
|
|
|
|
local LUABINS_CONCATTHRESHOLD = 1024
|
|
|
|
local gen_t = function(size)
|
|
-- two per numeric entry, three per string entry,
|
|
-- two entries per key-value pair
|
|
local actual_size = math.ceil(size / (2 + 3))
|
|
print("generating table of "..actual_size.." pairs")
|
|
local t = {}
|
|
for i = 1, actual_size do
|
|
t[i] = "a"..i
|
|
end
|
|
return t
|
|
end
|
|
|
|
-- Test table value autocollapse
|
|
check_ok(gen_t(LUABINS_CONCATTHRESHOLD - 100)) -- underflow, no autocollapse
|
|
check_ok(gen_t(LUABINS_CONCATTHRESHOLD)) -- autocollapse, no extra elements
|
|
check_ok(gen_t(LUABINS_CONCATTHRESHOLD + 100)) -- autocollapse, extra elements
|
|
|
|
-- Test table key autocollapse
|
|
check_ok({ [gen_t(LUABINS_CONCATTHRESHOLD - 4)] = true })
|
|
|
|
-- Test multiarg autocollapse
|
|
check_ok(
|
|
1,
|
|
gen_t(LUABINS_CONCATTHRESHOLD - 5),
|
|
2,
|
|
gen_t(LUABINS_CONCATTHRESHOLD - 5),
|
|
3
|
|
)
|
|
|
|
print("===== AUTOCOLLAPSE TESTS OK =====")
|
|
|
|
print("===== BEGIN MIN TABLE SIZE TESTS =====")
|
|
|
|
do
|
|
-- one small key
|
|
do
|
|
local data = { [true] = true }
|
|
local saved = check_ok(data)
|
|
ensure_equals(
|
|
"format sanity check",
|
|
"\001".."T".."\000\000\000\000".."\001\000\000\000".."11",
|
|
saved
|
|
)
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
saved:sub(1, #saved - 1)
|
|
)
|
|
|
|
-- As long as array and hash size sum is correct
|
|
-- (and both are within limits), load is successful.
|
|
-- If values are swapped, we get some performance hit.
|
|
check_load_ok(
|
|
"\001".."T".."\001\000\000\000".."\000\000\000\000".."11",
|
|
data
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
"\001".."T".."\001\000\000\000".."\001\000\000\000".."11"
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
"\001".."T".."\000\000\000\000".."\002\000\000\000".."11"
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: extra data at end",
|
|
"\001".."T".."\000\000\000\000".."\000\000\000\000".."11"
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
"\001".."T".."\255\255\255\255".."\255\255\255\255".."11"
|
|
)
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
"\001".."T".."\000\255\255\255".."\000\255\255\255".."11"
|
|
)
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
"\255".."T".."\000\000\000\000".."\000\000\000\000"
|
|
)
|
|
end
|
|
|
|
-- two small keys
|
|
do
|
|
local data = { [true] = true, [false] = false }
|
|
local saved = check_ok({ [true] = true, [false] = false })
|
|
ensure_equals_permute(
|
|
"format sanity check",
|
|
saved,
|
|
"\001" .. "T" .. "\000\000\000\000" .. "\002\000\000\000",
|
|
{
|
|
"0" .. "0";
|
|
"1" .. "1";
|
|
},
|
|
""
|
|
)
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
saved:sub(1, #saved - 1)
|
|
)
|
|
|
|
-- See above about swapped array and hash sizes
|
|
check_load_ok(
|
|
"\001".."T".."\001\000\000\000".."\001\000\000\000".."1100",
|
|
data
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
"\001".."T".."\000\000\000\000".."\003\000\000\000".."0011"
|
|
)
|
|
end
|
|
|
|
-- two small and one large key
|
|
do
|
|
local saved = check_ok({ [true] = true, [false] = false, [1] = true })
|
|
ensure_equals_permute(
|
|
"format sanity check",
|
|
saved,
|
|
"\001" .. "T" .. "\001\000\000\000" .. "\002\000\000\000",
|
|
{
|
|
"0" .. "0";
|
|
"1" .. "1";
|
|
-- Note number is a double
|
|
"N\000\000\000\000\000\000\240\063" .. "1";
|
|
},
|
|
""
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: corrupt data",
|
|
saved:sub(1, #saved - 1)
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
"\001".."T"
|
|
.. "\002\000\000\000".."\002\000\000\000"
|
|
.. "0011"
|
|
.. "N\000\000\000\000\000\000\240\063"
|
|
.. "1"
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
"\001".."T"
|
|
.. "\001\000\000\000".."\003\000\000\000"
|
|
.. "0011"
|
|
.. "N\000\000\000\000\000\000\240\063"
|
|
.. "1"
|
|
)
|
|
end
|
|
|
|
-- two small and two large keys
|
|
do
|
|
local saved = check_ok(
|
|
{ [true] = true, [false] = false, [1] = true, [42] = true }
|
|
)
|
|
local expected =
|
|
"\001".."T"
|
|
.. "\001\000\000\000".."\003\000\000\000"
|
|
.. "0011"
|
|
ensure_equals_permute(
|
|
"format sanity check",
|
|
saved,
|
|
"\001" .. "T" .. "\001\000\000\000" .. "\003\000\000\000",
|
|
{
|
|
"0" .. "0";
|
|
"1" .. "1";
|
|
"N\000\000\000\000\000\000\069\064" .. "1";
|
|
"N\000\000\000\000\000\000\240\063" .. "1";
|
|
},
|
|
""
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: corrupt data",
|
|
saved:sub(1, #saved - 1)
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
"\001".."T"
|
|
.. "\001\000\000\000".."\005\000\000\000"
|
|
.. "0011"
|
|
.. "N\000\000\000\000\000\000\069\064"
|
|
.. "1"
|
|
.. "N\000\000\000\000\000\000\240\063"
|
|
.. "1"
|
|
)
|
|
|
|
check_fail_load(
|
|
"can't load: corrupt data, bad size",
|
|
"\001".."T"
|
|
.. "\003\000\000\000".."\003\000\000\000"
|
|
.. "0011"
|
|
.. "N\000\000\000\000\000\000\069\064"
|
|
.. "1"
|
|
.. "N\000\000\000\000\000\000\240\063"
|
|
.. "1"
|
|
)
|
|
end
|
|
end
|
|
|
|
print("===== MIN TABLE SIZE TESTS OK =====")
|
|
|
|
print("===== BEGIN LOAD TRUNCATION TESTS =====")
|
|
|
|
local function gen_random_dataset(num, nesting)
|
|
num = num or math.random(0, 128)
|
|
nesting = nesting or 1
|
|
|
|
local gen_str = function()
|
|
local t = {}
|
|
local n = math.random(0, 1024)
|
|
for i = 1, n do
|
|
t[i] = string.char(math.random(0, 255))
|
|
end
|
|
return table.concat(t)
|
|
end
|
|
|
|
local gen_bool = function() return math.random() >= 0.5 end
|
|
|
|
local gen_nil = function() return nil end
|
|
|
|
local generators =
|
|
{
|
|
gen_nil;
|
|
gen_nil;
|
|
gen_nil;
|
|
gen_bool;
|
|
gen_bool;
|
|
gen_bool;
|
|
function() return math.random() end;
|
|
function() return math.random(-10000, 10000) end;
|
|
function() return math.random() * math.random(-10000, 10000) end;
|
|
gen_str;
|
|
gen_str;
|
|
gen_str;
|
|
function()
|
|
if nesting >= 24 then
|
|
return nil
|
|
end
|
|
|
|
local t = {}
|
|
local n = math.random(0, 24 - nesting)
|
|
for i = 1, n do
|
|
local k = gen_random_dataset(1, nesting + 1)
|
|
if k == nil then
|
|
k = "(nil)"
|
|
end
|
|
t[ k ] = gen_random_dataset(
|
|
1,
|
|
nesting + 1
|
|
)
|
|
end
|
|
|
|
return t
|
|
end;
|
|
}
|
|
|
|
local t = {}
|
|
for i = 1, num do
|
|
local n = math.random(1, #generators)
|
|
t[i] = generators[n]()
|
|
end
|
|
return unpack(t, 0, num)
|
|
end
|
|
|
|
local random_dataset_num, random_dataset_data = pack(gen_random_dataset())
|
|
local random_dataset_saved = check_ok(
|
|
unpack(random_dataset_data, 0, random_dataset_num)
|
|
)
|
|
|
|
local num_tries = 100
|
|
local errors = {}
|
|
for i = 1, num_tries do
|
|
local to = math.random(1, #random_dataset_saved - 1)
|
|
local new_data = random_dataset_saved:sub(1, to)
|
|
|
|
local res, err = luabins.load(new_data)
|
|
ensure_equals("truncated data must not be loaded", res, nil)
|
|
errors[err] = (errors[err] or 0) + 1
|
|
end
|
|
|
|
print("truncation errors encountered:")
|
|
for err, n in pairs(errors) do
|
|
print(err, n)
|
|
end
|
|
|
|
print("===== BASIC LOAD TRUNCATION OK =====")
|
|
|
|
print("===== BEGIN LOAD MUTATION TESTS =====")
|
|
|
|
local function mutate_string(str, num, override)
|
|
num = num or math.random(1, 8)
|
|
|
|
if num < 1 then
|
|
return str
|
|
end
|
|
|
|
local mutators =
|
|
{
|
|
-- truncate at end
|
|
function(str)
|
|
local pos = math.random(1, #str)
|
|
return str:sub(1, pos)
|
|
end;
|
|
-- truncate at beginning
|
|
function(str)
|
|
local pos = math.random(1, #str)
|
|
return str:sub(-pos)
|
|
end;
|
|
-- cut out the middle
|
|
function(str)
|
|
local from = math.random(1, #str)
|
|
local to = math.random(from, #str)
|
|
return str:sub(1, from) .. str:sub(to)
|
|
end;
|
|
-- swap two halves
|
|
function(str)
|
|
local pos = math.random(1, #str)
|
|
return str:sub(pos + 1, #str) .. str:sub(1, pos)
|
|
end;
|
|
-- swap two characters
|
|
function(str)
|
|
local pa, pb = math.random(1, #str), math.random(1, #str)
|
|
local a, b = str:sub(pa, pa), str:sub(pb, pb)
|
|
return
|
|
str:sub(1, pa - 1) ..
|
|
a ..
|
|
str:sub(pa + 1, pb - 1) ..
|
|
b ..
|
|
str:sub(pb + 1, #str)
|
|
end;
|
|
-- replace one character
|
|
function(str)
|
|
local pos = math.random(1, #str)
|
|
return
|
|
str:sub(1, pos - 1) ..
|
|
string.char(math.random(0, 255)) ..
|
|
str:sub(pos + 1, #str)
|
|
end;
|
|
-- increase one character
|
|
function(str)
|
|
local pos = math.random(1, #str)
|
|
local b = str:byte(pos, pos) + 1
|
|
if b > 255 then
|
|
b = 0
|
|
end
|
|
return
|
|
str:sub(1, pos - 1) ..
|
|
string.char(b) ..
|
|
str:sub(pos + 1, #str)
|
|
end;
|
|
-- decrease one character
|
|
function(str)
|
|
local pos = math.random(1, #str)
|
|
local b = str:byte(pos, pos) - 1
|
|
if b < 0 then
|
|
b = 255
|
|
end
|
|
return
|
|
str:sub(1, pos - 1) ..
|
|
string.char(b) ..
|
|
str:sub(pos + 1, #str)
|
|
end;
|
|
}
|
|
|
|
local n = override or math.random(1, #mutators)
|
|
|
|
str = mutators[n](str)
|
|
|
|
return mutate_string(str, num - 1, override)
|
|
end
|
|
|
|
local num_tries = 100000
|
|
local num_successes = 0
|
|
local errors = {}
|
|
for i = 1, num_tries do
|
|
local new_data = mutate_string(random_dataset_saved)
|
|
|
|
local res, err = luabins.load(new_data)
|
|
if res == nil then
|
|
errors[err] = (errors[err] or 0) + 1
|
|
else
|
|
num_successes = num_successes + 1
|
|
end
|
|
end
|
|
|
|
if num_successes == 0 then
|
|
print("no mutated strings loaded successfully")
|
|
else
|
|
-- This is ok, since we may corrupt data, not format.
|
|
-- If it is an issue for user, he must append checksum to data,
|
|
-- as usual.
|
|
print("mutated strings loaded successfully: "..num_successes)
|
|
end
|
|
|
|
print("mutation errors encountered:")
|
|
for err, n in pairs(errors) do
|
|
print(err, n)
|
|
end
|
|
|
|
print("===== BASIC LOAD MUTATION OK =====")
|
|
|
|
print("OK")
|