diff --git a/src/main/java/org/squiddev/cobalt/lib/FormatDesc.java b/src/main/java/org/squiddev/cobalt/lib/FormatDesc.java index 74cf955d..582a939b 100644 --- a/src/main/java/org/squiddev/cobalt/lib/FormatDesc.java +++ b/src/main/java/org/squiddev/cobalt/lib/FormatDesc.java @@ -50,7 +50,7 @@ public class FormatDesc { private final int flags; private final int width; - final int precision; + private final int precision; final int conversion; @@ -159,7 +159,10 @@ void checkFlags(int flags) throws LuaError { } void format(Buffer buf, byte c) { + int nSpaces = width > 1 ? width - 1 : 0; + if (!leftAdjust()) pad(buf, ' ', nSpaces); buf.append(c); + if (leftAdjust()) pad(buf, ' ', nSpaces); } public void format(Buffer buf, long number) { diff --git a/src/main/java/org/squiddev/cobalt/lib/StringFormat.java b/src/main/java/org/squiddev/cobalt/lib/StringFormat.java index 48aa546e..42f49ba3 100644 --- a/src/main/java/org/squiddev/cobalt/lib/StringFormat.java +++ b/src/main/java/org/squiddev/cobalt/lib/StringFormat.java @@ -96,7 +96,7 @@ static LuaString format(LuaState state, FormatState format) throws LuaError, Unw desc.format(result, value.checkDouble()); } case 'q' -> { - desc.checkFlags(0); + if (desc.length != 1) throw new LuaError("specifier '%q' cannot have modifiers"); addQuoted(result, format.arg, value); } case 's' -> { @@ -139,7 +139,7 @@ private static void addQuoted(Buffer buf, int arg, LuaValue s) throws LuaError { } } case TBOOLEAN, TNIL -> buf.append(s.toString()); - default -> throw ErrorFactory.argError(arg, "value has no literal representation"); + default -> throw ErrorFactory.argError(arg, "value has no literal form"); } } diff --git a/src/test/java/org/squiddev/cobalt/AssertTests.java b/src/test/java/org/squiddev/cobalt/AssertTests.java index 0bf11d6c..b19b3304 100644 --- a/src/test/java/org/squiddev/cobalt/AssertTests.java +++ b/src/test/java/org/squiddev/cobalt/AssertTests.java @@ -52,7 +52,6 @@ public void tables(String name) throws IOException, CompileException, LuaError, @ParameterizedTest(name = ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER) @ValueSource(strings = { "debug", - "debug-coroutine-hook", "debug-getinfo", "debug-upvalue", "gc", @@ -60,12 +59,8 @@ public void tables(String name) throws IOException, CompileException, LuaError, "invalid-tailcall", "lex-context", "lex-number", - "load-error", - "no-unwind", "setlist", - "string-compare", "string-issues", - "string-format", "time", "traceback", }) @@ -95,7 +90,6 @@ public void main(String name) throws IOException, CompileException, LuaError, In "nextvar", "pm", "sort", - "strings", "vararg", "verybig", }) @@ -113,19 +107,6 @@ public void lua51(String name) throws Exception { helpers.runWithDump(name); } - @ParameterizedTest(name = ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER) - @ValueSource(strings = { - "bitwise", - "strings", - }) - public void lua52(String name) throws Exception { - ScriptHelper helpers = new ScriptHelper("/assert/lua5.2/"); - helpers.setup(x -> x.bytecodeFormat(LuaBytecodeFormat.instance())); - Bit32Lib.add(helpers.state, helpers.globals); - - helpers.runWithDump(name); - } - @ParameterizedTest(name = ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER) @ValueSource(strings = { "db", diff --git a/src/test/java/org/squiddev/cobalt/LuaSpecTest.java b/src/test/java/org/squiddev/cobalt/LuaSpecTest.java index 0529540f..ae550281 100644 --- a/src/test/java/org/squiddev/cobalt/LuaSpecTest.java +++ b/src/test/java/org/squiddev/cobalt/LuaSpecTest.java @@ -89,11 +89,13 @@ public LuaSpecTest() throws IOException, LuaError, CompileException { env.rawset("fail", RegisteredFunction.of("fail", (state, arg) -> { var frame = state.getCurrentThread().getDebugState().getFrame(2); - var values = frame.stack; - for (int i = 0; i < values.length; i++) { - var value = values[i]; - var local = frame.closure.getPrototype().getLocalName(i + 1, frame.pc); - if (!value.isNil() || local != null) System.out.printf("% 2d => %s [%s]\n", i, values[i], local); + if (frame != null && frame.stack != null) { + var values = frame.stack; + for (int i = 0; i < values.length; i++) { + var value = values[i]; + var local = frame.closure.getPrototype().getLocalName(i + 1, frame.pc); + if (!value.isNil() || local != null) System.out.printf("% 2d => %s [%s]\n", i, values[i], local); + } } throw new AssertionError(arg.checkString() + "\n" + DebugHelpers.traceback(state.getCurrentThread(), 0)); }).create()); @@ -127,7 +129,7 @@ private static DynamicNode makePending(DynamicNode node) { public Stream getTests() throws IOException { return Files.walk(ROOT) .filter(x -> x.getFileName().toString().endsWith("_spec.lua")) - .map(path -> { + .flatMap(path -> { LuaFunction function; try (InputStream stream = Files.newInputStream(path)) { function = LoadState.load(state, stream, "@" + path.getFileName(), env); @@ -144,7 +146,7 @@ public Stream getTests() throws IOException { this.nodes = null; } - return DynamicContainer.dynamicContainer(path.getFileName().toString(), nodes); + return nodes.stream(); }); } } diff --git a/src/test/resources/assert/debug-coroutine-hook.lua b/src/test/resources/assert/debug-coroutine-hook.lua deleted file mode 100644 index 7bd90cef..00000000 --- a/src/test/resources/assert/debug-coroutine-hook.lua +++ /dev/null @@ -1,16 +0,0 @@ ---- Tests that debug hooks are not propagated to child coroutines --- While the hook themselves are propagated, the registry HOOKKEY isn't. Consequently --- only native hooks are propagated in practice. -local function testHook(a) end -debug.sethook(testHook, "c") - -local c = coroutine.create(function() - return debug.gethook() -end) - -local ok, hook = coroutine.resume(c) - -debug.sethook() - -assert(debug.gethook() == nil) -assert(ok and hook == nil) diff --git a/src/test/resources/assert/load-error.lua b/src/test/resources/assert/load-error.lua deleted file mode 100644 index 55f1b4a7..00000000 --- a/src/test/resources/assert/load-error.lua +++ /dev/null @@ -1,9 +0,0 @@ --- Ensures load returns the original error value - -local tbl = {} -local fun, err = load(function() - error(tbl) -end) - -assert(fun == nil) -assert(err == tbl) diff --git a/src/test/resources/assert/lua5.1/strings.lua b/src/test/resources/assert/lua5.1/strings.lua deleted file mode 100644 index 237dbad3..00000000 --- a/src/test/resources/assert/lua5.1/strings.lua +++ /dev/null @@ -1,176 +0,0 @@ -print('testing strings and string library') - -assert('alo' < 'alo1') -assert('' < 'a') -assert('alo\0alo' < 'alo\0b') -assert('alo\0alo\0\0' > 'alo\0alo\0') -assert('alo' < 'alo\0') -assert('alo\0' > 'alo') -assert('\0' < '\1') -assert('\0\0' < '\0\1') -assert('\1\0a\0a' <= '\1\0a\0a') -assert(not ('\1\0a\0b' <= '\1\0a\0a')) -assert('\0\0\0' < '\0\0\0\0') -assert(not('\0\0\0\0' < '\0\0\0')) -assert('\0\0\0' <= '\0\0\0\0') -assert(not('\0\0\0\0' <= '\0\0\0')) -assert('\0\0\0' <= '\0\0\0') -assert('\0\0\0' >= '\0\0\0') -assert(not ('\0\0b' < '\0\0a\0')) -print('+') - -assert(string.sub("123456789",2,4) == "234") -assert(string.sub("123456789",7) == "789") -assert(string.sub("123456789",7,6) == "") -assert(string.sub("123456789",7,7) == "7") -assert(string.sub("123456789",0,0) == "") -assert(string.sub("123456789",-10,10) == "123456789") -assert(string.sub("123456789",1,9) == "123456789") -assert(string.sub("123456789",-10,-20) == "") -assert(string.sub("123456789",-1) == "9") -assert(string.sub("123456789",-4) == "6789") -assert(string.sub("123456789",-6, -4) == "456") -assert(string.sub("\000123456789",3,5) == "234") -assert(("\000123456789"):sub(8) == "789") -print('+') - -assert(string.find("123456789", "345") == 3) -a,b = string.find("123456789", "345") -assert(string.sub("123456789", a, b) == "345") -assert(string.find("1234567890123456789", "345", 3) == 3) -assert(string.find("1234567890123456789", "345", 4) == 13) -assert(string.find("1234567890123456789", "346", 4) == nil) -assert(string.find("1234567890123456789", ".45", -9) == 13) -assert(string.find("abcdefg", "\0", 5, 1) == nil) -assert(string.find("", "") == 1) -assert(string.find('', 'aaa', 1) == nil) -assert(('alo(.)alo'):find('(.)', 1, 1) == 4) -print('+') - -assert(string.len("") == 0) -assert(string.len("\0\0\0") == 3) -assert(string.len("1234567890") == 10) - -assert(#"" == 0) -assert(#"\0\0\0" == 3) -assert(#"1234567890" == 10) - -assert(string.byte("a") == 97) -assert(string.byte("á") > 127) -assert(string.byte(string.char(255)) == 255) -assert(string.byte(string.char(0)) == 0) -assert(string.byte("\0") == 0) -assert(string.byte("\0\0alo\0x", -1) == string.byte('x')) -assert(string.byte("ba", 2) == 97) -assert(string.byte("\n\n", 2, -1) == 10) -assert(string.byte("\n\n", 2, 2) == 10) -assert(string.byte("") == nil) -assert(string.byte("hi", -3) == nil) -assert(string.byte("hi", 3) == nil) -assert(string.byte("hi", 9, 10) == nil) -assert(string.byte("hi", 2, 1) == nil) -assert(string.char() == "") -assert(string.char(0, 255, 0) == "\0\255\0") -assert(string.char(0, string.byte("á"), 0) == "\0á\0") -assert(string.char(string.byte("ál\0óu", 1, -1)) == "ál\0óu") -assert(string.char(string.byte("ál\0óu", 1, 0)) == "") -assert(string.char(string.byte("ál\0óu", -10, 100)) == "ál\0óu") -print('+') - -assert(string.upper("ab\0c") == "AB\0C") -assert(string.lower("\0ABCc%$") == "\0abcc%$") -assert(string.rep('teste', 0) == '') -assert(string.rep('tés\00tê', 2) == 'tés\0têtés\000tê') -assert(string.rep('', 10) == '') - -assert(string.reverse"" == "") -assert(string.reverse"\0\1\2\3" == "\3\2\1\0") -assert(string.reverse"\0001234" == "4321\0") - -for i=0,30 do assert(string.len(string.rep('a', i)) == i) end - -assert(type(tostring(nil)) == 'string') -assert(type(tostring(12)) == 'string') -assert(''..12 == '12' and type(12 .. '') == 'string') -assert(string.find(tostring{}, 'table:')) -assert(string.find(tostring(print), 'function:')) -assert(tostring(1234567890123) == '1234567890123') -assert(#tostring('\0') == 1) -assert(tostring(true) == "true") -assert(tostring(false) == "false") -print('+') - -x = '"ílo"\n\\' -assert(string.format('%q%s', x, x) == '"\\"ílo\\"\\\n\\\\""ílo"\n\\') -assert(string.format('%q', "\0") == [["\000"]]) -assert(string.format("\0%c\0%c%x\0", string.byte("á"), string.byte("b"), 140) == - "\0á\0b8c\0") -assert(string.format('') == "") -assert(string.format("%c",34)..string.format("%c",48)..string.format("%c",90)..string.format("%c",100) == - string.format("%c%c%c%c", 34, 48, 90, 100)) -assert(string.format("%s\0 is not \0%s", 'not be', 'be') == 'not be\0 is not \0be') -assert(string.format("%%%d %010d", 10, 23) == "%10 0000000023") -assert(tonumber(string.format("%f", 10.3)) == 10.3) -x = string.format('"%-50s"', 'a') -assert(#x == 52) -assert(string.sub(x, 1, 4) == '"a ') - -assert(string.format("-%.20s.20s", string.rep("%", 2000)) == "-"..string.rep("%", 20)..".20s") -assert(string.format('"-%20s.20s"', string.rep("%", 2000)) == - string.format("%q", "-"..string.rep("%", 2000)..".20s")) - - --- longest number that can be formated -assert(string.len(string.format('%99.99f', -1e308)) >= 100) - -assert(loadstring("return 1\n--comentário sem EOL no final")() == 1) - - -assert(table.concat{} == "") -assert(table.concat({}, 'x') == "") -assert(table.concat({'\0', '\0\1', '\0\1\2'}, '.\0.') == "\0.\0.\0\1.\0.\0\1\2") -local a = {}; for i=1,3000 do a[i] = "xuxu" end -assert(table.concat(a, "123").."123" == string.rep("xuxu123", 3000)) -assert(table.concat(a, "b", 20, 20) == "xuxu") -assert(table.concat(a, "", 20, 21) == "xuxuxuxu") -assert(table.concat(a, "", 22, 21) == "") -assert(table.concat(a, "3", 2999) == "xuxu3xuxu") - -a = {"a","b","c"} -assert(table.concat(a, ",", 1, 0) == "") -assert(table.concat(a, ",", 1, 1) == "a") -assert(table.concat(a, ",", 1, 2) == "a,b") -assert(table.concat(a, ",", 2) == "b,c") -assert(table.concat(a, ",", 3) == "c") -assert(table.concat(a, ",", 4) == "") - -local locales = { "ptb", "ISO-8859-1", "pt_BR" } -local function trylocale (w) - for _, l in ipairs(locales) do - if os.setlocale(l, w) then return true end - end - return false -end - -if not trylocale("collate") then - print("locale not supported") -else - assert("alo" < "álo" and "álo" < "amo") -end - -if not trylocale("ctype") then - print("locale not supported") -else - assert(string.gsub("áéíóú", "%a", "x") == "xxxxx") - assert(string.gsub("áÁéÉ", "%l", "x") == "xÁxÉ") - assert(string.gsub("áÁéÉ", "%u", "x") == "áxéx") - assert(string.upper"áÁé{xuxu}ção" == "ÁÁÉ{XUXU}ÇÃO") -end - -os.setlocale("C") -assert(os.setlocale() == 'C') -assert(os.setlocale(nil, "numeric") == 'C') - -print('OK') - - diff --git a/src/test/resources/assert/lua5.2/bitwise.lua b/src/test/resources/assert/lua5.2/bitwise.lua deleted file mode 100644 index afa158dd..00000000 --- a/src/test/resources/assert/lua5.2/bitwise.lua +++ /dev/null @@ -1,115 +0,0 @@ -print("testing bitwise operations") - -assert(bit32.band() == bit32.bnot(0)) -assert(bit32.btest() == true) -assert(bit32.bor() == 0) -assert(bit32.bxor() == 0) - -assert(bit32.band() == bit32.band(0xffffffff)) -assert(bit32.band(1,2) == 0) - - --- out-of-range numbers -assert(bit32.band(-1) == 0xffffffff) -assert(bit32.band(2^33 - 1) == 0xffffffff) -assert(bit32.band(-2^33 - 1) == 0xffffffff) -assert(bit32.band(2^33 + 1) == 1) -assert(bit32.band(-2^33 + 1) == 1) -assert(bit32.band(-2^40) == 0) -assert(bit32.band(2^40) == 0) -assert(bit32.band(-2^40 - 2) == 0xfffffffe) -assert(bit32.band(2^40 - 4) == 0xfffffffc) - -assert(bit32.lrotate(0, -1) == 0) -assert(bit32.lrotate(0, 7) == 0) -assert(bit32.lrotate(0x12345678, 4) == 0x23456781) -assert(bit32.rrotate(0x12345678, -4) == 0x23456781) -assert(bit32.lrotate(0x12345678, -8) == 0x78123456) -assert(bit32.rrotate(0x12345678, 8) == 0x78123456) -assert(bit32.lrotate(0xaaaaaaaa, 2) == 0xaaaaaaaa) -assert(bit32.lrotate(0xaaaaaaaa, -2) == 0xaaaaaaaa) -for i = -50, 50 do - assert(bit32.lrotate(0x89abcdef, i) == bit32.lrotate(0x89abcdef, i%32)) -end - -assert(bit32.lshift(0x12345678, 4) == 0x23456780) -assert(bit32.lshift(0x12345678, 8) == 0x34567800) -assert(bit32.lshift(0x12345678, -4) == 0x01234567) -assert(bit32.lshift(0x12345678, -8) == 0x00123456) -assert(bit32.lshift(0x12345678, 32) == 0) -assert(bit32.lshift(0x12345678, -32) == 0) -assert(bit32.rshift(0x12345678, 4) == 0x01234567) -assert(bit32.rshift(0x12345678, 8) == 0x00123456) -assert(bit32.rshift(0x12345678, 32) == 0) -assert(bit32.rshift(0x12345678, -32) == 0) -assert(bit32.arshift(0x12345678, 0) == 0x12345678) -assert(bit32.arshift(0x12345678, 1) == 0x12345678 / 2) -assert(bit32.arshift(0x12345678, -1) == 0x12345678 * 2) -assert(bit32.arshift(-1, 1) == 0xffffffff) -assert(bit32.arshift(-1, 24) == 0xffffffff) -assert(bit32.arshift(-1, 32) == 0xffffffff) -assert(bit32.arshift(-1, -1) == (-1 * 2) % 2^32) - -print("+") --- some special cases -local c = {0, 1, 2, 3, 10, 0x80000000, 0xaaaaaaaa, 0x55555555, - 0xffffffff, 0x7fffffff} - -for _, b in pairs(c) do - assert(bit32.band(b) == b) - assert(bit32.band(b, b) == b) - assert(bit32.btest(b, b) == (b ~= 0)) - assert(bit32.band(b, b, b) == b) - assert(bit32.btest(b, b, b) == (b ~= 0)) - assert(bit32.band(b, bit32.bnot(b)) == 0) - assert(bit32.bor(b, bit32.bnot(b)) == bit32.bnot(0)) - assert(bit32.bor(b) == b) - assert(bit32.bor(b, b) == b) - assert(bit32.bor(b, b, b) == b) - assert(bit32.bxor(b) == b) - assert(bit32.bxor(b, b) == 0) - assert(bit32.bxor(b, 0) == b) - assert(bit32.bnot(b) ~= b) - assert(bit32.bnot(bit32.bnot(b)) == b) - assert(bit32.bnot(b) == 2^32 - 1 - b) - assert(bit32.lrotate(b, 32) == b) - assert(bit32.rrotate(b, 32) == b) - assert(bit32.lshift(bit32.lshift(b, -4), 4) == bit32.band(b, bit32.bnot(0xf))) - assert(bit32.rshift(bit32.rshift(b, 4), -4) == bit32.band(b, bit32.bnot(0xf))) - for i = -40, 40 do - assert(bit32.lshift(b, i) == math.floor((b * 2^i) % 2^32)) - end -end - -assert(not pcall(bit32.band, {})) -assert(not pcall(bit32.bnot, "a")) -assert(not pcall(bit32.lshift, 45)) -assert(not pcall(bit32.lshift, 45, print)) -assert(not pcall(bit32.rshift, 45, print)) - -print("+") - - --- testing extract/replace - -assert(bit32.extract(0x12345678, 0, 4) == 8) -assert(bit32.extract(0x12345678, 4, 4) == 7) -assert(bit32.extract(0xa0001111, 28, 4) == 0xa) -assert(bit32.extract(0xa0001111, 31, 1) == 1) -assert(bit32.extract(0x50000111, 31, 1) == 0) -assert(bit32.extract(0xf2345679, 0, 32) == 0xf2345679) - -assert(not pcall(bit32.extract, 0, -1)) -assert(not pcall(bit32.extract, 0, 32)) -assert(not pcall(bit32.extract, 0, 0, 33)) -assert(not pcall(bit32.extract, 0, 31, 2)) - -assert(bit32.replace(0x12345678, 5, 28, 4) == 0x52345678) -assert(bit32.replace(0x12345678, 0x87654321, 0, 32) == 0x87654321) -assert(bit32.replace(0, 1, 2) == 2^2) -assert(bit32.replace(0, -1, 4) == 2^4) -assert(bit32.replace(-1, 0, 31) == 2^31 - 1) -assert(bit32.replace(-1, 0, 1, 2) == 2^32 - 7) - - -print'OK' diff --git a/src/test/resources/assert/lua5.2/strings.lua b/src/test/resources/assert/lua5.2/strings.lua deleted file mode 100644 index 9a529c07..00000000 --- a/src/test/resources/assert/lua5.2/strings.lua +++ /dev/null @@ -1,283 +0,0 @@ -_noformatA = true -- FIXME: Float formatting is hard! - -print('testing strings and string library') - -assert('alo' < 'alo1') -assert('' < 'a') -assert('alo\0alo' < 'alo\0b') -assert('alo\0alo\0\0' > 'alo\0alo\0') -assert('alo' < 'alo\0') -assert('alo\0' > 'alo') -assert('\0' < '\1') -assert('\0\0' < '\0\1') -assert('\1\0a\0a' <= '\1\0a\0a') -assert(not ('\1\0a\0b' <= '\1\0a\0a')) -assert('\0\0\0' < '\0\0\0\0') -assert(not('\0\0\0\0' < '\0\0\0')) -assert('\0\0\0' <= '\0\0\0\0') -assert(not('\0\0\0\0' <= '\0\0\0')) -assert('\0\0\0' <= '\0\0\0') -assert('\0\0\0' >= '\0\0\0') -assert(not ('\0\0b' < '\0\0a\0')) -print('+') - -assert(string.sub("123456789",2,4) == "234") -assert(string.sub("123456789",7) == "789") -assert(string.sub("123456789",7,6) == "") -assert(string.sub("123456789",7,7) == "7") -assert(string.sub("123456789",0,0) == "") -assert(string.sub("123456789",-10,10) == "123456789") -assert(string.sub("123456789",1,9) == "123456789") -assert(string.sub("123456789",-10,-20) == "") -assert(string.sub("123456789",-1) == "9") -assert(string.sub("123456789",-4) == "6789") -assert(string.sub("123456789",-6, -4) == "456") -if not _no32 then - assert(string.sub("123456789",-2^31, -4) == "123456") - assert(string.sub("123456789",-2^31, 2^31 - 1) == "123456789") - assert(string.sub("123456789",-2^31, -2^31) == "") -end -assert(string.sub("\000123456789",3,5) == "234") -assert(("\000123456789"):sub(8) == "789") -print('+') - -assert(string.find("123456789", "345") == 3) -a,b = string.find("123456789", "345") -assert(string.sub("123456789", a, b) == "345") -assert(string.find("1234567890123456789", "345", 3) == 3) -assert(string.find("1234567890123456789", "345", 4) == 13) -assert(string.find("1234567890123456789", "346", 4) == nil) -assert(string.find("1234567890123456789", ".45", -9) == 13) -assert(string.find("abcdefg", "\0", 5, 1) == nil) -assert(string.find("", "") == 1) -assert(string.find("", "", 1) == 1) --- assert(not string.find("", "", 2)) TODO: Here for Lua 5.1 compatibility. Worth keeping? -assert(string.find('', 'aaa', 1) == nil) -assert(('alo(.)alo'):find('(.)', 1, 1) == 4) -print('+') - -assert(string.len("") == 0) -assert(string.len("\0\0\0") == 3) -assert(string.len("1234567890") == 10) - -assert(#"" == 0) -assert(#"\0\0\0" == 3) -assert(#"1234567890" == 10) - -assert(string.byte("a") == 97) -assert(string.byte("\xe4") > 127) -assert(string.byte(string.char(255)) == 255) -assert(string.byte(string.char(0)) == 0) -assert(string.byte("\0") == 0) -assert(string.byte("\0\0alo\0x", -1) == string.byte('x')) -assert(string.byte("ba", 2) == 97) -assert(string.byte("\n\n", 2, -1) == 10) -assert(string.byte("\n\n", 2, 2) == 10) -assert(string.byte("") == nil) -assert(string.byte("hi", -3) == nil) -assert(string.byte("hi", 3) == nil) -assert(string.byte("hi", 9, 10) == nil) -assert(string.byte("hi", 2, 1) == nil) -assert(string.char() == "") -assert(string.char(0, 255, 0) == "\0\255\0") -assert(string.char(0, string.byte("\xe4"), 0) == "\0\xe4\0") -assert(string.char(string.byte("\xe4l\0�u", 1, -1)) == "\xe4l\0�u") -assert(string.char(string.byte("\xe4l\0�u", 1, 0)) == "") -assert(string.char(string.byte("\xe4l\0�u", -10, 100)) == "\xe4l\0�u") -print('+') - -assert(string.upper("ab\0c") == "AB\0C") -assert(string.lower("\0ABCc%$") == "\0abcc%$") -assert(string.rep('teste', 0) == '') -assert(string.rep('t�s\00t�', 2) == 't�s\0t�t�s\000t�') -assert(string.rep('', 10) == '') - --- repetitions with separator -assert(string.rep('teste', 0, 'xuxu') == '') -assert(string.rep('teste', 1, 'xuxu') == 'teste') -assert(string.rep('\1\0\1', 2, '\0\0') == '\1\0\1\0\0\1\0\1') -assert(string.rep('', 10, '.') == string.rep('.', 9)) -if not _no32 then - assert(not pcall(string.rep, "aa", 2^30)) - assert(not pcall(string.rep, "", 2^30, "aa")) -end - -assert(string.reverse"" == "") -assert(string.reverse"\0\1\2\3" == "\3\2\1\0") -assert(string.reverse"\0001234" == "4321\0") - -for i=0,30 do assert(string.len(string.rep('a', i)) == i) end - -assert(type(tostring(nil)) == 'string') -assert(type(tostring(12)) == 'string') -assert(''..12 == '12' and type(12 .. '') == 'string') -assert(string.find(tostring{}, 'table:')) -assert(string.find(tostring(print), 'function:')) -assert(tostring(1234567890123) == '1234567890123') -assert(#tostring('\0') == 1) -assert(tostring(true) == "true") -assert(tostring(false) == "false") -print('+') - -x = '"�lo"\n\\' -assert(string.format('%q%s', x, x) == '"\\"�lo\\"\\\n\\\\""�lo"\n\\') --- FIXME: assert(string.format('%q', "\0") == [["\0"]]) -assert(load(string.format('return %q', x))() == x) -assert(load(string.format('return %q', x))() == x) -assert(string.format("\0%c\0%c%x\0", string.byte("\xe4"), string.byte("b"), 140) == - "\0\xe4\0b8c\0") -assert(string.format('') == "") -assert(string.format("%c",34)..string.format("%c",48)..string.format("%c",90)..string.format("%c",100) == - string.format("%c%c%c%c", 34, 48, 90, 100)) -assert(string.format("%s\0 is not \0%s", 'not be', 'be') == 'not be\0 is not \0be') -assert(string.format("%%%d %010d", 10, 23) == "%10 0000000023") -assert(tonumber(string.format("%f", 10.3)) == 10.3) -x = string.format('"%-50s"', 'a') -assert(#x == 52) -assert(string.sub(x, 1, 4) == '"a ') - -assert(string.format("-%.20s.20s", string.rep("%", 2000)) == - "-"..string.rep("%", 20)..".20s") -assert(string.format('"-%20s.20s"', string.rep("%", 2000)) == - string.format("%q", "-"..string.rep("%", 2000)..".20s")) - --- format x tostring -assert(string.format("%s %s", nil, true) == "nil true") -assert(string.format("%s %.4s", false, true) == "false true") -assert(string.format("%.3s %.3s", false, true) == "fal tru") -local m = setmetatable({}, {__tostring = function () return "hello" end}) -assert(string.format("%s %.10s", m, m) == "hello hello") - - -assert(string.format("%x", 0.3) == "0") -assert(string.format("%02x", 0.1) == "00") -assert(string.format("%08X", 2^32 - 1) == "FFFFFFFF") -assert(string.format("%+08d", 2^31 - 1) == "+2147483647") -assert(string.format("%+08d", -2^31) == "-2147483648") - - --- longest number that can be formated -assert(string.len(string.format('%99.99f', -1e308)) >= 100) - - - -if not _nolonglong then - print("testing large numbers for format") - assert(string.format("%8x", 2^52 - 1) == "fffffffffffff") - assert(string.format("%d", -1) == "-1") - assert(tonumber(string.format("%u", 2^62)) == 2^62) - assert(string.format("%8x", 0xffffffff) == "ffffffff") - assert(string.format("%8x", 0x7fffffff) == "7fffffff") - assert(string.format("%d", 2^53) == "9007199254740992") - assert(string.format("%d", -2^53) == "-9007199254740992") - assert(string.format("0x%8X", 0x8f000003) == "0x8F000003") - -- maximum integer that fits both in 64-int and (exact) double - local x = 2^64 - 2^(64-53) - -- FIXME: assert(x == 0xfffffffffffff800) - -- FIXME: assert(tonumber(string.format("%u", x)) == x) - -- FIXME: assert(tonumber(string.format("0X%x", x)) == x) - -- FIXME: assert(string.format("%x", x) == "fffffffffffff800") - assert(string.format("%d", x/2) == "9223372036854774784") - assert(string.format("%d", -x/2) == "-9223372036854774784") - assert(string.format("%d", -2^63) == "-9223372036854775808") - -- FIXME: assert(string.format("%x", 2^63) == "8000000000000000") -end - -if not _noformatA then - print("testing 'format %a %A'") - assert(string.format("%.2a", 0.5) == "0x1.00p-1") - assert(string.format("%A", 0x1fffffffffffff) == "0X1.FFFFFFFFFFFFFP+52") - assert(string.format("%.4a", -3) == "-0x1.8000p+1") - assert(tonumber(string.format("%a", -0.1)) == -0.1) -end - --- errors in format - -local function check (fmt, msg) - local s, err = pcall(string.format, fmt, 10) - assert(not s and string.find(err, msg)) -end - -local aux = string.rep('0', 600) -check("%100.3d", "too long") -check("%1"..aux..".3d", "too long") -check("%1.100d", "too long") -check("%10.1"..aux.."004d", "too long") -check("%t", "invalid conversion") -check("%"..aux.."d", "repeated flags") -check("%d %d", "got nil") -- FIX ME: PUC Lua reports "no value" - - --- integers out of range --- TODO: Works in Lua 5.1, so not sure what to do here! --- assert(not pcall(string.format, "%d", 2^63)) --- assert(not pcall(string.format, "%x", 2^64)) --- assert(not pcall(string.format, "%x", -2^64)) --- assert(not pcall(string.format, "%x", -1)) - - -assert(load("return 1\n--coment�rio sem EOL no final")() == 1) - - -assert(table.concat{} == "") -assert(table.concat({}, 'x') == "") -assert(table.concat({'\0', '\0\1', '\0\1\2'}, '.\0.') == "\0.\0.\0\1.\0.\0\1\2") -local a = {}; for i=1,3000 do a[i] = "xuxu" end -assert(table.concat(a, "123").."123" == string.rep("xuxu123", 3000)) -assert(table.concat(a, "b", 20, 20) == "xuxu") -assert(table.concat(a, "", 20, 21) == "xuxuxuxu") -assert(table.concat(a, "x", 22, 21) == "") -assert(table.concat(a, "3", 2999) == "xuxu3xuxu") -if not _no32 then - assert(table.concat({}, "x", 2^31-1, 2^31-2) == "") - assert(table.concat({}, "x", -2^31+1, -2^31) == "") - assert(table.concat({}, "x", 2^31-1, -2^31) == "") - -- FIXME: assert(table.concat({[2^31-1] = "alo"}, "x", 2^31-1, 2^31-1) == "alo") -end - -assert(not pcall(table.concat, {"a", "b", {}})) - -a = {"a","b","c"} -assert(table.concat(a, ",", 1, 0) == "") -assert(table.concat(a, ",", 1, 1) == "a") -assert(table.concat(a, ",", 1, 2) == "a,b") -assert(table.concat(a, ",", 2) == "b,c") -assert(table.concat(a, ",", 3) == "c") -assert(table.concat(a, ",", 4) == "") - -if not _port then - -local locales = { "ptb", "ISO-8859-1", "pt_BR" } -local function trylocale (w) - for i = 1, #locales do - if os.setlocale(locales[i], w) then return true end - end - return false -end - -if not trylocale("collate") then - print("locale not supported") -else - assert("alo" < "�lo" and "�lo" < "amo") -end - -if not trylocale("ctype") then - print("locale not supported") -else - assert(load("a = 3.4")); -- parser should not change outside locale - assert(not load("� = 3.4")); -- even with errors - assert(string.gsub("�����", "%a", "x") == "xxxxx") - assert(string.gsub("����", "%l", "x") == "x�x�") - assert(string.gsub("����", "%u", "x") == "�x�x") - assert(string.upper"���{xuxu}��o" == "���{XUXU}��O") -end - -os.setlocale("C") -assert(os.setlocale() == 'C') -assert(os.setlocale(nil, "numeric") == 'C') - -end - -print('OK') - - diff --git a/src/test/resources/assert/no-unwind.lua b/src/test/resources/assert/no-unwind.lua deleted file mode 100644 index 95eff4d0..00000000 --- a/src/test/resources/assert/no-unwind.lua +++ /dev/null @@ -1,10 +0,0 @@ --- load effectively acts as a pcall, but didn't pop the stack as expected -(function() - local ok, err = pcall(function() - load(function() - error("Oh dear") - end) - end) - assert(ok, err) -end)() - diff --git a/src/test/resources/assert/string-compare.lua b/src/test/resources/assert/string-compare.lua deleted file mode 100644 index f8f72648..00000000 --- a/src/test/resources/assert/string-compare.lua +++ /dev/null @@ -1,4 +0,0 @@ -assert(string.char(127) >= '\0') -assert(string.char(128) >= '\0') -assert(string.char(127) >= '\127') -assert(string.char(127) <= '\128') diff --git a/src/test/resources/assert/string-format.lua b/src/test/resources/assert/string-format.lua deleted file mode 100644 index 9a11a5d5..00000000 --- a/src/test/resources/assert/string-format.lua +++ /dev/null @@ -1,18 +0,0 @@ -local res = setmetatable({}, { __tostring = function() - return "Test __tostring" -end }) -assert(("%s"):format(res) == "Test __tostring") - -local c = coroutine.create(function() - local res = setmetatable({}, { - __tostring = function() - coroutine.yield() - return "Test __tostring yield" - end - }) - assert(("%s"):format(res) == "Test __tostring yield") -end) - -while coroutine.status(c) ~= "dead" do - assert(coroutine.resume(c)) -end diff --git a/src/test/resources/spec/_prelude.lua b/src/test/resources/spec/_prelude.lua index f1337b2b..a9fdc42e 100644 --- a/src/test/resources/spec/_prelude.lua +++ b/src/test/resources/spec/_prelude.lua @@ -11,7 +11,7 @@ local function serialise(value, seen, indent) seen[value] = true local items = {} - local len, contents_len = rawlen(value), 0 + local len, contents_len = (rawlen and rawlen(value) or #value), 0 for k, v in pairs(value) do local item if type(k) == "number" and (k % 1) == 0 and k >= 1 and k <= len then diff --git a/src/test/resources/spec/_run.lua b/src/test/resources/spec/_run.lua index bf117bfe..15073ea6 100644 --- a/src/test/resources/spec/_run.lua +++ b/src/test/resources/spec/_run.lua @@ -25,6 +25,8 @@ for line in handle:lines() do files[#files + 1] = line end handle:close() local function check_version(name) + if name:find(":cobalt") then return false end + local version_check = name:match(":lua([^ ]+)") if not version_check then return true end diff --git a/src/test/resources/spec/base_spec.lua b/src/test/resources/spec/base_spec.lua index 8759aa52..42ecbee7 100644 --- a/src/test/resources/spec/base_spec.lua +++ b/src/test/resources/spec/base_spec.lua @@ -27,11 +27,64 @@ describe("The base library", function() end) describe("tostring", function() + it("nil", function() expect(tostring(nil)):eq("nil") end) + + it("booleans", function() + expect(tostring(true)):eq("true") + expect(tostring(false)):eq("false") + end) + + describe("numbers", function() + it("basic conversions", function() + expect(tostring(12)):eq("12") + expect(""..12):eq('12') + expect(12 .. ""):eq('12') + + expect(tostring(1234567890123)):eq("1234567890123") + end) + + it("floats", function() + expect(tostring(-1203)):eq("-1203") + expect(tostring(1203.125)):eq("1203.125") + expect(tostring(-0.5)):eq("-0.5") + expect(tostring(-32767)):eq("-32767") + end) + + it("integers :lua>=5.3", function() + expect(tostring(4611686018427387904)):eq("4611686018427387904") + expect(tostring(-4611686018427387904)):eq("-4611686018427387904") + end) + + it("integer-compatible floats are preserved :lua>=5.3 :!cobalt", function() + expect('' .. 12):eq('12') expect(12.0 .. ''):eq('12.0') + expect(tostring(-1203 + 0.0)):eq("-1203.0") + end) + + it("integer-compatible floats are truncated :lua<=5.2", function() + expect(tostring(0.0)):eq("0") + expect('' .. 12):eq('12') expect(12.0 .. ''):eq('12') + expect(tostring(-1203 + 0.0)):eq("-1203") + end) + end) + + it("tables", function() expect(tostring {}):str_match('^table:') end) + + it("functions", function() expect(tostring(print)):str_match('^function:') end) + + it("null bytes", function() + expect(tostring('\0')):eq("\0") + end) + it("uses __name :lua>=5.3", function() - local obj = setmetatable({}, { __name="abc" }) + local obj = setmetatable({}, { __name = "abc" }) expect(tostring(obj)):str_match("^abc: ") end) + it("errors if __tostring does not return a string :lua>=5.3 :!cobalt", function() + local obj = setmetatable({}, { __tostring = function () return {} end }) + expect.error(tostring, obj):eq("'__tostring' must return a string") + end) + it("can return a non-string value :lua<=5.2", function() -- Lua 5.3+ requires this to be a string. Which is sensible, but a breaking change! local obj = setmetatable({}, { __tostring = function() return false end }) @@ -86,6 +139,14 @@ describe("The base library", function() end) describe("load", function() + it("returns the error value", function() + local tbl = {} + local fun, err = load(function() error(tbl) end) + + expect(fun):eq(nil) + expect(err):eq(tbl) + end) + -- I'd hope nobody relies on this behaviour, but you never know! it("propagates the current error handler", function() local res = { diff --git a/src/test/resources/spec/debug_spec.lua b/src/test/resources/spec/debug_spec.lua new file mode 100644 index 00000000..935335f1 --- /dev/null +++ b/src/test/resources/spec/debug_spec.lua @@ -0,0 +1,21 @@ +describe("The debug library", function() + describe("Debug hooks", function() + it("are not propagated to other coroutines", function() + -- Tests that debug hooks are not propagated to child coroutines + -- While the hook themselves are propagated, the registry HOOKKEY + -- isn't. Consequently only native hooks are propagated in practice. + local function hook(a) end + debug.sethook(hook, "c") + + local c = coroutine.create(function() return debug.gethook() end) + + local ok, hook = coroutine.resume(c) + + debug.sethook() + + expect(debug.gethook()):eq(nil) + expect(ok):eq(true) + expect(hook):eq(nil) + end) + end) +end) diff --git a/src/test/resources/spec/string_spec.lua b/src/test/resources/spec/string_spec.lua index 45e7ba2f..2b5f32d9 100644 --- a/src/test/resources/spec/string_spec.lua +++ b/src/test/resources/spec/string_spec.lua @@ -1,23 +1,85 @@ describe("The string library", function() - describe("string.format", function() - it("'q' option formats edge-case numbers correctly :lua>=5.4", function() - local formatted = string.format("%q %q %q", 0/0, 1/0, -1/0) - expect(formatted):eq("(0/0) 1e9999 -1e9999") - end) + it("strings can be compared", function() + assert("alo" < "alo1") + assert("" < "a") + assert("alo\0alo" < "alo\0b") + assert("alo\0alo\0\0" > "alo\0alo\0") + assert("alo" < "alo\0") + assert("alo\0" > "alo") + assert("\0" < "\1") + assert("\0\0" < "\0\1") + assert("\1\0a\0a" <= "\1\0a\0a") + assert(not ("\1\0a\0b" <= "\1\0a\0a")) + assert("\0\0\0" < "\0\0\0\0") + assert(not("\0\0\0\0" < "\0\0\0")) + assert("\0\0\0" <= "\0\0\0\0") + assert(not("\0\0\0\0" <= "\0\0\0")) + assert("\0\0\0" <= "\0\0\0") + assert("\0\0\0" >= "\0\0\0") + assert(not ("\0\0b" < "\0\0a\0")) - it("'q' option prints hexadecimal floats :lua>=5.4", function() - expect(("%q"):format(234.53)):eq("0x1.d50f5c28f5c29p+7") - end) + -- Make sure we're using unsigned comparison. + assert(string.char(127) >= '\0') + assert(string.char(128) >= '\0') + assert(string.char(127) < '\128') end) - describe("string.pack", function() - it("'z' modifier on exactly the buffer boundary :lua>=5.3", function() - local packed = string.pack("z", ("#"):rep(32)) - expect(packed):eq("################################\0") + describe("string.sub", function() + it("supports various ranges", function() + expect(string.sub("123456789",2,4)):eq("234") + expect(string.sub("123456789",7)):eq("789") + expect(string.sub("123456789",7,6)):eq("") + expect(string.sub("123456789",7,7)):eq("7") + expect(string.sub("123456789",0,0)):eq("") + expect(string.sub("123456789",-10,10)):eq("123456789") + expect(string.sub("123456789",1,9)):eq("123456789") + expect(string.sub("123456789",-10,-20)):eq("") + expect(string.sub("123456789",-1)):eq("9") + expect(string.sub("123456789",-4)):eq("6789") + expect(string.sub("123456789",-6, -4)):eq("456") + expect(string.sub("\000123456789",3,5)):eq("234") + expect(("\000123456789"):sub(8)):eq("789") + end) + + it("on large integers", function() + expect(string.sub("123456789",-2^31, -4)):eq("123456") + expect(string.sub("123456789",-2^31, 2^31 - 1)):eq("123456789") + expect(string.sub("123456789",-2^31, -2^31)):eq("") end) end) describe("string.find", function() + it("works on various ranges", function() + expect(string.find("123456789", "345")):eq(3) + local a,b = string.find("123456789", "345") + expect(string.sub("123456789", a, b)):eq("345") + expect(string.find("1234567890123456789", "345", 3)):eq(3) + expect(string.find("1234567890123456789", "345", 4)):eq(13) + expect(string.find("1234567890123456789", "346", 4)):eq(nil) + expect(string.find("1234567890123456789", ".45", -9)):eq(13) + expect(string.find("abcdefg", "\0", 5, 1)):eq(nil) + end) + + it("works on empty strings", function() + expect(string.find("", "")):eq(1) + expect(string.find("", "", 1)):eq(1) + expect(string.find('', 'aaa', 1)):eq(nil) + end) + + it("accepts an empty match", function() + expect({ ("foo"):find("") }):same { 1, 0 } + end) + + it("fails on empty strings when out of bounds :lua>=5.2 :!cobalt`", function() + expect(string.find("", "", 2)):eq(nil) + expect(("foo"):find("", 10)):eq(nil) + end) + + it("supports the plain modifier", function() + expect(('alo(.)alo'):find('(.)', 1, true)):eq(4) + expect(('alo(.)alo'):find('(.)', 1, 1)):eq(4) -- Coerces to a boolean. + end) + it("character classes :lua>=5.2", function() -- Lua 5.1 doesn't support 'g'. @@ -55,4 +117,392 @@ describe("The string library", function() end end) end) + + describe("string.len", function() + it("via the string library", function() + expect(string.len("")):eq(0) + expect(string.len("\0\0\0")):eq(3) + expect(string.len("1234567890")):eq(10) + end) + + it("via the operator", function() + expect(#""):eq(0) + expect(#"\0\0\0"):eq(3) + expect(#"1234567890"):eq(10) + end) + end) + + describe("string.byte", function() + it("byte works without a range ", function() + expect(string.byte("a")):eq(97) + expect(string.byte("\228")):eq(228) + end) + + it("byte works with null bytes", function() + expect(string.byte("\0")):eq(0) + expect(string.byte("\0\0alo\0x", -1)):eq(string.byte('x')) + end) + + it("works with various ranges", function() + expect(string.byte("ba", 2)):eq(97) + expect(string.byte("\n\n", 2, -1)):eq(10) + expect(string.byte("\n\n", 2, 2)):eq(10) + end) + + it("returns nil on an empty range", function() + expect(string.byte("")):eq(nil) + expect(string.byte("hi", -3)):eq(nil) + expect(string.byte("hi", 3)):eq(nil) + expect(string.byte("hi", 9, 10)):eq(nil) + expect(string.byte("hi", 2, 1)):eq(nil) + end) + end) + + describe("string.char", function() + it("returns an empty string with no args", function() + expect(string.char()):eq("") + end) + + it("accepts multiple characters", function() + expect(string.char(0, 255, 0)):eq("\0\255\0") + end) + + it("errors on out-of-range values :lua>=5.3 :!cobalt", function() + expect.error(string.char, 256):str_match("value out of range") + expect.error(string.char, -1):str_match("value out of range") + expect.error(string.char, math.maxinteger or 2^40):str_match("value out of range") + expect.error(string.char, math.mininteger or -2^40):str_match("value out of range") + end) + end) + + describe("string.byte/string.char round-tripping", function() + it("single characters", function() + expect(string.byte(string.char(255))):eq(255) + expect(string.byte(string.char(0))):eq(0) + end) + + it("multiple characters", function() + expect(string.char(0, string.byte("\193"), 0)):eq("\0\193\0") + expect(string.char(string.byte("\193l\0\243u", 1, -1))):eq("\193l\0\243u") + expect(string.char(string.byte("\193l\0\243u", 1, 0))):eq("") + expect(string.char(string.byte("\193l\0\243u", -10, 100))):eq("\193l\0\243u") + end) + end) + + it("string.upper", function() + expect(string.upper("ab\0c")):eq("AB\0C") + end) + + it("string.lower", function() + expect(string.lower("\0ABCc%$")):eq("\0abcc%$") + end) + + describe("string.rep", function() + it("with 0 repetitions", function() + expect(string.rep('teste', 0)):eq('') + end) + + it("with multiple repetitions", function() + expect(string.rep('tés\00tÄ™', 2)):eq('tés\0tÄ™tés\000tÄ™') + end) + + it("with an empty string", function() + expect(string.rep('', 10)):eq('') + end) + + it("for various lengths", function() + for i=0,30 do + expect(string.len(string.rep('a', i))):eq(i) + end + end) + + it("with a separator :lua>=5.2", function() + expect(string.rep('teste', 0, 'xuxu')):eq('') + expect(string.rep('teste', 1, 'xuxu')):eq('teste') + expect(string.rep('\1\0\1', 2, '\0\0')):eq('\1\0\1\0\0\1\0\1') + expect(string.rep('', 10, '.')):eq(string.rep('.', 9)) + end) + + it("errors on overflows :lua>=5.3 :!cobalt", function() + expect.error(string.rep, "aa", 2^30):eq("resulting string too large") + expect.error(string.rep, "", 2^30, "aa"):eq("resulting string too large") + end) + end) + + it("string.reverse", function() + expect(string.reverse ""):eq("") + expect(string.reverse "\0\1\2\3"):eq("\3\2\1\0") + expect(string.reverse "\0001234"):eq("4321\0") + end) + + describe("string.format", function() + it("'q' and 's' format strings", function() + local x = '"\237lo"\n\\' + expect(string.format('%q%s', x, x)):eq('"\\"\237lo\\"\\\n\\\\""\237lo"\n\\') + end) + + describe("'s' option", function() + it("with \\0", function() + expect(string.format("%s\0 is not \0%s", 'not be', 'be')):eq('not be\0 is not \0be') + end) + + it("converts other values to strings :lua>=5.2", function() + expect(string.format("%s %s", nil, true)):eq("nil true") + expect(string.format("%s %.4s", false, true)):eq("false true") + expect(string.format("%.3s %.3s", false, true)):eq("fal tru") + local m = setmetatable({}, {__tostring = function () return "hello" end}) + expect(string.format("%s %.10s", m, m)):eq("hello hello") + end) + + it("uses __name :lua>=5.3", function() + local m = setmetatable({}, { __name = "hi" }) + expect(string.format("%.4s", m, m)):eq("hi: ") + end) + + it("errors on invalid flags :lua>=5.4", function() + expect.error(string.format, "%0.34s", ""):str_match("invalid conversion") + expect.error(string.format, "%0.s", ""):str_match("invalid conversion") + end) + + it("supports yielding within __tostring :cobalt", function() + local m = setmetatable({}, { __tostring = function() + return coroutine.yield() .. "!" + end }) + + local co = coroutine.create(string.format) + assert(coroutine.resume(co, "%s", m)) + local _, res = assert(coroutine.resume(co, "Hello")) + expect(res):eq("Hello!") + end) + + it("supports various modifiers", function() + local x = string.format('"%-50s"', 'a') + expect(#x):eq(52) + expect(string.sub(x, 1, 4)):eq('"a ') + + expect(string.format("-%.20s.20s", string.rep("%", 2000))):eq("-"..string.rep("%", 20)..".20s") + expect(string.format('"-%20s.20s"', string.rep("%", 2000))):eq(string.format("%q", "-"..string.rep("%", 2000)..".20s")) + + expect(string.format("%.0s", "alo")):eq("") + expect(string.format("%.s", "alo")):eq("") + end) + end) + + describe("'q' option", function() + it("escapes \\0 to \\000 :lua<=5.1", function() + expect(string.format('%q', "\0")):eq([["\000"]]) + end) + + it("escapes \\0 to \\0 :lua>=5.2 :!cobalt", function() + expect(string.format('%q', "\0")):eq([["\0"]]) + end) + + for _, value in pairs { + { '"\237lo"\n\\' }, + { "\0\1\0023\5\0009" }, + { "\0\0\1\255" }, + -- Basic literals + { 2 ^ 40, nil, ":lua>=5.3" }, + { -2 ^ 40, nil, ":lua>=5.3" }, + { 0.1, nil, ":lua>=5.3" }, + { true, nil, ":lua>=5.3"}, + { nil, nil, ":lua>=5.3" }, + { false, nil, ":lua>=5.3" }, + { math.pi, "math.pi", ":lua>=5.3" }, + { math.huge, "math.huge", ":lua>=5.4" }, + { -math.huge, "math.huge", ":lua>=5.4" }, + -- Large numers + { math.maxinteger, "math.maxinteger", ":lua>=5.3 :!cobalt" }, + { math.mininteger, "math.mininteger", ":lua>=5.3 :!cobalt" }, + } do + local value, label, filter = value[1], value[2] or tostring(value[1]), value[3] + if filter then label = label .. " " .. filter end + it("roundtrips " .. label, function() + local load = loadstring or load + expect(load(string.format('return %q', value))()):eq(value) + end) + end + + it("errors on non-literal values :lua>=5.3", function() + expect.error(string.format, "%q", {}):str_match("value has no literal form") + end) + + it("formats edge-case numbers correctly :lua>=5.4", function() + local formatted = string.format("%q %q %q", 0/0, 1/0, -1/0) + expect(formatted):eq("(0/0) 1e9999 -1e9999") + end) + + it("prints hexadecimal floats :lua>=5.4", function() + expect(("%q"):format(234.53)):eq("0x1.d50f5c28f5c29p+7") + end) + + it("errors on invalid flags :lua>=5.4", function() + expect.error(string.format, "%10q", ""):str_match("cannot have modifiers") + expect.error(string.format, "%#q", ""):str_match("cannot have modifiers") + end) + end) + + describe("'c' option", function() + it("basic functionality", function() + expect(string.format("\0%c\0%c%x\0", 225, string.byte("b"), 140)):eq("\0\225\0b8c\0") + expect( + string.format("%c",34)..string.format("%c",48)..string.format("%c",90)..string.format("%c",100) + ):eq(string.format("%c%c%c%c", 34, 48, 90, 100)) + end) + + it("supports a width", function() + expect(string.format("%-16c", 97)):eq("a ") + expect(string.format("%16c", 97)):eq(" a") + end) + + it("errors on invalid flags :lua>=5.4", function() + expect.error(string.format, "%010c", 10):str_match("invalid conversion") + expect.error(string.format, "%.10c", 10):str_match("invalid conversion") + end) + end) + + describe("'d' option", function() + it("basic modifiers", function() + expect(string.format("%%%d %010d", 10, 23)):eq("%10 0000000023") + expect(string.format("%+08d", 2^31 - 1)):eq("+2147483647") + expect(string.format("%+08d", -2^31)):eq("-2147483648") + end) + + it("with very large numbers", function() + expect(string.format("%d", -1)):eq("-1") + expect(tonumber(string.format("%u", 2^62))):eq(2^62) + expect(string.format("%d", 2^53)):eq("9007199254740992") + expect(string.format("%d", -2^53)):eq("-9007199254740992") + end) + + it("errors on repeated flags :lua<=5.3", function() + expect.error(string.format, "%0000000000d", 10):eq("invalid format (repeated flags)") + end) + + it("errors on invalid options :lua>=5.4", function() + expect.error(string.format, "%#i", 0):str_match("invalid conversion") + end) + + it("supports various modifiers", function() + expect(string.format("%#12o", 10)):eq(" 012") + expect(string.format("%013i", -100)):eq("-000000000100") + expect(string.format("%2.5d", -100)):eq("-00100") + expect(string.format("%.u", 0)):eq("") + end) + end) + + describe("'f' option", function() + it("round trips", function() + expect(tonumber(string.format("%f", 10.3))):eq(10.3) + end) + + it("with large widths", function() + local res = string.format('%99.99f', -1e308) + if #res < 101 + 38 then fail("String is " .. #res) end + expect(tonumber(res)):eq(-1e308) + end) + + it("errors on too large ranges", function() + expect.error(string.format, "%1"..("0"):rep(600)..".3d", 10):str_match("too long") + + local msg = _VERSION == "Lua 5.4" and "invalid conversion" or "too long" + expect.error(string.format, "%100.3d", 10):str_match(msg) + expect.error(string.format, "%1.100d", 10):str_match(msg) + + expect.error(string.format, "%10.1"..("0"):rep(600).."004d", 10):str_match("too long") + end) + + it("supports various modifiers", function() + expect(string.format("%+#014.0f", 100)):eq("+000000000100.") + expect(string.format("% 1.0E", 100)):eq(" 1E+02") + expect(string.format("%+.3G", 1.5)):eq("+1.5") + expect(string.format("% .1g", 2^10)):eq(" 1e+03") + end) + end) + + describe("'x' option", function() + it("on floats :lua<=5.2", function() + expect(string.format("%x", 0.3)):eq("0") + expect(string.format("%02x", 0.1)):eq("00") + end) + + it("with large numbers", function() + expect(string.format("%08X", 2^32 - 1)):eq("FFFFFFFF") + + expect(string.format("%8x", 2^52 - 1)):eq("fffffffffffff") + expect(string.format("%8x", 0xffffffff)):eq("ffffffff") + expect(string.format("%8x", 0x7fffffff)):eq("7fffffff") + expect(string.format("0x%8X", 0x8f000003)):eq("0x8F000003") + end) + + it("supports formatting flags", function() + expect(string.format("%#10x", 100)):eq(" 0x64") + expect(string.format("%#-17X", 100)):eq("0X64 ") + end) + end) + + describe("'a' option :lua>=5.2", function() + it("basic functionality", function() + expect(string.format("%.2a", 0.5)):eq("0x1.00p-1") + expect(string.format("%A", 0x1fffffffffffff)):eq("0X1.FFFFFFFFFFFFFP+52") + expect(string.format("%.4a", -3)):eq("-0x1.8000p+1") + expect(tonumber(string.format("%a", -0.1))):eq(-0.1) + end) + + it("zero", function() + expect(string.format("%A", 0.0)):eq("0X0P+0") + end) + + it("negative zero :!cobalt", function() + expect(string.format("%a", -0.0)):eq("-0x0p+0") + end) + + it("weird numbers :!cobalt", function() + expect(string.format("%a", 1/0)):str_match("^inf") + expect(string.format("%A", -1/0)):str_match("^%-INF") + expect(string.format("%a", 0/0)):str_match("^%-?nan") + expect(string.format("%a", -0.0)):str_match("^%-0x0") + end) + end) + + -- We skip both these tests on Cobalt, as it's not clear what the correct + -- semantics should be + it("very large numbers :lua==5.2 :!cobalt", function() + local x = 2^64 - 2^(64-53) + expect(x):eq(0xfffffffffffff800) + expect(tonumber(string.format("%u", x))):eq(x) + expect(tonumber(string.format("0X%x", x))):eq(x) + expect(string.format("%x", x)):eq("fffffffffffff800") + expect(string.format("%d", x/2)):eq("9223372036854774784") + expect(string.format("%d", -x/2)):eq("-9223372036854774784") + expect(string.format("%d", -2^63)):eq("-9223372036854775808") + expect(string.format("%x", 2^63)):eq("8000000000000000") + end) + + it("very large numbers :lua>=5.3 :!cobalt", function() + local max, min = 0x7fffffffffffffff, -0x8000000000000000 + expect(string.format("%x", 0xfffffffffffff)):eq("fffffffffffff") + expect(string.format("0x%8X", 0x8f000003)):eq("0x8F000003") + expect(string.format("%d", 2^53)):eq("9007199254740992") + expect(string.format("%i", -2^53)):eq("-9007199254740992") + expect(string.format("%x", max)):eq("7fffffffffffffff") + expect(string.format("%x", min)):eq("8000000000000000") + expect(string.format("%d", max)):eq("9223372036854775807") + expect(string.format("%d", min)):eq("-9223372036854775808") + expect(string.format("%u", -1)):eq("18446744073709551615") + expect(tostring(1234567890123)):eq('1234567890123') + end) + + it("errors on invalid options :lua>=5.4", function() + -- The error message was different on previous versions - Cobalt uses the 5.4 version + expect.error(string.format, "%t", 10):eq("invalid conversion '%t' to 'format'") + end) + end) + + describe("string.pack", function() + it("'z' modifier on exactly the buffer boundary :lua>=5.3", function() + local packed = string.pack("z", ("#"):rep(32)) + expect(packed):eq("################################\0") + end) + end) end) diff --git a/src/test/resources/spec/table_spec.lua b/src/test/resources/spec/table_spec.lua index 6fe8c7bf..8cec1c30 100644 --- a/src/test/resources/spec/table_spec.lua +++ b/src/test/resources/spec/table_spec.lua @@ -440,7 +440,7 @@ describe("Lua tables", function() expect(table.remove(a, 1)):eq('a') expect(table.remove(a, 1)):eq('b') expect(table.remove(a, 1)):eq(nil) - assert(#a == 0 and a.n == nil) + expect(#a):eq(0) expect(a.n):eq(nil) end) it("test #5", function() @@ -669,14 +669,14 @@ describe("Lua tables", function() describe("table.unpack", function() it("accepts nil arguments :lua>=5.2", function() local a, b, c = table.unpack({ 1, 2, 3, 4, 5 }, nil, 2) - assert(a == 1) - assert(b == 2) - assert(c == nil) + expect(a):eq(1) + expect(b):eq(2) + expect(c):eq(nil) local a, b, c = table.unpack({ 1, 2 }, nil, nil) - assert(a == 1) - assert(b == 2) - assert(c == nil) + expect(a):eq(1) + expect(b):eq(2) + expect(c):eq(nil) end) it("some basic functionality :lua>=5.2", function() @@ -732,9 +732,9 @@ describe("Lua tables", function() table.unpack({}, maxI, minI) pcall(table.unpack, {}, 1, maxi + 1) local a, b = table.unpack({[maxi] = 20}, maxi, maxi) - assert(a == 20 and b == nil) + expect(a):eq(20) expect(b):eq(nil) a, b = table.unpack({[maxi] = 20}, maxi - 1, maxi) - assert(a == nil and b == 20) + expect(a):eq(nil) expect(b):eq(20) local t = {[maxI - 1] = 12, [maxI] = 23} a, b = table.unpack(t, maxI - 1, maxI); assert(a == 12 and b == 23) a, b = table.unpack(t, maxI, maxI); assert(a == 23 and b == nil) @@ -759,6 +759,51 @@ describe("Lua tables", function() end) describe("table.concat", function() + it("returns empty strings on empty tables", function() + expect(table.concat{}):eq("") + expect(table.concat({}, 'x')):eq("") + end) + + it("works with \\0", function() + expect(table.concat({'\0', '\0\1', '\0\1\2'}, '.\0.')):eq("\0.\0.\0\1.\0.\0\1\2") + end) + + it("accepts various ranges", function() + local a = {}; for i=1,3000 do a[i] = "xuxu" end + expect(table.concat(a, "123").."123"):eq(string.rep("xuxu123", 3000)) + expect(table.concat(a, "b", 20, 20)):eq("xuxu") + expect(table.concat(a, "", 20, 21)):eq("xuxuxuxu") + expect(table.concat(a, "", 22, 21)):eq("") + expect(table.concat(a, "x", 22, 21)):eq("") + expect(table.concat(a, "3", 2999)):eq("xuxu3xuxu") + + local a = {"a","b","c"} + expect(table.concat(a, ",", 1, 0)):eq("") + expect(table.concat(a, ",", 1, 1)):eq("a") + expect(table.concat(a, ",", 1, 2)):eq("a,b") + expect(table.concat(a, ",", 2)):eq("b,c") + expect(table.concat(a, ",", 3)):eq("c") + expect(table.concat(a, ",", 4)):eq("") + end) + + it("avoids integer overflow :!cobalt", function() + expect(table.concat({}, "x", 2^31-1, 2^31-2)):eq("") + expect(table.concat({}, "x", -2^31+1, -2^31)):eq("") + expect(table.concat({}, "x", 2^31-1, -2^31)):eq("") + expect(table.concat({[2^31-1] = "alo"}, "x", 2^31-1, 2^31-1)):eq("alo") + end) + + it("errors on non-strings :!cobalt", function() + expect.error(table.concat, {"a", "b", {}}) + :eq("invalid value (table) at index 3 in table for 'concat'") + end) + + it("errors on non-strings :cobalt", function() + -- FIXME: This is entirely wrong! + expect.error(table.concat, {"a", "b", {}}) + :eq("bad argument (string expected, got table)") + end) + it("uses metamethods :lua>=5.3", function() local basic = make_slice({ "a", "b", "c", "d", "e" }, 2, 3) expect(table.concat(basic)):eq("bcd")