-------------------------------------------------------------------------------- -- 0160-string.lua: tests for string-related tools -- This file is a part of lua-nucleo library -- Copyright (c) lua-nucleo authors (see file `COPYRIGHT` for the license) -------------------------------------------------------------------------------- local make_suite = assert(loadfile('test/test-lib/init/strict.lua'))(...) local arguments = import 'lua-nucleo/args.lua' { 'arguments' } local ensure, ensure_equals, ensure_strequals, ensure_tequals, ensure_fails_with_substring = import 'lua-nucleo/ensure.lua' { 'ensure', 'ensure_equals', 'ensure_strequals', 'ensure_tequals', 'ensure_fails_with_substring' } local make_concatter, trim, escape_string, htmlspecialchars, fill_placeholders_ex, fill_placeholders, fill_curly_placeholders, cdata_wrap, cdata_cat, split_by_char, split_by_offset, count_substrings, kv_concat, escape_lua_pattern, escape_for_json, starts_with, ends_with, create_escape_subst, url_encode, integer_to_string_with_base, cut_with_ellipsis, number_to_string, serialize_number, string_exports = import 'lua-nucleo/string.lua' { 'make_concatter', 'trim', 'escape_string', 'htmlspecialchars', 'fill_placeholders_ex', 'fill_placeholders', 'fill_curly_placeholders', 'cdata_wrap', 'cdata_cat', 'split_by_char', 'split_by_offset', 'count_substrings', 'kv_concat', 'escape_lua_pattern', 'escape_for_json', 'starts_with', 'ends_with', 'create_escape_subst', 'url_encode', 'integer_to_string_with_base', 'cut_with_ellipsis', 'number_to_string', 'serialize_number' } local math_pi = math.pi -------------------------------------------------------------------------------- local test = make_suite("string", string_exports) -------------------------------------------------------------------------------- test:tests_for "make_concatter" test "make_concatter-basic" (function() local cat, concat = make_concatter() ensure_equals("cat is function", type(cat), "function") ensure_equals("concat is function", type(concat), "function") ensure_equals("cat returns self", cat("42"), cat) ensure_equals("concat on single element", concat(), "42") end) test "make_concatter-empty" (function() local cat, concat = make_concatter() ensure_equals("concat on empty data is empty string", concat(), "") end) test "make_concatter-simple" (function() local cat, concat = make_concatter() cat "a" cat "bc" (42) cat "" "d" "" ensure_equals("concat", concat(), "abc42d") end) test "make_concatter-embedded-zeroes" (function() local cat, concat = make_concatter() cat "a" "\0" "bc\0" "def\0" ensure_equals("concat", concat(), "a\0bc\0def\0") end) test "make_concatter-glue" (function() local cat, concat = make_concatter() cat "a" "\0" "bc\0" "def\0" ensure_equals("concat", concat("{\0}"), "a{\0}\0{\0}bc\0{\0}def\0") end) -------------------------------------------------------------------------------- test:tests_for "trim" test "trim-basic" (function() ensure_equals("empty string", trim(""), "") ensure_equals("none", trim("a"), "a") ensure_equals("left", trim(" b"), "b") ensure_equals("right", trim("c "), "c") ensure_equals("both", trim(" d "), "d") ensure_equals("middle", trim("e f"), "e f") ensure_equals("many", trim("\t \t \tg \th\t \t "), "g \th") end) -------------------------------------------------------------------------------- test:tests_for "starts_with" test "starts_with-minimal" (function() ensure_equals("trivial", starts_with("", ""), true) ensure_equals("strings always start with empty string", starts_with("abc", ""), true) ensure_equals("1..1", starts_with("abc", "a"), true) ensure_equals("1..2", starts_with("abc", "ab"), true) ensure_equals("1..3", starts_with("abc", "abc"), true) ensure_equals("1..4", starts_with("abc", "abcb"), false) ensure_equals("special char", starts_with("abc", "\000"), false) ensure_equals("binary-safe", starts_with("Русский язык велик и могуч", "Русский я"), true) ensure_equals("against number", starts_with("foo", 1), false) ensure_equals("against boolean", starts_with("foo", false), false) ensure_equals("against table", starts_with("foo", {}), false) ensure_equals("against nil", starts_with("foo", nil), false) end) test:tests_for "ends_with" test "ends_with-minimal" (function() ensure_equals("trivial", ends_with("", ""), true) ensure_equals("strings always end with empty string", ends_with("abc", ""), true) ensure_equals("1..1", ends_with("abc", "c"), true) ensure_equals("1..2", ends_with("abc", "bc"), true) ensure_equals("1..3", ends_with("abc", "abc"), true) ensure_equals("1..4", ends_with("abc", "abc "), false) ensure_equals("special char", ends_with("abc", "\000"), false) ensure_equals("binary-safe", ends_with("Русский язык велик и могуч", "к и могуч"), true) ensure_equals("against number", ends_with("foo", 1), false) ensure_equals("against boolean", ends_with("foo", false), false) ensure_equals("against table", ends_with("foo", {}), false) ensure_equals("against nil", ends_with("foo", nil), false) end) -------------------------------------------------------------------------------- test:tests_for "escape_string" test "escape_string-minimal" (function () ensure_equals( "Equal strings", escape_string("simple str without wrong chars"), "simple str without wrong chars" ) ensure_equals("escaped str exceptions", escape_string("\009\010"), "\t\n") ensure_equals( "escaped str", escape_string("\000\001\128"), "%00%01%80" ) local str_test = "" for i = 0, 255 do str_test = str_test .. string.char(i) end ensure_equals("escaped str all symbols 129 - 255", escape_string(str_test), [[%00%01%02%03%04%05%06%07%08]] .. "\t\n" .. [[%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F !"#$%]] .. [[&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij]] .. [[klmnopqrstuvwxyz{|}~%7F%80%81%82%83%84%85%86%87%88%89%8A%8B%8C%8D%8E%]] .. [[8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%A0%A1%A2%A3%A4%A5%]] .. [[A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%B8%B9%BA%BB%BC%]] .. [[BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%D0%D1%D2%D3%]] .. [[D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%E8%E9%EA%]] .. [[EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF]]) end) -------------------------------------------------------------------------------- test:tests_for "create_escape_subst" test "create_escape_subst-minimal" (function () local escape_subst = create_escape_subst("\\%03d") local str_test = "" for i = 0, 255 do str_test = str_test .. string.char(i) end ensure_equals( "Equal strings", tostring( str_test ):gsub("[%c%z\128-\255]", escape_subst), [[\000\001\002\003\004\005\006\007\008]] .. "\t\n" .. [[\011\012\013\014\015\016\017]] .. [[\018\019\020\021\022\023\024\025\026\027\028\029\030\031 !"#$%&'()*+]] .. [[,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno]] .. [[pqrstuvwxyz{|}~\127\128\129\130\131\132\133\134\135\136\137\138\139\]] .. [[140\141\142\143\144\145\146\147\148\149\150\151\152\153\154\155\156\]] .. [[157\158\159\160\161\162\163\164\165\166\167\168\169\170\171\172\173\]] .. [[174\175\176\177\178\179\180\181\182\183\184\185\186\187\188\189\190\]] .. [[191\192\193\194\195\196\197\198\199\200\201\202\203\204\205\206\207\]] .. [[208\209\210\211\212\213\214\215\216\217\218\219\220\221\222\223\224\]] .. [[225\226\227\228\229\230\231\232\233\234\235\236\237\238\239\240\241\]] .. [[242\243\244\245\246\247\248\249\250\251\252\253\254\255]] ) end) -------------------------------------------------------------------------------- test:tests_for "htmlspecialchars" test "htmlspecialchars-minimal" (function () -- Uses texts from PHP 5.3.0 htmlspecialchars tests local buf = {} -- We need special cat, not using make_concatter local cat = function(v) -- Matching var_dump for strings arguments("string", v) buf[#buf + 1] = 'string(' buf[#buf + 1] = #v buf[#buf + 1] = ') "' buf[#buf + 1] = v -- Unquoted buf[#buf + 1] = "\"\n" end -- normal string cat (htmlspecialchars("
Testing

New file.

")) -- long string cat (htmlspecialchars("
Testing

New file.


File WORKS!!!


End of file!!!

")) -- checking behavior of quote cat (htmlspecialchars("A 'quote' is bold")) local expected = [[ string(46) "<br>Testing<p>New file.</p> " string(187) "<br>Testing<p>New file.</p><p><br>File <b><i><u>WORKS!!!</i></u></b></p><br><p>End of file!!!</p>" string(46) "A 'quote' is <b>bold</b>" ]] ensure_strequals("escaped", table.concat(buf), expected) end) -------------------------------------------------------------------------------- test:tests_for "escape_lua_pattern" test "escape_lua_pattern-basic" (function () ensure_strequals( "escapinng lua pattern", escape_lua_pattern("abc^$()%.[]*+-?\0xyz"), "abc%^%$%(%)%%%.%[%]%*%+%-%?%zxyz" ) ensure_strequals( "no escapinng", escape_lua_pattern("just normal string 12345"), "just normal string 12345" ) end) test "escape_lua_pattern-find" (function () local cat, concat = make_concatter() for i = 0, 255 do cat(string.char(i)) end local input_string = concat() -- contains all possible symbols for i = 0, 255 do -- check all possible symbols local char = string.char(i) local i_start, i_end = input_string:find(escape_lua_pattern(char)) ensure("match for " .. char .. " is found", i_start) ensure_equals("match for " .. char .. " is one symbol", i_start, i_end) ensure_equals("position for " .. char .. " is correct", i_start, i + 1) end end) -------------------------------------------------------------------------------- test:tests_for 'cdata_wrap' 'cdata_cat' -------------------------------------------------------------------------------- test "cdata_wrap-cdata_cat" (function () local check = function(value, expected) do local actual = cdata_wrap(value) ensure_strequals("cdata_wrap", actual, expected) end do local cat, concat = make_concatter() cdata_cat(cat, value) ensure_strequals("cdata_cat", concat(), expected) end end check("", "") check("embedded\0zero", "") check("", "]]>") end) -------------------------------------------------------------------------------- test:test_for "fill_placeholders" (function () ensure_strequals("both empty", fill_placeholders("", {}), "") ensure_strequals("empty dict", fill_placeholders("test", {}), "test") ensure_strequals("empty str", fill_placeholders("", { a = 42 }), "") ensure_strequals("missing key", fill_placeholders("$(b)", { a = 42 }), "$(b)") ensure_strequals("bad format", fill_placeholders("$a", { a = 42 }), "$a") ensure_strequals("missing right brace", fill_placeholders("$a)", { a = 42 }), "$a)") ensure_strequals("missing left brace", fill_placeholders("$(a", { a = 42 }), "$(a") ensure_strequals("ok", fill_placeholders("a = `$(a)'", { a = 42 }), "a = `42'") ensure_tequals("no extra data", { fill_placeholders("a = `$(a)'", { a = 42 }) }, { "a = `42'" }) ensure_strequals("extra key", fill_placeholders("a = `$(a)'", { a = 42, b = 43 }), "a = `42'") ensure_strequals("two keys", fill_placeholders("`$(key)' = `$(value)'", { key = "a", value = 42 }), "`a' = `42'") ensure_strequals("empty string key", fill_placeholders("`$()'", { [""] = 42 }), "`42'") ensure_strequals("extra braces", fill_placeholders("$(a `$(a)')", { a = 42 }), "$(a `$(a)')") ensure_strequals("extra right brace", fill_placeholders("`$(a)')", { a = 42 }), "`42')") end) -------------------------------------------------------------------------------- test:test_for "url_encode" (function () ensure_strequals("empty", url_encode(""), "") ensure_strequals("simple", url_encode("test"), "test") ensure_strequals("test with number", url_encode("test555"), "test555") ensure_strequals("test with space", url_encode("test string"), "test+string") ensure_strequals( "symbols", url_encode("1234567890-=!@#$%^&*()_+"), "1234567890-%3D%21%40%23%24%25%5E%26%2A%28%29_%2B" ) end) -------------------------------------------------------------------------------- test:test_for "integer_to_string_with_base" (function() ensure_equals("simple", integer_to_string_with_base(10, 26), "A") ensure_equals("empty base", integer_to_string_with_base(10), "10") ensure_equals("test with negative numbers", integer_to_string_with_base(-11, 26), "-B") ensure_equals("test with zero and empty base", integer_to_string_with_base(0), "0") ensure_equals("test with zero and non-empty base", integer_to_string_with_base(0, 15), "0") -- NOTE: integer_to_string_with_base(-0) can produce '0' or '-0', depending on -- previous code. See: -- http://thread.gmane.org/gmane.comp.lang.lua.general/90837/focus=90838 -- http://article.gmane.org/gmane.comp.lang.lua.general/12950 ensure( "test with negative zero and empty base", integer_to_string_with_base(-0) == "0" or integer_to_string_with_base(-0) == "-0" ) local n = 136 local base = 36 local str = integer_to_string_with_base(n, base) ensure_equals("test with tonumber", tonumber(str, base), n) ensure_fails_with_substring("test with empty params", integer_to_string_with_base, "n must be a number") ensure_fails_with_substring( "test with string value of base", function() integer_to_string_with_base(10, "asd") end, "base must be a number" ) ensure_fails_with_substring( "test with negative base", function() integer_to_string_with_base(10, -10) end, "base out of range" ) ensure_fails_with_substring( "test on nan", function() integer_to_string_with_base(0/0) end, "n is nan" ) ensure_fails_with_substring( "test on +inf", function() integer_to_string_with_base(1/0) end, "n is inf" ) ensure_fails_with_substring( "test on -inf", function() integer_to_string_with_base(-1/0) end, "n is inf" ) ensure_fails_with_substring( "test on -inf", function() integer_to_string_with_base(-1/0) end, "n is inf" ) end) test:test_for "cut_with_ellipsis" (function() local test_string = "test long string" ensure_equals( "test with string with correct max length", cut_with_ellipsis(test_string, #test_string), test_string ) ensure_equals( "test with string length - 1", cut_with_ellipsis(test_string, #test_string - 1), "test long st..." ) ensure_equals( "test with string length - 2", cut_with_ellipsis(test_string, #test_string - 2), "test long s..." ) ensure_equals( "test with string length - 3", cut_with_ellipsis(test_string, #test_string - 3), "test long ..." ) ensure_equals( "test with string with excess max length", cut_with_ellipsis(test_string, #test_string + 50), test_string ) ensure_equals( "test with string with default max length", cut_with_ellipsis(test_string), test_string ) ensure_equals( "test with cutting long string", cut_with_ellipsis(test_string, 12), "test long..." ) ensure_equals( "test with max length = 1", cut_with_ellipsis(test_string, 1), "t" ) ensure_equals( "test with max length = 2", cut_with_ellipsis(test_string, 2), "te" ) ensure_equals( "test with max length = 3", cut_with_ellipsis(test_string, 3), "tes" ) ensure_equals( "test with max length = 4", cut_with_ellipsis(test_string, 4), "t..." ) ensure_equals( "test with empty string", cut_with_ellipsis(""), "" ) ensure_fails_with_substring( "test with non-positive required string length", function() cut_with_ellipsis(test_string, 0) end, "required string length must be positive" ) end) -------------------------------------------------------------------------------- test:test_for "number_to_string" (function () ensure_strequals("inf", number_to_string(1/0), "1/0") ensure_strequals("-inf", number_to_string(-1/0), "-1/0") ensure_strequals("nan", number_to_string(0/0), "0/0") end) test:test_for "serialize_number" (function () ensure_strequals("inf", serialize_number( 1/0), "1/0") ensure_strequals("-inf", serialize_number(-1/0), "-1/0") ensure_strequals("nan", serialize_number(0/0), "0/0") ensure_strequals("123", serialize_number(123), "123") local pi_15 = loadstring("return " .. ("%.15g"):format(math_pi))() local pi_16 = loadstring("return " .. ("%.16g"):format(math_pi))() local pi_17 = loadstring("return " .. serialize_number(math_pi))() local pi_18 = loadstring("return " .. ("%.18g"):format(math_pi))() local pi_55 = loadstring("return " .. ("%.55g"):format(math_pi))() ensure( "serialize pi by %.15g", pi_15 ~= math.pi ) ensure( "serialize pi by %.16g", pi_16 == math.pi ) ensure( "serialize pi by %.17g", pi_17 == math.pi ) ensure( "serialize pi by %.18g", pi_18 == math.pi ) ensure( "serialize pi by %.55g", pi_55 == math.pi ) local one_third_15 = loadstring("return " .. ("%.15g"):format(1/3))() local one_third_16 = loadstring("return " .. ("%.16g"):format(1/3))() local one_third_17 = loadstring("return " .. serialize_number(1/3))() local one_third_18 = loadstring("return " .. ("%.18g"):format(1/3))() local one_third_55 = loadstring("return " .. ("%.55g"):format(1/3))() ensure( "serialize 1/3 by %.15g", one_third_15 ~= 1/3 ) ensure( "serialize 1/3 by %.16g", one_third_16 == 1/3 ) ensure( "serialize 1/3 by %.17g", one_third_17 == 1/3 ) ensure( "serialize 1/3 by %.18g", one_third_18 == 1/3 ) ensure( "serialize 1/3 by %.55g", one_third_55 == 1/3 ) end) -------------------------------------------------------------------------------- test:UNTESTED 'fill_placeholders_ex' test:UNTESTED 'fill_curly_placeholders' test:UNTESTED 'split_by_char' test:UNTESTED 'split_by_offset' test:UNTESTED 'count_substrings' test:UNTESTED 'kv_concat' test:UNTESTED 'escape_for_json' test:UNTESTED 'get_escaped_chars_in_ranges'