implement table tests, fix crypto module json fail
This commit is contained in:
parent
1d05ac8bb2
commit
5551f16bc1
@ -1,5 +1,4 @@
|
||||
-- modules/crypto.lua - Comprehensive cryptographic utilities
|
||||
|
||||
local json = require("json")
|
||||
local crypto = {}
|
||||
|
||||
-- ======================================================================
|
||||
@ -93,16 +92,16 @@ end
|
||||
-- Hash file contents
|
||||
function crypto.hash_file(path, algorithm)
|
||||
algorithm = algorithm or "sha256"
|
||||
|
||||
|
||||
if not moonshark.file_exists(path) then
|
||||
error("File not found: " .. path)
|
||||
end
|
||||
|
||||
|
||||
local content = moonshark.file_read(path)
|
||||
if not content then
|
||||
error("Failed to read file: " .. path)
|
||||
end
|
||||
|
||||
|
||||
if algorithm == "md5" then
|
||||
return crypto.md5(content)
|
||||
elseif algorithm == "sha1" then
|
||||
@ -189,11 +188,11 @@ end
|
||||
function crypto.random_password(length, include_symbols)
|
||||
length = length or 12
|
||||
local charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
|
||||
if include_symbols then
|
||||
charset = charset .. "!@#$%^&*()_+-=[]{}|;:,.<>?"
|
||||
end
|
||||
|
||||
|
||||
return crypto.random_string(length, charset)
|
||||
end
|
||||
|
||||
@ -228,13 +227,13 @@ end
|
||||
function crypto.xor_encrypt(data, key)
|
||||
local result = {}
|
||||
local key_len = #key
|
||||
|
||||
|
||||
for i = 1, #data do
|
||||
local data_byte = string.byte(data, i)
|
||||
local key_byte = string.byte(key, ((i - 1) % key_len) + 1)
|
||||
table.insert(result, string.char(bit32 and bit32.bxor(data_byte, key_byte) or bit.bxor(data_byte, key_byte)))
|
||||
end
|
||||
|
||||
|
||||
return table.concat(result)
|
||||
end
|
||||
|
||||
@ -247,7 +246,7 @@ end
|
||||
function crypto.hash_chain(data, iterations, algorithm)
|
||||
iterations = iterations or 1000
|
||||
algorithm = algorithm or "sha256"
|
||||
|
||||
|
||||
local result = data
|
||||
for i = 1, iterations do
|
||||
result = crypto[algorithm](result)
|
||||
@ -260,12 +259,12 @@ function crypto.derive_key(password, salt, iterations, algorithm)
|
||||
iterations = iterations or 10000
|
||||
algorithm = algorithm or "sha256"
|
||||
salt = salt or crypto.random_hex(16)
|
||||
|
||||
|
||||
local derived = password .. salt
|
||||
for i = 1, iterations do
|
||||
derived = crypto[algorithm](derived)
|
||||
end
|
||||
|
||||
|
||||
return derived, salt
|
||||
end
|
||||
|
||||
@ -296,7 +295,7 @@ end
|
||||
function crypto.encode_chain(data, formats)
|
||||
formats = formats or {"base64"}
|
||||
local result = data
|
||||
|
||||
|
||||
for _, format in ipairs(formats) do
|
||||
if format == "base64" then
|
||||
result = crypto.base64_encode(result)
|
||||
@ -308,7 +307,7 @@ function crypto.encode_chain(data, formats)
|
||||
error("Unknown encoding format: " .. format)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
@ -316,7 +315,7 @@ end
|
||||
function crypto.decode_chain(data, formats)
|
||||
formats = formats or {"base64"}
|
||||
local result = data
|
||||
|
||||
|
||||
-- Reverse the formats for decoding
|
||||
for i = #formats, 1, -1 do
|
||||
local format = formats[i]
|
||||
@ -330,7 +329,7 @@ function crypto.decode_chain(data, formats)
|
||||
error("Unknown decoding format: " .. format)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return result
|
||||
end
|
||||
|
||||
@ -344,8 +343,7 @@ end
|
||||
-- Create fingerprint from table data
|
||||
function crypto.fingerprint(data, algorithm)
|
||||
algorithm = algorithm or "sha256"
|
||||
local json = moonshark.json_encode(data)
|
||||
return crypto[algorithm](json)
|
||||
return crypto[algorithm](json.encode(data))
|
||||
end
|
||||
|
||||
-- Simple data integrity check
|
||||
@ -363,9 +361,9 @@ function crypto.verify_integrity(check)
|
||||
if not check.data or not check.hash then
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
local expected = crypto.sha256(check.data)
|
||||
return crypto.secure_compare(expected, check.hash)
|
||||
end
|
||||
|
||||
return crypto
|
||||
return crypto
|
||||
|
@ -1,5 +1,3 @@
|
||||
-- modules/json.lua - High-performance JSON module
|
||||
|
||||
local json = {}
|
||||
|
||||
function json.encode(value)
|
||||
@ -599,4 +597,4 @@ function json.validate(data, schema)
|
||||
return validate_value(data, schema)
|
||||
end
|
||||
|
||||
return json
|
||||
return json
|
||||
|
@ -186,8 +186,15 @@ function tbl.filter(t, predicate)
|
||||
|
||||
local result = {}
|
||||
if tbl.is_array(t) then
|
||||
for i, v in ipairs(t) do
|
||||
if predicate(v, i, t) then
|
||||
local max_index = 0
|
||||
for k in pairs(t) do
|
||||
if type(k) == "number" and k > max_index then
|
||||
max_index = k
|
||||
end
|
||||
end
|
||||
for i = 1, max_index do
|
||||
local v = t[i]
|
||||
if v ~= nil and predicate(v, i, t) then
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
@ -780,7 +787,7 @@ function tbl.flatten(t, depth)
|
||||
error("tbl.flatten: depth must be a positive integer", 2)
|
||||
end
|
||||
|
||||
depth = depth or math.huge
|
||||
depth = depth or 1
|
||||
|
||||
local function flatten_recursive(arr, current_depth)
|
||||
local result = {}
|
||||
@ -815,7 +822,7 @@ function tbl.deep_merge(...)
|
||||
if type(v) == "table" and type(target[k]) == "table" then
|
||||
target[k] = merge_recursive(target[k], v)
|
||||
else
|
||||
target[k] = tbl.deep_copy(v)
|
||||
target[k] = type(v) == "table" and tbl.deep_copy(v) or v
|
||||
end
|
||||
end
|
||||
return target
|
||||
@ -936,7 +943,31 @@ end
|
||||
function tbl.compact(t)
|
||||
if type(t) ~= "table" then error("tbl.compact: argument must be a table", 2) end
|
||||
|
||||
return tbl.filter(t, function(v) return v ~= nil and v ~= false end)
|
||||
-- Check if table has only integer keys (array-like)
|
||||
local has_only_int_keys = true
|
||||
local max_key = 0
|
||||
for k in pairs(t) do
|
||||
if type(k) ~= "number" or k ~= math.floor(k) or k <= 0 then
|
||||
has_only_int_keys = false
|
||||
break
|
||||
end
|
||||
max_key = math.max(max_key, k)
|
||||
end
|
||||
|
||||
if has_only_int_keys then
|
||||
-- Treat as array-like and return clean array
|
||||
local result = {}
|
||||
for i = 1, max_key do
|
||||
local v = t[i]
|
||||
if v ~= nil and v ~= false then
|
||||
table.insert(result, v)
|
||||
end
|
||||
end
|
||||
return result
|
||||
else
|
||||
-- Regular table filtering
|
||||
return tbl.filter(t, function(v) return v ~= nil and v ~= false end)
|
||||
end
|
||||
end
|
||||
|
||||
function tbl.sample(t, n)
|
||||
@ -954,4 +985,15 @@ function tbl.sample(t, n)
|
||||
return tbl.slice(shuffled, 1, n)
|
||||
end
|
||||
|
||||
function tbl.fold(t, folder, initial)
|
||||
if type(t) ~= "table" then error("tbl.fold: first argument must be a table", 2) end
|
||||
if type(folder) ~= "function" then error("tbl.fold: second argument must be a function", 2) end
|
||||
|
||||
local accumulator = initial
|
||||
for k, v in pairs(t) do
|
||||
accumulator = folder(accumulator, v, k, t)
|
||||
end
|
||||
return accumulator
|
||||
end
|
||||
|
||||
return tbl
|
||||
|
@ -13,7 +13,7 @@ test("Base64 Encoding/Decoding", function()
|
||||
local encoded = crypto.base64_encode(test_data)
|
||||
assert_equal(type(encoded), "string")
|
||||
assert(#encoded > 0, "encoded string should not be empty")
|
||||
|
||||
|
||||
local decoded = crypto.base64_decode(encoded)
|
||||
assert_equal(decoded, test_data)
|
||||
end)
|
||||
@ -21,7 +21,7 @@ end)
|
||||
test("Base64 URL Encoding/Decoding", function()
|
||||
local encoded = crypto.base64_url_encode(test_data)
|
||||
assert_equal(type(encoded), "string")
|
||||
|
||||
|
||||
local decoded = crypto.base64_url_decode(encoded)
|
||||
assert_equal(decoded, test_data)
|
||||
end)
|
||||
@ -30,7 +30,7 @@ test("Hex Encoding/Decoding", function()
|
||||
local encoded = crypto.hex_encode(test_data)
|
||||
assert_equal(type(encoded), "string")
|
||||
assert(encoded:match("^[0-9a-f]+$"), "hex should only contain hex characters")
|
||||
|
||||
|
||||
local decoded = crypto.hex_decode(encoded)
|
||||
assert_equal(decoded, test_data)
|
||||
end)
|
||||
@ -50,7 +50,7 @@ test("MD5 Hash", function()
|
||||
assert_equal(type(hash), "string")
|
||||
assert_equal(#hash, 32) -- MD5 is 32 hex characters
|
||||
assert(hash:match("^[0-9a-f]+$"), "hash should be hex")
|
||||
|
||||
|
||||
-- Same input should produce same hash
|
||||
assert_equal(crypto.md5(test_data), hash)
|
||||
end)
|
||||
@ -80,7 +80,7 @@ test("Hash Multiple Inputs", function()
|
||||
local hash1 = crypto.hash_multiple({"hello", "world"})
|
||||
local hash2 = crypto.hash_multiple({"hello", "world"})
|
||||
local hash3 = crypto.hash_multiple({"world", "hello"})
|
||||
|
||||
|
||||
assert_equal(hash1, hash2)
|
||||
assert(hash1 ~= hash3, "different order should produce different hash")
|
||||
end)
|
||||
@ -107,10 +107,10 @@ test("MAC Functions", function()
|
||||
local mac = crypto.mac(test_data, test_key)
|
||||
assert_equal(type(mac), "string")
|
||||
assert(#mac > 0, "mac should not be empty")
|
||||
|
||||
|
||||
local valid = crypto.verify_mac(test_data, test_key, mac)
|
||||
assert_equal(valid, true)
|
||||
|
||||
|
||||
local invalid = crypto.verify_mac("different data", test_key, mac)
|
||||
assert_equal(invalid, false)
|
||||
end)
|
||||
@ -122,13 +122,13 @@ end)
|
||||
test("UUID Generation", function()
|
||||
local uuid1 = crypto.uuid()
|
||||
local uuid2 = crypto.uuid_v4()
|
||||
|
||||
|
||||
assert_equal(type(uuid1), "string")
|
||||
assert_equal(type(uuid2), "string")
|
||||
assert_equal(#uuid1, 36) -- Standard UUID length
|
||||
assert_equal(#uuid2, 36)
|
||||
assert(uuid1 ~= uuid2, "UUIDs should be unique")
|
||||
|
||||
|
||||
assert_equal(crypto.is_uuid(uuid1), true)
|
||||
assert_equal(crypto.is_uuid(uuid2), true)
|
||||
assert_equal(crypto.is_uuid("not-a-uuid"), false)
|
||||
@ -141,7 +141,7 @@ end)
|
||||
test("Random Bytes", function()
|
||||
local bytes1 = crypto.random_bytes(16)
|
||||
local bytes2 = crypto.random_bytes(16)
|
||||
|
||||
|
||||
assert_equal(type(bytes1), "string")
|
||||
assert_equal(#bytes1, 16)
|
||||
assert(bytes1 ~= bytes2, "random bytes should be different")
|
||||
@ -150,7 +150,7 @@ end)
|
||||
test("Random Hex", function()
|
||||
local hex1 = crypto.random_hex(8)
|
||||
local hex2 = crypto.random_hex(8)
|
||||
|
||||
|
||||
assert_equal(type(hex1), "string")
|
||||
assert_equal(#hex1, 16) -- 8 bytes = 16 hex characters
|
||||
assert(hex1:match("^[0-9a-f]+$"), "should be hex")
|
||||
@ -160,11 +160,11 @@ end)
|
||||
test("Random String", function()
|
||||
local str1 = crypto.random_string(10)
|
||||
local str2 = crypto.random_string(10)
|
||||
|
||||
|
||||
assert_equal(type(str1), "string")
|
||||
assert_equal(#str1, 10)
|
||||
assert(str1 ~= str2, "random strings should be different")
|
||||
|
||||
|
||||
local custom = crypto.random_string(5, "abc")
|
||||
assert_equal(#custom, 5)
|
||||
assert(custom:match("^[abc]+$"), "should only contain specified characters")
|
||||
@ -179,7 +179,7 @@ end)
|
||||
test("Random Password", function()
|
||||
local pass1 = crypto.random_password(12)
|
||||
local pass2 = crypto.random_password(12, true) -- with symbols
|
||||
|
||||
|
||||
assert_equal(#pass1, 12)
|
||||
assert_equal(#pass2, 12)
|
||||
assert(pass1 ~= pass2, "passwords should be different")
|
||||
@ -188,7 +188,7 @@ end)
|
||||
test("Token Generation", function()
|
||||
local token1 = crypto.token(32)
|
||||
local token2 = crypto.token(32)
|
||||
|
||||
|
||||
assert_equal(#token1, 64) -- 32 bytes = 64 hex characters
|
||||
assert(token1:match("^[0-9a-f]+$"), "token should be hex")
|
||||
assert(token1 ~= token2, "tokens should be unique")
|
||||
@ -197,7 +197,7 @@ end)
|
||||
test("Nonce Generation", function()
|
||||
local nonce1 = crypto.nonce()
|
||||
local nonce2 = crypto.nonce(32)
|
||||
|
||||
|
||||
assert_equal(#nonce1, 32) -- default 16 bytes = 32 hex
|
||||
assert_equal(#nonce2, 64) -- 32 bytes = 64 hex
|
||||
assert(nonce1 ~= nonce2, "nonces should be unique")
|
||||
@ -211,7 +211,7 @@ test("Secure Compare", function()
|
||||
local str1 = "hello"
|
||||
local str2 = "hello"
|
||||
local str3 = "world"
|
||||
|
||||
|
||||
assert_equal(crypto.secure_compare(str1, str2), true)
|
||||
assert_equal(crypto.secure_compare(str1, str3), false)
|
||||
end)
|
||||
@ -219,10 +219,10 @@ end)
|
||||
test("Checksum Functions", function()
|
||||
local checksum = crypto.checksum(test_data)
|
||||
assert_equal(type(checksum), "string")
|
||||
|
||||
|
||||
local valid = crypto.verify_checksum(test_data, checksum)
|
||||
assert_equal(valid, true)
|
||||
|
||||
|
||||
local invalid = crypto.verify_checksum("different", checksum)
|
||||
assert_equal(invalid, false)
|
||||
end)
|
||||
@ -231,7 +231,7 @@ test("XOR Encryption", function()
|
||||
local key = "mykey"
|
||||
local encrypted = crypto.xor_encrypt(test_data, key)
|
||||
local decrypted = crypto.xor_decrypt(encrypted, key)
|
||||
|
||||
|
||||
assert_equal(decrypted, test_data)
|
||||
assert(encrypted ~= test_data, "encrypted should be different")
|
||||
end)
|
||||
@ -240,7 +240,7 @@ test("Hash Chain", function()
|
||||
local chain1 = crypto.hash_chain(test_data, 100)
|
||||
local chain2 = crypto.hash_chain(test_data, 100)
|
||||
local chain3 = crypto.hash_chain(test_data, 101)
|
||||
|
||||
|
||||
assert_equal(chain1, chain2)
|
||||
assert(chain1 ~= chain3, "different iterations should produce different results")
|
||||
end)
|
||||
@ -248,7 +248,7 @@ end)
|
||||
test("Key Derivation", function()
|
||||
local derived1, salt1 = crypto.derive_key("password", "salt", 1000)
|
||||
local derived2, salt2 = crypto.derive_key("password", salt1, 1000)
|
||||
|
||||
|
||||
assert_equal(derived1, derived2)
|
||||
assert_equal(salt1, salt2)
|
||||
assert_equal(type(derived1), "string")
|
||||
@ -259,7 +259,7 @@ test("Fingerprint", function()
|
||||
local data = {name = "test", value = 42}
|
||||
local fp1 = crypto.fingerprint(data)
|
||||
local fp2 = crypto.fingerprint(data)
|
||||
|
||||
|
||||
assert_equal(fp1, fp2)
|
||||
assert_equal(type(fp1), "string")
|
||||
assert(#fp1 > 0, "fingerprint should not be empty")
|
||||
@ -267,15 +267,15 @@ end)
|
||||
|
||||
test("Integrity Check", function()
|
||||
local check = crypto.integrity_check(test_data)
|
||||
|
||||
|
||||
assert_equal(check.data, test_data)
|
||||
assert_equal(type(check.hash), "string")
|
||||
assert_equal(type(check.timestamp), "number")
|
||||
assert_equal(type(check.uuid), "string")
|
||||
|
||||
|
||||
local valid = crypto.verify_integrity(check)
|
||||
assert_equal(valid, true)
|
||||
|
||||
|
||||
-- Tamper with data
|
||||
check.data = "tampered"
|
||||
local invalid = crypto.verify_integrity(check)
|
||||
@ -290,11 +290,11 @@ test("Error Handling", function()
|
||||
-- Invalid base64
|
||||
local success, err = pcall(crypto.base64_decode, "invalid===base64")
|
||||
assert_equal(success, false)
|
||||
|
||||
|
||||
-- Invalid hex
|
||||
local success2, err2 = pcall(crypto.hex_decode, "invalid_hex")
|
||||
assert_equal(success2, false)
|
||||
|
||||
|
||||
-- Invalid UUID validation (returns boolean, doesn't throw)
|
||||
assert_equal(crypto.is_uuid("not-a-uuid"), false)
|
||||
assert_equal(crypto.is_uuid(""), false)
|
||||
@ -307,26 +307,26 @@ end)
|
||||
|
||||
test("Performance Test", function()
|
||||
local large_data = string.rep("test data for performance ", 1000)
|
||||
|
||||
|
||||
local start = os.clock()
|
||||
local hash = crypto.sha256(large_data)
|
||||
local hash_time = os.clock() - start
|
||||
|
||||
|
||||
start = os.clock()
|
||||
local encoded = crypto.base64_encode(large_data)
|
||||
local encode_time = os.clock() - start
|
||||
|
||||
|
||||
start = os.clock()
|
||||
local decoded = crypto.base64_decode(encoded)
|
||||
local decode_time = os.clock() - start
|
||||
|
||||
|
||||
print(string.format(" SHA256 of %d bytes: %.3fs", #large_data, hash_time))
|
||||
print(string.format(" Base64 encode: %.3fs", encode_time))
|
||||
print(string.format(" Base64 decode: %.3fs", decode_time))
|
||||
|
||||
|
||||
assert_equal(decoded, large_data)
|
||||
assert_equal(type(hash), "string")
|
||||
end)
|
||||
|
||||
summary()
|
||||
test_exit()
|
||||
test_exit()
|
||||
|
888
tests/table.lua
Normal file
888
tests/table.lua
Normal file
@ -0,0 +1,888 @@
|
||||
require("tests")
|
||||
local tbl = require("table")
|
||||
|
||||
-- Test data
|
||||
local simple_array = {1, 2, 3, 4, 5}
|
||||
local simple_table = {a = 1, b = 2, c = 3}
|
||||
local mixed_table = {1, 2, a = "hello", b = "world"}
|
||||
local nested_table = {
|
||||
a = {x = 1, y = 2},
|
||||
b = {x = 3, y = 4},
|
||||
c = {1, 2, 3}
|
||||
}
|
||||
|
||||
-- ======================================================================
|
||||
-- BUILT-IN TABLE FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Insert Operations", function()
|
||||
local t = {1, 2, 3}
|
||||
|
||||
tbl.insert(t, 4)
|
||||
assert_equal(4, #t)
|
||||
assert_equal(4, t[4])
|
||||
|
||||
tbl.insert(t, 2, "inserted")
|
||||
assert_equal(5, #t)
|
||||
assert_equal("inserted", t[2])
|
||||
assert_equal(2, t[3])
|
||||
end)
|
||||
|
||||
test("Table Remove Operations", function()
|
||||
local t = {1, 2, 3, 4, 5}
|
||||
|
||||
local removed = tbl.remove(t)
|
||||
assert_equal(5, removed)
|
||||
assert_equal(4, #t)
|
||||
|
||||
removed = tbl.remove(t, 2)
|
||||
assert_equal(2, removed)
|
||||
assert_equal(3, #t)
|
||||
assert_equal(3, t[2])
|
||||
end)
|
||||
|
||||
test("Table Concat", function()
|
||||
local t = {"hello", "world", "test"}
|
||||
assert_equal("helloworldtest", tbl.concat(t))
|
||||
assert_equal("hello,world,test", tbl.concat(t, ","))
|
||||
assert_equal("world,test", tbl.concat(t, ",", 2))
|
||||
assert_equal("world", tbl.concat(t, ",", 2, 2))
|
||||
end)
|
||||
|
||||
test("Table Sort", function()
|
||||
local t = {3, 1, 4, 1, 5}
|
||||
tbl.sort(t)
|
||||
assert_table_equal({1, 1, 3, 4, 5}, t)
|
||||
|
||||
local t2 = {"c", "a", "b"}
|
||||
tbl.sort(t2)
|
||||
assert_table_equal({"a", "b", "c"}, t2)
|
||||
|
||||
local t3 = {3, 1, 4, 1, 5}
|
||||
tbl.sort(t3, function(a, b) return a > b end)
|
||||
assert_table_equal({5, 4, 3, 1, 1}, t3)
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- BASIC TABLE OPERATIONS
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Length and Size", function()
|
||||
assert_equal(5, tbl.length(simple_array))
|
||||
assert_equal(0, tbl.length({}))
|
||||
|
||||
assert_equal(3, tbl.size(simple_table))
|
||||
assert_equal(4, tbl.size(mixed_table))
|
||||
assert_equal(0, tbl.size({}))
|
||||
end)
|
||||
|
||||
test("Table Empty Check", function()
|
||||
assert_equal(true, tbl.is_empty({}))
|
||||
assert_equal(false, tbl.is_empty(simple_array))
|
||||
assert_equal(false, tbl.is_empty(simple_table))
|
||||
end)
|
||||
|
||||
test("Table Array Check", function()
|
||||
assert_equal(true, tbl.is_array(simple_array))
|
||||
assert_equal(true, tbl.is_array({}))
|
||||
assert_equal(false, tbl.is_array(simple_table))
|
||||
assert_equal(false, tbl.is_array(mixed_table))
|
||||
|
||||
assert_equal(true, tbl.is_array({1, 2, 3}))
|
||||
assert_equal(false, tbl.is_array({1, 2, nil, 4}))
|
||||
assert_equal(false, tbl.is_array({[0] = 1, [1] = 2}))
|
||||
end)
|
||||
|
||||
test("Table Clear", function()
|
||||
local t = tbl.clone(simple_table)
|
||||
tbl.clear(t)
|
||||
assert_equal(true, tbl.is_empty(t))
|
||||
end)
|
||||
|
||||
test("Table Clone", function()
|
||||
local cloned = tbl.clone(simple_table)
|
||||
assert_table_equal(simple_table, cloned)
|
||||
|
||||
-- Modify original shouldn't affect clone
|
||||
simple_table.new_key = "test"
|
||||
assert_equal(nil, cloned.new_key)
|
||||
simple_table.new_key = nil
|
||||
end)
|
||||
|
||||
test("Table Deep Copy", function()
|
||||
local copied = tbl.deep_copy(nested_table)
|
||||
assert_table_equal(nested_table, copied)
|
||||
|
||||
-- Modify nested part shouldn't affect copy
|
||||
nested_table.a.z = 99
|
||||
assert_equal(nil, copied.a.z)
|
||||
nested_table.a.z = nil
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- SEARCHING AND FINDING
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Contains", function()
|
||||
assert_equal(true, tbl.contains(simple_array, 3))
|
||||
assert_equal(false, tbl.contains(simple_array, 6))
|
||||
assert_equal(true, tbl.contains(simple_table, 2))
|
||||
assert_equal(false, tbl.contains(simple_table, "hello"))
|
||||
end)
|
||||
|
||||
test("Table Index Of", function()
|
||||
assert_equal(3, tbl.index_of(simple_array, 3))
|
||||
assert_equal(nil, tbl.index_of(simple_array, 6))
|
||||
assert_equal("b", tbl.index_of(simple_table, 2))
|
||||
assert_equal(nil, tbl.index_of(simple_table, "hello"))
|
||||
end)
|
||||
|
||||
test("Table Find", function()
|
||||
local value, key = tbl.find(simple_array, function(v) return v > 3 end)
|
||||
assert_equal(4, value)
|
||||
assert_equal(4, key)
|
||||
|
||||
local value2, key2 = tbl.find(simple_table, function(v, k) return k == "b" end)
|
||||
assert_equal(2, value2)
|
||||
assert_equal("b", key2)
|
||||
|
||||
local value3 = tbl.find(simple_array, function(v) return v > 10 end)
|
||||
assert_equal(nil, value3)
|
||||
end)
|
||||
|
||||
test("Table Find Index", function()
|
||||
local idx = tbl.find_index(simple_array, function(v) return v > 3 end)
|
||||
assert_equal(4, idx)
|
||||
|
||||
local idx2 = tbl.find_index(simple_table, function(v, k) return k == "c" end)
|
||||
assert_equal("c", idx2)
|
||||
|
||||
local idx3 = tbl.find_index(simple_array, function(v) return v > 10 end)
|
||||
assert_equal(nil, idx3)
|
||||
end)
|
||||
|
||||
test("Table Count", function()
|
||||
local arr = {1, 2, 3, 2, 4, 2}
|
||||
assert_equal(3, tbl.count(arr, 2))
|
||||
assert_equal(0, tbl.count(arr, 5))
|
||||
|
||||
assert_equal(2, tbl.count(arr, function(v) return v > 2 end))
|
||||
assert_equal(2, tbl.count(arr, function(v) return v == 1 or v == 4 end))
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- FILTERING AND MAPPING
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Filter", function()
|
||||
local evens = tbl.filter(simple_array, function(v) return v % 2 == 0 end)
|
||||
assert_table_equal({2, 4}, evens)
|
||||
|
||||
local filtered_table = tbl.filter(simple_table, function(v) return v > 1 end)
|
||||
assert_equal(2, tbl.size(filtered_table))
|
||||
assert_equal(2, filtered_table.b)
|
||||
assert_equal(3, filtered_table.c)
|
||||
end)
|
||||
|
||||
test("Table Reject", function()
|
||||
local odds = tbl.reject(simple_array, function(v) return v % 2 == 0 end)
|
||||
assert_table_equal({1, 3, 5}, odds)
|
||||
end)
|
||||
|
||||
test("Table Map", function()
|
||||
local doubled = tbl.map(simple_array, function(v) return v * 2 end)
|
||||
assert_table_equal({2, 4, 6, 8, 10}, doubled)
|
||||
|
||||
local mapped_table = tbl.map(simple_table, function(v) return v + 10 end)
|
||||
assert_equal(11, mapped_table.a)
|
||||
assert_equal(12, mapped_table.b)
|
||||
assert_equal(13, mapped_table.c)
|
||||
end)
|
||||
|
||||
test("Table Map Values", function()
|
||||
local incremented = tbl.map_values(simple_table, function(v) return v + 1 end)
|
||||
assert_equal(2, incremented.a)
|
||||
assert_equal(3, incremented.b)
|
||||
assert_equal(4, incremented.c)
|
||||
end)
|
||||
|
||||
test("Table Map Keys", function()
|
||||
local prefixed = tbl.map_keys(simple_table, function(k) return "key_" .. k end)
|
||||
assert_equal(1, prefixed.key_a)
|
||||
assert_equal(2, prefixed.key_b)
|
||||
assert_equal(3, prefixed.key_c)
|
||||
assert_equal(nil, prefixed.a)
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- REDUCING AND AGGREGATING
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Reduce", function()
|
||||
local sum = tbl.reduce(simple_array, function(acc, v) return acc + v end)
|
||||
assert_equal(15, sum)
|
||||
|
||||
local sum_with_initial = tbl.reduce(simple_array, function(acc, v) return acc + v end, 10)
|
||||
assert_equal(25, sum_with_initial)
|
||||
|
||||
local product = tbl.reduce({2, 3, 4}, function(acc, v) return acc * v end)
|
||||
assert_equal(24, product)
|
||||
end)
|
||||
|
||||
test("Table Fold", function()
|
||||
local sum = tbl.fold(simple_array, function(acc, v) return acc + v end, 0)
|
||||
assert_equal(15, sum)
|
||||
|
||||
local concatenated = tbl.fold({"a", "b", "c"}, function(acc, v) return acc .. v end, "")
|
||||
assert_equal("abc", concatenated)
|
||||
end)
|
||||
|
||||
test("Table Math Operations", function()
|
||||
assert_equal(15, tbl.sum(simple_array))
|
||||
assert_equal(120, tbl.product(simple_array))
|
||||
assert_equal(1, tbl.min(simple_array))
|
||||
assert_equal(5, tbl.max(simple_array))
|
||||
assert_equal(3, tbl.average(simple_array))
|
||||
|
||||
local floats = {1.5, 2.5, 3.0}
|
||||
assert_close(7.0, tbl.sum(floats))
|
||||
assert_close(2.33333, tbl.average(floats), 0.001)
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- BOOLEAN OPERATIONS
|
||||
-- ======================================================================
|
||||
|
||||
test("Table All", function()
|
||||
assert_equal(true, tbl.all({true, true, true}))
|
||||
assert_equal(false, tbl.all({true, false, true}))
|
||||
assert_equal(true, tbl.all({}))
|
||||
|
||||
assert_equal(true, tbl.all(simple_array, function(v) return v > 0 end))
|
||||
assert_equal(false, tbl.all(simple_array, function(v) return v > 3 end))
|
||||
end)
|
||||
|
||||
test("Table Any", function()
|
||||
assert_equal(true, tbl.any({false, true, false}))
|
||||
assert_equal(false, tbl.any({false, false, false}))
|
||||
assert_equal(false, tbl.any({}))
|
||||
|
||||
assert_equal(true, tbl.any(simple_array, function(v) return v > 3 end))
|
||||
assert_equal(false, tbl.any(simple_array, function(v) return v > 10 end))
|
||||
end)
|
||||
|
||||
test("Table None", function()
|
||||
assert_equal(true, tbl.none({false, false, false}))
|
||||
assert_equal(false, tbl.none({false, true, false}))
|
||||
assert_equal(true, tbl.none({}))
|
||||
|
||||
assert_equal(false, tbl.none(simple_array, function(v) return v > 3 end))
|
||||
assert_equal(true, tbl.none(simple_array, function(v) return v > 10 end))
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- SET OPERATIONS
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Unique", function()
|
||||
local duplicates = {1, 2, 2, 3, 3, 3, 4}
|
||||
local unique = tbl.unique(duplicates)
|
||||
assert_table_equal({1, 2, 3, 4}, unique)
|
||||
|
||||
local empty_unique = tbl.unique({})
|
||||
assert_table_equal({}, empty_unique)
|
||||
end)
|
||||
|
||||
test("Table Intersection", function()
|
||||
local arr1 = {1, 2, 3, 4}
|
||||
local arr2 = {3, 4, 5, 6}
|
||||
local intersect = tbl.intersection(arr1, arr2)
|
||||
assert_equal(2, #intersect)
|
||||
assert_equal(true, tbl.contains(intersect, 3))
|
||||
assert_equal(true, tbl.contains(intersect, 4))
|
||||
end)
|
||||
|
||||
test("Table Union", function()
|
||||
local arr1 = {1, 2, 3}
|
||||
local arr2 = {3, 4, 5}
|
||||
local union = tbl.union(arr1, arr2)
|
||||
assert_equal(5, #union)
|
||||
for i = 1, 5 do
|
||||
assert_equal(true, tbl.contains(union, i))
|
||||
end
|
||||
end)
|
||||
|
||||
test("Table Difference", function()
|
||||
local arr1 = {1, 2, 3, 4, 5}
|
||||
local arr2 = {3, 4}
|
||||
local diff = tbl.difference(arr1, arr2)
|
||||
assert_table_equal({1, 2, 5}, diff)
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- ARRAY OPERATIONS
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Reverse", function()
|
||||
local reversed = tbl.reverse(simple_array)
|
||||
assert_table_equal({5, 4, 3, 2, 1}, reversed)
|
||||
|
||||
local single = tbl.reverse({42})
|
||||
assert_table_equal({42}, single)
|
||||
|
||||
local empty = tbl.reverse({})
|
||||
assert_table_equal({}, empty)
|
||||
end)
|
||||
|
||||
test("Table Shuffle", function()
|
||||
local shuffled = tbl.shuffle(simple_array)
|
||||
assert_equal(5, #shuffled)
|
||||
|
||||
-- All original elements should still be present
|
||||
for _, v in ipairs(simple_array) do
|
||||
assert_equal(true, tbl.contains(shuffled, v))
|
||||
end
|
||||
|
||||
-- Should be same length
|
||||
assert_equal(#simple_array, #shuffled)
|
||||
end)
|
||||
|
||||
test("Table Rotate", function()
|
||||
local arr = {1, 2, 3, 4, 5}
|
||||
|
||||
local rotated_right = tbl.rotate(arr, 2)
|
||||
assert_table_equal({4, 5, 1, 2, 3}, rotated_right)
|
||||
|
||||
local rotated_left = tbl.rotate(arr, -2)
|
||||
assert_table_equal({3, 4, 5, 1, 2}, rotated_left)
|
||||
|
||||
local no_rotation = tbl.rotate(arr, 0)
|
||||
assert_table_equal(arr, no_rotation)
|
||||
|
||||
local full_rotation = tbl.rotate(arr, 5)
|
||||
assert_table_equal(arr, full_rotation)
|
||||
end)
|
||||
|
||||
test("Table Slice", function()
|
||||
local sliced = tbl.slice(simple_array, 2, 4)
|
||||
assert_table_equal({2, 3, 4}, sliced)
|
||||
|
||||
local from_start = tbl.slice(simple_array, 1, 3)
|
||||
assert_table_equal({1, 2, 3}, from_start)
|
||||
|
||||
local to_end = tbl.slice(simple_array, 3)
|
||||
assert_table_equal({3, 4, 5}, to_end)
|
||||
|
||||
local negative_indices = tbl.slice(simple_array, -3, -1)
|
||||
assert_table_equal({3, 4, 5}, negative_indices)
|
||||
end)
|
||||
|
||||
test("Table Splice", function()
|
||||
local arr = {1, 2, 3, 4, 5}
|
||||
|
||||
-- Remove elements
|
||||
local removed = tbl.splice(arr, 2, 2)
|
||||
assert_table_equal({2, 3}, removed)
|
||||
assert_table_equal({1, 4, 5}, arr)
|
||||
|
||||
-- Insert elements
|
||||
arr = {1, 2, 3, 4, 5}
|
||||
removed = tbl.splice(arr, 3, 0, "a", "b")
|
||||
assert_table_equal({}, removed)
|
||||
assert_table_equal({1, 2, "a", "b", 3, 4, 5}, arr)
|
||||
|
||||
-- Replace elements
|
||||
arr = {1, 2, 3, 4, 5}
|
||||
removed = tbl.splice(arr, 2, 2, "x", "y", "z")
|
||||
assert_table_equal({2, 3}, removed)
|
||||
assert_table_equal({1, "x", "y", "z", 4, 5}, arr)
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- SORTING HELPERS
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Sort By", function()
|
||||
local people = {
|
||||
{name = "Alice", age = 30},
|
||||
{name = "Bob", age = 25},
|
||||
{name = "Charlie", age = 35}
|
||||
}
|
||||
|
||||
local sorted_by_age = tbl.sort_by(people, function(p) return p.age end)
|
||||
assert_equal("Bob", sorted_by_age[1].name)
|
||||
assert_equal("Charlie", sorted_by_age[3].name)
|
||||
|
||||
local sorted_by_name = tbl.sort_by(people, function(p) return p.name end)
|
||||
assert_equal("Alice", sorted_by_name[1].name)
|
||||
assert_equal("Charlie", sorted_by_name[3].name)
|
||||
end)
|
||||
|
||||
test("Table Is Sorted", function()
|
||||
assert_equal(true, tbl.is_sorted({1, 2, 3, 4, 5}))
|
||||
assert_equal(false, tbl.is_sorted({1, 3, 2, 4, 5}))
|
||||
assert_equal(true, tbl.is_sorted({}))
|
||||
assert_equal(true, tbl.is_sorted({42}))
|
||||
|
||||
assert_equal(true, tbl.is_sorted({5, 4, 3, 2, 1}, function(a, b) return a > b end))
|
||||
assert_equal(false, tbl.is_sorted({1, 2, 3, 4, 5}, function(a, b) return a > b end))
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- UTILITY FUNCTIONS
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Keys and Values", function()
|
||||
local keys = tbl.keys(simple_table)
|
||||
assert_equal(3, #keys)
|
||||
assert_equal(true, tbl.contains(keys, "a"))
|
||||
assert_equal(true, tbl.contains(keys, "b"))
|
||||
assert_equal(true, tbl.contains(keys, "c"))
|
||||
|
||||
local values = tbl.values(simple_table)
|
||||
assert_equal(3, #values)
|
||||
assert_equal(true, tbl.contains(values, 1))
|
||||
assert_equal(true, tbl.contains(values, 2))
|
||||
assert_equal(true, tbl.contains(values, 3))
|
||||
end)
|
||||
|
||||
test("Table Pairs", function()
|
||||
local pairs_list = tbl.pairs({a = 1, b = 2})
|
||||
assert_equal(2, #pairs_list)
|
||||
|
||||
-- Should contain key-value pairs
|
||||
local found_a, found_b = false, false
|
||||
for _, pair in ipairs(pairs_list) do
|
||||
if pair[1] == "a" and pair[2] == 1 then found_a = true end
|
||||
if pair[1] == "b" and pair[2] == 2 then found_b = true end
|
||||
end
|
||||
assert_equal(true, found_a)
|
||||
assert_equal(true, found_b)
|
||||
end)
|
||||
|
||||
test("Table Merge", function()
|
||||
local t1 = {a = 1, b = 2}
|
||||
local t2 = {c = 3, d = 4}
|
||||
local t3 = {b = 20, e = 5}
|
||||
|
||||
local merged = tbl.merge(t1, t2, t3)
|
||||
assert_equal(5, tbl.size(merged))
|
||||
assert_equal(1, merged.a)
|
||||
assert_equal(20, merged.b) -- Last one wins
|
||||
assert_equal(3, merged.c)
|
||||
assert_equal(4, merged.d)
|
||||
assert_equal(5, merged.e)
|
||||
end)
|
||||
|
||||
test("Table Extend", function()
|
||||
local t1 = {a = 1, b = 2}
|
||||
local t2 = {c = 3, d = 4}
|
||||
|
||||
local extended = tbl.extend(t1, t2)
|
||||
assert_equal(t1, extended) -- Should return t1
|
||||
assert_equal(4, tbl.size(t1))
|
||||
assert_equal(3, t1.c)
|
||||
assert_equal(4, t1.d)
|
||||
end)
|
||||
|
||||
test("Table Invert", function()
|
||||
local inverted = tbl.invert(simple_table)
|
||||
assert_equal("a", inverted[1])
|
||||
assert_equal("b", inverted[2])
|
||||
assert_equal("c", inverted[3])
|
||||
end)
|
||||
|
||||
test("Table Pick and Omit", function()
|
||||
local big_table = {a = 1, b = 2, c = 3, d = 4, e = 5}
|
||||
|
||||
local picked = tbl.pick(big_table, "a", "c", "e")
|
||||
assert_equal(3, tbl.size(picked))
|
||||
assert_equal(1, picked.a)
|
||||
assert_equal(3, picked.c)
|
||||
assert_equal(5, picked.e)
|
||||
assert_equal(nil, picked.b)
|
||||
assert_equal(nil, picked.d)
|
||||
|
||||
local omitted = tbl.omit(big_table, "b", "d")
|
||||
assert_equal(3, tbl.size(omitted))
|
||||
assert_equal(1, omitted.a)
|
||||
assert_equal(3, omitted.c)
|
||||
assert_equal(5, omitted.e)
|
||||
assert_equal(nil, omitted.b)
|
||||
assert_equal(nil, omitted.d)
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- DEEP OPERATIONS
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Deep Equals", function()
|
||||
local t1 = {a = {x = 1, y = 2}, b = {1, 2, 3}}
|
||||
local t2 = {a = {x = 1, y = 2}, b = {1, 2, 3}}
|
||||
local t3 = {a = {x = 1, y = 3}, b = {1, 2, 3}}
|
||||
|
||||
assert_equal(true, tbl.deep_equals(t1, t2))
|
||||
assert_equal(false, tbl.deep_equals(t1, t3))
|
||||
assert_equal(true, tbl.deep_equals({}, {}))
|
||||
assert_equal(false, tbl.deep_equals({a = 1}, {a = 1, b = 2}))
|
||||
end)
|
||||
|
||||
test("Table Flatten", function()
|
||||
local nested = {{1, 2}, {3, 4}, {5, {6, 7}}}
|
||||
local flattened = tbl.flatten(nested)
|
||||
assert_table_equal({1, 2, 3, 4, 5, {6, 7}}, flattened)
|
||||
|
||||
local deep_flattened = tbl.flatten(nested, 2)
|
||||
assert_table_equal({1, 2, 3, 4, 5, 6, 7}, deep_flattened)
|
||||
|
||||
local already_flat = tbl.flatten({1, 2, 3})
|
||||
assert_table_equal({1, 2, 3}, already_flat)
|
||||
end)
|
||||
|
||||
test("Table Deep Merge", function()
|
||||
local t1 = {a = {x = 1}, b = 2}
|
||||
local t2 = {a = {y = 3}, c = 4}
|
||||
|
||||
local merged = tbl.deep_merge(t1, t2)
|
||||
assert_equal(1, merged.a.x)
|
||||
assert_equal(3, merged.a.y)
|
||||
assert_equal(2, merged.b)
|
||||
assert_equal(4, merged.c)
|
||||
|
||||
-- Original tables should be unchanged
|
||||
assert_equal(nil, t1.a.y)
|
||||
assert_equal(nil, t1.c)
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- ADVANCED OPERATIONS
|
||||
-- ======================================================================
|
||||
|
||||
test("Table Chunk", function()
|
||||
local chunks = tbl.chunk({1, 2, 3, 4, 5, 6, 7}, 3)
|
||||
assert_equal(3, #chunks)
|
||||
assert_table_equal({1, 2, 3}, chunks[1])
|
||||
assert_table_equal({4, 5, 6}, chunks[2])
|
||||
assert_table_equal({7}, chunks[3])
|
||||
|
||||
local exact_chunks = tbl.chunk({1, 2, 3, 4}, 2)
|
||||
assert_equal(2, #exact_chunks)
|
||||
assert_table_equal({1, 2}, exact_chunks[1])
|
||||
assert_table_equal({3, 4}, exact_chunks[2])
|
||||
end)
|
||||
|
||||
test("Table Partition", function()
|
||||
local evens, odds = tbl.partition(simple_array, function(v) return v % 2 == 0 end)
|
||||
assert_table_equal({2, 4}, evens)
|
||||
assert_table_equal({1, 3, 5}, odds)
|
||||
|
||||
local empty_true, all_false = tbl.partition({1, 3, 5}, function(v) return v % 2 == 0 end)
|
||||
assert_table_equal({}, empty_true)
|
||||
assert_table_equal({1, 3, 5}, all_false)
|
||||
end)
|
||||
|
||||
test("Table Group By", function()
|
||||
local people = {
|
||||
{name = "Alice", department = "engineering"},
|
||||
{name = "Bob", department = "sales"},
|
||||
{name = "Charlie", department = "engineering"},
|
||||
{name = "David", department = "sales"}
|
||||
}
|
||||
|
||||
local by_dept = tbl.group_by(people, function(person) return person.department end)
|
||||
assert_equal(2, tbl.size(by_dept))
|
||||
assert_equal(2, #by_dept.engineering)
|
||||
assert_equal(2, #by_dept.sales)
|
||||
assert_equal("Alice", by_dept.engineering[1].name)
|
||||
assert_equal("Bob", by_dept.sales[1].name)
|
||||
end)
|
||||
|
||||
test("Table Zip", function()
|
||||
local names = {"Alice", "Bob", "Charlie"}
|
||||
local ages = {25, 30, 35}
|
||||
local cities = {"NYC", "LA", "Chicago"}
|
||||
|
||||
local zipped = tbl.zip(names, ages, cities)
|
||||
assert_equal(3, #zipped)
|
||||
assert_table_equal({"Alice", 25, "NYC"}, zipped[1])
|
||||
assert_table_equal({"Bob", 30, "LA"}, zipped[2])
|
||||
assert_table_equal({"Charlie", 35, "Chicago"}, zipped[3])
|
||||
|
||||
-- Different lengths
|
||||
local short_zip = tbl.zip({1, 2, 3}, {"a", "b"})
|
||||
assert_equal(2, #short_zip)
|
||||
assert_table_equal({1, "a"}, short_zip[1])
|
||||
assert_table_equal({2, "b"}, short_zip[2])
|
||||
end)
|
||||
|
||||
test("Table Compact", function()
|
||||
local messy = {1, nil, false, 2, nil, 3, false}
|
||||
local compacted = tbl.compact(messy)
|
||||
assert_table_equal({1, 2, 3}, compacted)
|
||||
|
||||
local clean = {1, 2, 3}
|
||||
local unchanged = tbl.compact(clean)
|
||||
assert_table_equal(clean, unchanged)
|
||||
end)
|
||||
|
||||
test("Table Sample", function()
|
||||
local sample1 = tbl.sample(simple_array, 3)
|
||||
assert_equal(3, #sample1)
|
||||
|
||||
-- All sampled elements should be from original
|
||||
for _, v in ipairs(sample1) do
|
||||
assert_equal(true, tbl.contains(simple_array, v))
|
||||
end
|
||||
|
||||
local single_sample = tbl.sample(simple_array)
|
||||
assert_equal(1, #single_sample)
|
||||
assert_equal(true, tbl.contains(simple_array, single_sample[1]))
|
||||
|
||||
local oversample = tbl.sample({1, 2}, 5)
|
||||
assert_equal(2, #oversample)
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- EDGE CASES AND ERROR HANDLING
|
||||
-- ======================================================================
|
||||
|
||||
test("Empty Table Handling", function()
|
||||
local empty = {}
|
||||
|
||||
assert_equal(true, tbl.is_empty(empty))
|
||||
assert_equal(0, tbl.length(empty))
|
||||
assert_equal(0, tbl.size(empty))
|
||||
assert_equal(true, tbl.is_array(empty))
|
||||
|
||||
assert_table_equal({}, tbl.filter(empty, function() return true end))
|
||||
assert_table_equal({}, tbl.map(empty, function(v) return v * 2 end))
|
||||
assert_table_equal({}, tbl.keys(empty))
|
||||
assert_table_equal({}, tbl.values(empty))
|
||||
|
||||
assert_equal(true, tbl.all(empty))
|
||||
assert_equal(false, tbl.any(empty))
|
||||
assert_equal(true, tbl.none(empty))
|
||||
end)
|
||||
|
||||
test("Single Element Tables", function()
|
||||
local single = {42}
|
||||
|
||||
assert_equal(1, tbl.length(single))
|
||||
assert_equal(1, tbl.size(single))
|
||||
assert_equal(true, tbl.is_array(single))
|
||||
assert_equal(false, tbl.is_empty(single))
|
||||
|
||||
assert_equal(42, tbl.sum(single))
|
||||
assert_equal(42, tbl.product(single))
|
||||
assert_equal(42, tbl.min(single))
|
||||
assert_equal(42, tbl.max(single))
|
||||
assert_equal(42, tbl.average(single))
|
||||
|
||||
assert_table_equal({42}, tbl.reverse(single))
|
||||
assert_table_equal({84}, tbl.map(single, function(v) return v * 2 end))
|
||||
end)
|
||||
|
||||
test("Circular Reference Handling", function()
|
||||
local t1 = {a = 1}
|
||||
local t2 = {b = 2}
|
||||
t1.ref = t2
|
||||
t2.ref = t1
|
||||
|
||||
-- Deep copy should handle circular references
|
||||
local copied = tbl.deep_copy(t1)
|
||||
assert_equal(1, copied.a)
|
||||
assert_equal(2, copied.ref.b)
|
||||
assert_equal(copied, copied.ref.ref) -- Should maintain circular structure
|
||||
end)
|
||||
|
||||
test("Large Table Performance", function()
|
||||
local large = {}
|
||||
for i = 1, 10000 do
|
||||
large[i] = i
|
||||
end
|
||||
|
||||
assert_equal(10000, tbl.length(large))
|
||||
assert_equal(true, tbl.is_array(large))
|
||||
assert_equal(50005000, tbl.sum(large)) -- Sum of 1 to 10000
|
||||
|
||||
local evens = tbl.filter(large, function(v) return v % 2 == 0 end)
|
||||
assert_equal(5000, #evens)
|
||||
|
||||
local doubled = tbl.map(large, function(v) return v * 2 end)
|
||||
assert_equal(10000, #doubled)
|
||||
assert_equal(2, doubled[1])
|
||||
assert_equal(20000, doubled[10000])
|
||||
end)
|
||||
|
||||
test("Mixed Type Table Handling", function()
|
||||
local mixed = {1, "hello", true, {a = 1}, function() end}
|
||||
|
||||
assert_equal(5, tbl.length(mixed))
|
||||
assert_equal(true, tbl.is_array(mixed))
|
||||
assert_equal(true, tbl.contains(mixed, "hello"))
|
||||
assert_equal(true, tbl.contains(mixed, true))
|
||||
|
||||
local strings_only = tbl.filter(mixed, function(v) return type(v) == "string" end)
|
||||
assert_equal(1, #strings_only)
|
||||
assert_equal("hello", strings_only[1])
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- PERFORMANCE TESTS
|
||||
-- ======================================================================
|
||||
|
||||
test("Performance Test", function()
|
||||
local large_array = {}
|
||||
for i = 1, 10000 do
|
||||
large_array[i] = math.random(1, 1000)
|
||||
end
|
||||
|
||||
local start = os.clock()
|
||||
local filtered = tbl.filter(large_array, function(v) return v > 500 end)
|
||||
local filter_time = os.clock() - start
|
||||
|
||||
start = os.clock()
|
||||
local mapped = tbl.map(large_array, function(v) return v * 2 end)
|
||||
local map_time = os.clock() - start
|
||||
|
||||
start = os.clock()
|
||||
local sum = tbl.sum(large_array)
|
||||
local sum_time = os.clock() - start
|
||||
|
||||
start = os.clock()
|
||||
local sorted = tbl.sort_by(large_array, function(v) return v end)
|
||||
local sort_time = os.clock() - start
|
||||
|
||||
start = os.clock()
|
||||
local unique = tbl.unique(large_array)
|
||||
local unique_time = os.clock() - start
|
||||
|
||||
print(string.format(" Filter %d elements: %.3fs", #filtered, filter_time))
|
||||
print(string.format(" Map %d elements: %.3fs", #mapped, map_time))
|
||||
print(string.format(" Sum %d elements: %.3fs", #large_array, sum_time))
|
||||
print(string.format(" Sort %d elements: %.3fs", #sorted, sort_time))
|
||||
print(string.format(" Unique from %d to %d: %.3fs", #large_array, #unique, unique_time))
|
||||
|
||||
assert(#filtered > 0, "should filter some elements")
|
||||
assert_equal(#large_array, #mapped)
|
||||
assert(sum > 0, "sum should be positive")
|
||||
assert_equal(#large_array, #sorted)
|
||||
assert(tbl.is_sorted(sorted), "should be sorted")
|
||||
end)
|
||||
|
||||
-- ======================================================================
|
||||
-- INTEGRATION TESTS
|
||||
-- ======================================================================
|
||||
|
||||
test("Data Processing Pipeline", function()
|
||||
local sales_data = {
|
||||
{product = "laptop", price = 1000, quantity = 2, category = "electronics"},
|
||||
{product = "mouse", price = 25, quantity = 10, category = "electronics"},
|
||||
{product = "book", price = 15, quantity = 5, category = "books"},
|
||||
{product = "phone", price = 800, quantity = 3, category = "electronics"},
|
||||
{product = "magazine", price = 5, quantity = 20, category = "books"}
|
||||
}
|
||||
|
||||
-- Calculate total revenue per item
|
||||
local with_revenue = tbl.map(sales_data, function(item)
|
||||
local new_item = tbl.clone(item)
|
||||
new_item.revenue = item.price * item.quantity
|
||||
return new_item
|
||||
end)
|
||||
|
||||
-- Filter high-value items (revenue >= 100)
|
||||
local high_value = tbl.filter(with_revenue, function(item)
|
||||
return item.revenue >= 100
|
||||
end)
|
||||
|
||||
-- Group by category
|
||||
local by_category = tbl.group_by(high_value, function(item)
|
||||
return item.category
|
||||
end)
|
||||
|
||||
-- Calculate total revenue by category
|
||||
local category_totals = tbl.map_values(by_category, function(items)
|
||||
return tbl.sum(tbl.map(items, function(item) return item.revenue end))
|
||||
end)
|
||||
|
||||
assert_equal(2, tbl.size(category_totals))
|
||||
assert_equal(4650, category_totals.electronics) -- laptop: 2000, mouse: 250, phone: 2400
|
||||
assert_equal(100, category_totals.books) -- magazine: 100
|
||||
end)
|
||||
|
||||
test("Complex Data Transformation", function()
|
||||
local users = {
|
||||
{id = 1, name = "Alice", age = 25, skills = {"lua", "python"}},
|
||||
{id = 2, name = "Bob", age = 30, skills = {"javascript", "lua"}},
|
||||
{id = 3, name = "Charlie", age = 35, skills = {"python", "java"}},
|
||||
{id = 4, name = "David", age = 28, skills = {"lua", "go"}}
|
||||
}
|
||||
|
||||
-- Find Lua developers
|
||||
local lua_devs = tbl.filter(users, function(user)
|
||||
return tbl.contains(user.skills, "lua")
|
||||
end)
|
||||
|
||||
-- Sort by age
|
||||
local sorted_lua_devs = tbl.sort_by(lua_devs, function(user) return user.age end)
|
||||
|
||||
-- Extract just names and ages
|
||||
local simplified = tbl.map(sorted_lua_devs, function(user)
|
||||
return {name = user.name, age = user.age}
|
||||
end)
|
||||
|
||||
assert_equal(3, #simplified)
|
||||
assert_equal("Alice", simplified[1].name) -- Youngest
|
||||
assert_equal("David", simplified[2].name)
|
||||
assert_equal("Bob", simplified[3].name) -- Oldest
|
||||
|
||||
-- Group all users by age ranges
|
||||
local age_groups = tbl.group_by(users, function(user)
|
||||
if user.age < 30 then return "young"
|
||||
else return "experienced" end
|
||||
end)
|
||||
|
||||
assert_equal(2, #age_groups.young) -- Alice, David
|
||||
assert_equal(2, #age_groups.experienced) -- Bob, Charlie
|
||||
end)
|
||||
|
||||
test("Statistical Analysis", function()
|
||||
local test_scores = {
|
||||
{student = "Alice", scores = {85, 92, 78, 95, 88}},
|
||||
{student = "Bob", scores = {72, 85, 90, 77, 82}},
|
||||
{student = "Charlie", scores = {95, 88, 92, 90, 85}},
|
||||
{student = "David", scores = {68, 75, 80, 72, 78}}
|
||||
}
|
||||
|
||||
-- Calculate average score for each student
|
||||
local with_averages = tbl.map(test_scores, function(student)
|
||||
local avg = tbl.average(student.scores)
|
||||
return {
|
||||
student = student.student,
|
||||
scores = student.scores,
|
||||
average = avg,
|
||||
max_score = tbl.max(student.scores),
|
||||
min_score = tbl.min(student.scores)
|
||||
}
|
||||
end)
|
||||
|
||||
-- Find top performer
|
||||
local top_student = tbl.reduce(with_averages, function(best, current)
|
||||
return current.average > best.average and current or best
|
||||
end)
|
||||
|
||||
-- Students above class average
|
||||
local all_averages = tbl.map(with_averages, function(s) return s.average end)
|
||||
local class_average = tbl.average(all_averages)
|
||||
local above_average = tbl.filter(with_averages, function(s)
|
||||
return s.average > class_average
|
||||
end)
|
||||
|
||||
assert_equal("Charlie", top_student.student)
|
||||
assert_equal(90, top_student.average)
|
||||
assert_equal(2, #above_average) -- Charlie and Alice
|
||||
assert_close(83.4, class_average, 0.1)
|
||||
end)
|
||||
|
||||
summary()
|
||||
test_exit()
|
Loading…
x
Reference in New Issue
Block a user