diff options
Diffstat (limited to 'Data/Libraries/Penlight/tests')
44 files changed, 5826 insertions, 0 deletions
diff --git a/Data/Libraries/Penlight/tests/lua/animal.lua b/Data/Libraries/Penlight/tests/lua/animal.lua new file mode 100644 index 0000000..9366db9 --- /dev/null +++ b/Data/Libraries/Penlight/tests/lua/animal.lua @@ -0,0 +1,54 @@ +-- Module containing classes +local class = require 'pl.class' +local utils = require 'pl.utils' +local error = error +if utils.lua51 then + module 'animal' +else + _ENV = {} +end + +class.Animal() + +function Animal:_init(name) + self.name = name +end + +function Animal:__tostring() + return self.name..': '..self:speak() +end + +class.Dog(Animal) + +function Dog:speak() + return 'bark' +end + +class.Cat(Animal) + +function Cat:_init(name,breed) + self:super(name) -- must init base! + self.breed = breed +end + +function Cat:speak() + return 'meow' +end + +-- you may declare the methods in-line like so; +-- note the meaning of `_base`! +class.Lion { + _base = Cat; + speak = function(self) + return 'roar' + end +} + +-- a class may handle unknown methods with `catch`: +Lion:catch(function(self,name) + return function() error("no such method "..name,2) end +end) + +if not utils.lua51 then + return _ENV +end diff --git a/Data/Libraries/Penlight/tests/lua/bar.lua b/Data/Libraries/Penlight/tests/lua/bar.lua new file mode 100644 index 0000000..191cb70 --- /dev/null +++ b/Data/Libraries/Penlight/tests/lua/bar.lua @@ -0,0 +1,10 @@ +--- test module for demonstrating app.require_here() +local bar = {} + +function bar.name () + return 'bar' +end + +return bar + + diff --git a/Data/Libraries/Penlight/tests/lua/foo/args.lua b/Data/Libraries/Penlight/tests/lua/foo/args.lua new file mode 100644 index 0000000..8a53130 --- /dev/null +++ b/Data/Libraries/Penlight/tests/lua/foo/args.lua @@ -0,0 +1,9 @@ +--- test module for demonstrating app.require_here() +local args = {} + +function args.answer () + return 42 +end + +return args + diff --git a/Data/Libraries/Penlight/tests/lua/mod52.lua b/Data/Libraries/Penlight/tests/lua/mod52.lua new file mode 100644 index 0000000..3dd4e46 --- /dev/null +++ b/Data/Libraries/Penlight/tests/lua/mod52.lua @@ -0,0 +1,30 @@ +local test = require 'pl.test' +local LUA_VERSION = _VERSION +print(LUA_VERSION) + +-- if STRICT is true, then M is distinct from _ENV, and ONLY contains +-- the exported functions! + +local _ENV,M = require 'pl.import_into' (rawget(_G,'STRICT')) + +function answer () + -- of course, you don't have the usual global environment available + -- so define it as a local up above, or use utils.import(_G). + + local versioned_errors = { + ["1"] = "attempt to call global 'print'", + ["2"] = "attempt to call global 'print'", + ["3"] = "attempt to call a nil value", + ["4"] = "a nil value", + } + local expected = versioned_errors[LUA_VERSION:match("Lua 5.(%d)")] + test.assertraise(function() + print 'hello' + end, expected) + + -- but all the Penlight modules are available + return pretty.write(utils.split '10 20 30', '') +end + +return M + diff --git a/Data/Libraries/Penlight/tests/lua/mymod.lua b/Data/Libraries/Penlight/tests/lua/mymod.lua new file mode 100644 index 0000000..e7d73a7 --- /dev/null +++ b/Data/Libraries/Penlight/tests/lua/mymod.lua @@ -0,0 +1,19 @@ +local strict = require 'pl.strict' +local test = require 'pl.test' +local M = strict.module (...) + +function M.answer () + Boo = false -- fine, it's a declared global + -- in strict mode, you cannot assign to globals if you aren't in main + test.assertraise(function() + Foo = true + end," assign to undeclared global 'Foo'") + return 42 +end + +function M.question () + return 'what is the answer to Life, the Universe and Everything?' +end + +return M + diff --git a/Data/Libraries/Penlight/tests/test-__vector.lua b/Data/Libraries/Penlight/tests/test-__vector.lua new file mode 100644 index 0000000..e040564 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-__vector.lua @@ -0,0 +1,123 @@ +---- deriving specialized classes from List +-- illustrating covariance of List methods +local test = require 'pl.test' +local class = require 'pl.class' +local types = require 'pl.types' +local operator = require 'pl.operator' +local List = require 'pl.List' + +local asserteq = test.asserteq + +class.Vector(List) + + +function Vector.range (x1,x2,delta) + return Vector(List.range(x1,x2,delta)) +end + +local function vbinop (op,v1,v2,scalar) + if not Vector:class_of(v1) then + v2, v1 = v1, v2 + end + if type(v2) ~= 'table' then + return v1:map(op,v2) + else + if scalar then error("operation not permitted on two vectors",3) end + if #v1 ~= #v2 then error("vectors have different lengths",3) end + return v1:map2(op,v2) + end +end + +function Vector.__add (v1,v2) + return vbinop(operator.add,v1,v2) +end + +function Vector.__sub (v1,v2) + return vbinop(operator.sub,v1,v2) +end + +function Vector.__mul (v1,v2) + return vbinop(operator.mul,v1,v2,true) +end + +function Vector.__div (v1,v2) + return vbinop(operator.div,v1,v2,true) +end + +function Vector.__unm (v) + return v:map(operator.unm) +end + +Vector:catch(List.default_map_with(math)) + +v = Vector() + +assert(v:is_a(Vector)) +assert(Vector:class_of(v)) + +v:append(10) +v:append(20) +asserteq(1+v,v+1) + +-- covariance: the inherited Vector.map returns a Vector +asserteq(List{1,2} + v:map '2*_',{21,42}) + +u = Vector{1,2} + +asserteq(v + u,{11,22}) +asserteq(v - u,{9,18}) +asserteq (v - 1, {9,19}) +asserteq(2 * v, {20,40}) +-- print(v * v) -- throws error: not permitted +-- print(v + Vector{1,2,3}) -- throws error: different lengths +asserteq(2*v + u, {21,42}) +asserteq(-v, {-10,-20}) + +-- Vector.slice returns the Right Thing due to covariance +asserteq( + Vector.range(0,1,0.1):slice(1,3)+1, + {1,1.1,1.2}, + 1e-8) + +u:transform '_+1' +asserteq(u,{2,3}) + +u = Vector.range(0,1,0.1) +asserteq( + u:map(math.sin), + {0,0.0998,0.1986,0.2955,0.3894,0.4794,0.5646,0.6442,0.7173,0.7833,0.8414}, +0.001) + +-- unknown Vector methods are assumed to be math.* functions +asserteq(Vector{-1,2,3,-4}:abs(),Vector.range(1,4)) + +local R = Vector.range + +-- concatenating two Vectors returns another vector (covariance again) +-- note the operator precedence here... +asserteq(( + R(0,1,0.1)..R(1.2,2,0.2)) + 1, + {1,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,1.9,2,2.2,2.4,2.6,2.8,3}, + 1e-8) + + +class.Strings(List) + +Strings:catch(List.default_map_with(string)) + +ls = Strings{'one','two','three'} +asserteq(ls:upper(),{'ONE','TWO','THREE'}) +asserteq(ls:sub(1,2),{'on','tw','th'}) + +-- all map operations on specialized lists +-- results in another list of that type! This isn't necessarily +-- what you want. +local sizes = ls:map '#' +asserteq(sizes, {3,3,5}) +asserteq(types.type(sizes),'Strings') +asserteq(sizes:is_a(Strings),true) +sizes = Vector:cast(sizes) +asserteq(types.type(sizes),'Vector') +asserteq(sizes+1,{4,4,6}) + + diff --git a/Data/Libraries/Penlight/tests/test-app.lua b/Data/Libraries/Penlight/tests/test-app.lua new file mode 100644 index 0000000..980cb16 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-app.lua @@ -0,0 +1,280 @@ +local app = require "pl.app" +local utils = require "pl.utils" +local path = require "pl.path" +local asserteq = require 'pl.test'.asserteq + +local quote = utils.quote_arg + +local _, cmd = app.lua() +cmd = cmd .. " " .. quote({"-e", "package.path=[[./lua/?.lua;./lua/?/init.lua;]]..package.path"}) + +local function run_script(s, fname) + local tmpname = path.tmpname() + if fname then + tmpname = path.join(path.dirname(tmpname), fname) + end + assert(utils.writefile(tmpname, s)) + local success, code, stdout, stderr = utils.executeex(cmd.." "..tmpname) + os.remove(tmpname) + return success, code, stdout, stderr +end + +do -- app.script_name + + local success, code, stdout, stderr = run_script([[ + print(require("pl.app").script_name()) + ]], + "justsomescriptname.lua") + asserteq(stderr, "") + asserteq(stdout:match("(justsome.+)$"), "justsomescriptname.lua\n") + + + -- commandline, no scriptname + local success, code, stdout, stderr = run_script([[ + arg[0] = nil -- simulate no scriptname + local name, err = require("pl.app").script_name() + io.stdout:write(tostring(name)) + io.stderr:write(err) + ]]) + assert(stderr:find("No script name found")) + asserteq(stdout, "nil") + + + -- commandline, no args table + local success, code, stdout, stderr = run_script([[ + arg = nil -- simulate no arg table + local name, err = require("pl.app").script_name() + io.stdout:write(tostring(name)) + io.stderr:write(err) + ]]) + assert(stderr:find("No script name found")) + asserteq(stdout, "nil") +end + +do -- app.require_here + local cd = path.currentdir() --path.dirname(path.tmpname()) + + -- plain script name + local success, code, stdout, stderr = run_script([[ + arg[0] = "justsomescriptname.lua" + local p = package.path + require("pl.app").require_here() + print(package.path:sub(1, -#p-1)) + ]]) + asserteq(stderr, "") + stdout = path.normcase(stdout) + assert(stdout:find(path.normcase(cd.."/?.lua;"), 1, true)) + assert(stdout:find(path.normcase(cd.."/?/init.lua;"), 1, true)) + + + -- plain script name, with a relative base name + local success, code, stdout, stderr = run_script([[ + arg[0] = "justsomescriptname.lua" + local p = package.path + require("pl.app").require_here("basepath/to/somewhere") + print(package.path:sub(1, -#p-1)) + ]]) + asserteq(stderr, "") + stdout = path.normcase(stdout) + assert(stdout:find(path.normcase(cd.."/basepath/to/somewhere/?.lua;"), 1, true)) + assert(stdout:find(path.normcase(cd.."/basepath/to/somewhere/?/init.lua;"), 1, true)) + + + -- plain script name, with an absolute base name + local success, code, stdout, stderr = run_script([[ + arg[0] = "justsomescriptname.lua" + local p = package.path + require("pl.app").require_here("/basepath/to/somewhere") + print(package.path:sub(1, -#p-1)) + ]]) + asserteq(stderr, "") + stdout = path.normcase(stdout) + asserteq(stdout, path.normcase("/basepath/to/somewhere/?.lua;/basepath/to/somewhere/?/init.lua;\n")) + + + -- scriptname with a relative path + local success, code, stdout, stderr = run_script([[ + arg[0] = "relative/prefix/justsomescriptname.lua" + local p = package.path + require("pl.app").require_here() + print(package.path:sub(1, -#p-1)) + os.exit() + ]]) + asserteq(stderr, "") + stdout = path.normcase(stdout) + assert(stdout:find(path.normcase(cd.."/relative/prefix/?.lua;"), 1, true)) + assert(stdout:find(path.normcase(cd.."/relative/prefix/?/init.lua;"), 1, true)) + + + -- script with an absolute path + local success, code, stdout, stderr = run_script([[ + arg[0] = "/fixed/justsomescriptname.lua" + local p = package.path + require("pl.app").require_here() + print(package.path:sub(1, -#p-1)) + ]]) + asserteq(stderr, "") + stdout = path.normcase(stdout) + asserteq(stdout, path.normcase("/fixed/?.lua;/fixed/?/init.lua;\n")) + +end + + +do -- app.appfile + local success, code, stdout, stderr = run_script([[ + arg[0] = "some/path/justsomescriptname_for_penlight_testing.lua" + print(require("pl.app").appfile("filename.data")) + ]]) + asserteq(stderr, "") + stdout = path.normcase(stdout) + local fname = path.normcase(path.expanduser("~/.justsomescriptname_for_penlight_testing/filename.data")) + asserteq(stdout, fname .."\n") + assert(path.isdir(path.dirname(fname))) + path.rmdir(path.dirname(fname)) + +end + + +do -- app.lua + local success, code, stdout, stderr = run_script([[ + arg[0] = "justsomescriptname.lua" + local a,b = require("pl.app").lua() + print(a) + ]]) + asserteq(stderr, "") + asserteq(stdout, cmd .."\n") + +end + + +do -- app.parse_args + + -- no value specified + local args = utils.split("-a -b") + local t,s = app.parse_args(args, { a = true}) + asserteq(t, nil) + asserteq(s, "no value for 'a'") + + + -- flag that take a value, space separated + local args = utils.split("-a -b value -c") + local t,s = app.parse_args(args, { b = true}) + asserteq(t, { + a = true, + b = "value", + c = true, + }) + asserteq(s, {}) + + + -- flag_with_values specified as a list + local args = utils.split("-a -b value -c") + local t,s = app.parse_args(args, { "b" }) + asserteq(t, { + a = true, + b = "value", + c = true, + }) + asserteq(s, {}) + + + -- flag_with_values missing value at end + local args = utils.split("-a -b") + local t,s = app.parse_args(args, { "b" }) + asserteq(t, nil) + asserteq(s, "no value for 'b'") + + + -- error on an unknown flag + local args = utils.split("-a -b value -c") + local t,s = app.parse_args(args, { b = true }, { "b", "c" }) + asserteq(t, nil) + asserteq(s, "unknown flag 'a'") + + + -- flag that doesn't take a value + local args = utils.split("-a -b:value") + local t,s = app.parse_args(args, {}) + asserteq(t, { + ["a"] = true, + ["b"] = "value" + }) + asserteq(s, {}) + + + -- correctly parsed values, spaces, :, =, and multiple : or = + local args = utils.split("-a value -b value:one=two -c=value2:2") + local t,s = app.parse_args(args, { "a", "b", "c" }) + asserteq(t, { + ["a"] = "value", + ["b"] = "value:one=two", + ["c"] = "value2:2", + }) + asserteq(s, {}) + + + -- many values, duplicates, and parameters mixed + local args = utils.split( + "-a -b -cde --long1 --ff:ffvalue --gg=ggvalue -h:hvalue -i=ivalue " .. + "-i=2ndvalue param -i:3rdvalue -j1 -k2 -1:hello remaining values") + local t,s = app.parse_args(args) + asserteq({ + i = "3rdvalue", + ["1"] = "hello", + ff = "ffvalue", + long1 = true, + c = true, + b = true, + gg = "ggvalue", + j = "1", + k = "2", + d = true, + h = "hvalue", + a = true, + e = true + }, t) + asserteq({ + "param", + "remaining", + "values" + }, s) + + + -- specify valid flags and aliasses + local args = utils.split("-a -b value -e -f3") + local t,s = app.parse_args(args, + { + "b", + f = true, + }, { + bully = "b", -- b with value will be reported as 'bully', alias as string + a = true, -- hash-type value + c = { "d", "e" }, -- e will be reported as c, aliasses as list/table + }) + asserteq(t, { + a = true, + bully = "value", + c = true, + f = "3", + }) + asserteq(s, {}) + + + -- error on an unknown flag, in a chain of short ones + local args = utils.split("-b value -cd") + local t,s = app.parse_args(args, { b = true }, { "b", "c" }) + asserteq(t, nil) + asserteq(s, "unknown flag 'd'") + + + -- flag, in a chain of short ones, gets converted to alias + local args = utils.split("-dbc") + local t,s = app.parse_args(args, nil, { "d", full_name = "b", "c" }) + asserteq(t, { + full_name = true, -- specified as b in a chain of short ones + c = true, + d = true, + }) + asserteq(s, {}) + +end diff --git a/Data/Libraries/Penlight/tests/test-class.lua b/Data/Libraries/Penlight/tests/test-class.lua new file mode 100644 index 0000000..8621005 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-class.lua @@ -0,0 +1,207 @@ +local class = require 'pl.class' +local test = require 'pl.test' +asserteq = test.asserteq +T = test.tuple + +A = class() + +function A:_init () + self.a = 1 +end + +-- calling base class' ctor automatically +A1 = class(A) + +asserteq(A1(),{a=1}) + +-- explicitly calling base ctor with super + +B = class(A) + +function B:_init () + self:super() + self.b = 2 +end + +function B:foo () + self.eee = 1 +end + +function B:foo2 () + self.g = 8 +end + +asserteq(B(),{a=1,b=2}) + +-- can continue this chain + +C = class(B) + +function C:_init () + self:super() + self.c = 3 +end + +function C:foo () + -- recommended way to call inherited version of method... + B.foo(self) +end + +c = C() +c:foo() + +asserteq(c,{a=1,b=2,c=3,eee=1}) + +-- test indirect inherit + +D = class(C) + +E = class(D) + +function E:_init () + self:super() + self.e = 4 +end + +function E:foo () + -- recommended way to call inherited version of method... + self.eeee = 5 + C.foo(self) +end + +F = class(E) + +function F:_init () + self:super() + self.f = 6 +end + +f = F() +f:foo() +f:foo2() -- Test : invocation inherits this function from all the way up in B + +asserteq(f,{a=1,b=2,c=3,eee=1,e=4,eeee=5,f=6,g=8}) + +-- Test that inappropriate calls to super() fail gracefully + +G = class() -- Class with no init + +H = class(G) -- Class with an init that wrongly calls super() + +function H:_init() + self:super() -- Notice: G has no _init +end + +I = class(H) -- Inherits the init with a bad super +J = class(I) -- Grandparent-inits the init with a bad super + +K = class(J) -- Has an init, which calls the init with a bad super + +function K:_init() + self:super() +end + +local function createG() + return G() +end + +local function createH() -- Wrapper function for pcall + return H() +end + +local function createJ() + return J() +end + +local function createK() + return K() +end + +assert(pcall(createG)) -- Should succeed +assert(not pcall(createH)) -- These three should fail +assert(not pcall(createJ)) +assert(not pcall(createK)) + +--- class methods! +assert(c:is_a(C)) +assert(c:is_a(B)) +assert(c:is_a(A)) +assert(c:is_a() == C) +assert(C:class_of(c)) +assert(B:class_of(c)) +assert(A:class_of(c)) + +--- metamethods! + +function C:__tostring () + return ("%d:%d:%d"):format(self.a,self.b,self.c) +end + +function C.__eq (c1,c2) + return c1.a == c2.a and c1.b == c2.b and c1.c == c2.c +end + +asserteq(C(),{a=1,b=2,c=3}) + +asserteq(tostring(C()),"1:2:3") + +asserteq(C()==C(),true) + +----- properties ----- + +local MyProps = class(class.properties) +local setted_a, got_b + +function MyProps:_init () + self._a = 1 + self._b = 2 +end + +function MyProps:set_a (v) + setted_a = true + self._a = v +end + +function MyProps:get_b () + got_b = true + return self._b +end + +function MyProps:set (a,b) + self._a = a + self._b = b +end + +local mp = MyProps() + +mp.a = 10 + +asserteq(mp.a,10) +asserteq(mp.b,2) +asserteq(setted_a and got_b, true) + +class.MoreProps(MyProps) +local setted_c + +function MoreProps:_init() + self:super() + self._c = 3 +end + +function MoreProps:set_c (c) + setted_c = true + self._c = c +end + +mm = MoreProps() + +mm:set(10,20) +mm.c = 30 + +asserteq(setted_c, true) +asserteq(T(mm.a, mm.b, mm.c),T(10,20,30)) + + + + + diff --git a/Data/Libraries/Penlight/tests/test-class2.lua b/Data/Libraries/Penlight/tests/test-class2.lua new file mode 100644 index 0000000..b463b6e --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-class2.lua @@ -0,0 +1,28 @@ +-- animal.lua +require 'pl.app'.require_here 'lua' + +local test = require 'pl.test' +local asserteq = test.asserteq + +local A = require 'animal' + +local fido, felix, leo +fido = A.Dog('Fido') +felix = A.Cat('Felix','Tabby') +leo = A.Lion('Leo','African') + +asserteq(fido:speak(),'bark') +asserteq(felix:speak(),'meow') +asserteq(leo:speak(),'roar') + +asserteq(tostring(leo),'Leo: roar') + +test.assertraise(function() leo:circus_act() end, "no such method circus_act") + +asserteq(leo:is_a(A.Animal),true) +asserteq(leo:is_a(A.Dog),false) +asserteq(leo:is_a(A.Cat),true) + +asserteq(A.Dog:class_of(leo),false) +asserteq(A.Cat:class_of(leo),true) +asserteq(A.Lion:class_of(leo),true) diff --git a/Data/Libraries/Penlight/tests/test-class3.lua b/Data/Libraries/Penlight/tests/test-class3.lua new file mode 100644 index 0000000..eb3c26f --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-class3.lua @@ -0,0 +1,29 @@ +-- another way to define classes. Works particularly well +-- with Moonscript +local class = require('pl.class') +local A = class{ + _init = function(self, name) + self.name = name + end, + greet = function(self) + return "hello " .. self.name + end, + __tostring = function(self) + return self.name + end +} + +local B = class{ + _base = A, + + greet = function(self) + return "hola " .. self.name + end +} + +local a = A('john') +assert(a:greet()=="hello john") +assert(tostring(a) == "john") +local b = B('juan') +assert(b:greet()=="hola juan") +assert(tostring(b)=="juan") diff --git a/Data/Libraries/Penlight/tests/test-class4.lua b/Data/Libraries/Penlight/tests/test-class4.lua new file mode 100644 index 0000000..2a4945f --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-class4.lua @@ -0,0 +1,26 @@ +local class = require 'pl.class' +local A = class() +function A:_init() + self.init_chain = "A" +end +local B = class(A) +local C = class(B) +function C:_init() + self:super() + self.init_chain = self.init_chain.."C" +end +local D = class(C) +local E = class(D) +function E:_init() + self:super() + self.init_chain = self.init_chain.."E" +end +local F = class(E) +local G = class(F) +function G:_init() + self:super() + self.init_chain = self.init_chain.."G" +end + +local i = G() +assert(i.init_chain == "ACEG") diff --git a/Data/Libraries/Penlight/tests/test-compat.lua b/Data/Libraries/Penlight/tests/test-compat.lua new file mode 100644 index 0000000..c4a2724 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-compat.lua @@ -0,0 +1,29 @@ +local test = require 'pl.test' +local asserteq = test.asserteq + +local compat = require "pl.compat" +local coroutine = require "coroutine" + +local code_generator = coroutine.wrap(function() + local result = {"ret", "urn \"Hello World!\""} + for _,v in ipairs(result) do + coroutine.yield(v) + end + coroutine.yield(nil) +end) + +local f, err = compat.load(code_generator) +asserteq(err, nil) +asserteq(f(), "Hello World!") + + +-- package.searchpath +if compat.lua51 and not compat.jit then + assert(package.searchpath("pl.compat", package.path):match("lua[/\\]pl[/\\]compat")) + + local path = "some/?/nice.path;another/?.path" + local ok, err = package.searchpath("my.file.name", path, ".", "/") + asserteq(err, "\tno file 'some/my/file/name/nice.path'\n\tno file 'another/my/file/name.path'") + local ok, err = package.searchpath("my/file/name", path, "/", ".") + asserteq(err, "\tno file 'some/my.file.name/nice.path'\n\tno file 'another/my.file.name.path'") +end
\ No newline at end of file diff --git a/Data/Libraries/Penlight/tests/test-comprehension.lua b/Data/Libraries/Penlight/tests/test-comprehension.lua new file mode 100644 index 0000000..900bb45 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-comprehension.lua @@ -0,0 +1,68 @@ +-- test-comprehension.lua +-- test of comprehension.lua +local utils = require 'pl.utils' +local comp = require 'pl.comprehension' . new() +local asserteq = require 'pl.test' . asserteq + +-- test of list building +asserteq(comp 'x for x' {}, {}) +asserteq(comp 'x for x' {2,3}, {2,3}) +asserteq(comp 'x^2 for x' {2,3}, {2^2,3^2}) +asserteq(comp 'x for x if x % 2 == 0' {4,5,6,7}, {4,6}) +asserteq(comp '{x,y} for x for y if x>2 if y>4' ({2,3},{4,5}), {{3,5}}) + +-- test of table building +local t = comp 'table(x,x+1 for x)' {3,4} +assert(t[3] == 3+1 and t[4] == 4+1) +local t = comp 'table(x,x+y for x for y)' ({3,4}, {2}) +assert(t[3] == 3+2 and t[4] == 4+2) +local t = comp 'table(v,k for k,v in pairs(_1))' {[3]=5, [5]=7} +assert(t[5] == 3 and t[7] == 5) + +-- test of sum +assert(comp 'sum(x for x)' {} == 0) +assert(comp 'sum(x for x)' {2,3} == 2+3) +assert(comp 'sum(x^2 for x)' {2,3} == 2^2+3^2) +assert(comp 'sum(x*y for x for y)' ({2,3}, {4,5}) == 2*4+2*5+3*4+3*5) +assert(comp 'sum(x^2 for x if x % 2 == 0)' {4,5,6,7} == 4^2+6^2) +assert(comp 'sum(x*y for x for y if x>2 if y>4)' ({2,3}, {4,5}) == 3*5) + +-- test of min/max +assert(comp 'min(x for x)' {3,5,2,4} == 2) +assert(comp 'max(x for x)' {3,5,2,4} == 5) + +-- test of placeholder parameters -- +assert(comp 'sum(x^_1 + _3 for x if x >= _4)' (2, nil, 3, 4, {3,4,5}) + == 4^2+3 + 5^2+3) + +-- test of for = +assert(comp 'sum(x^2 for x=2,3)' () == 2^2+3^2) +assert(comp 'sum(x^2 for x=2,6,1+1)' () == 2^2+4^2+6^2) +assert(comp 'sum(x*y*z for x=1,2 for y=3,3 for z)' {5,6} == + 1*3*5 + 2*3*5 + 1*3*6 + 2*3*6) +assert(comp 'sum(x*y*z for z for x=1,2 for y=3,3)' {5,6} == + 1*3*5 + 2*3*5 + 1*3*6 + 2*3*6) + +-- test of for in +assert(comp 'sum(i*v for i,v in ipairs(_1))' {2,3} == 1*2+2*3) +assert(comp 'sum(i*v for i,v in _1,_2,_3)' (ipairs{2,3}) == 1*2+2*3) + +-- test of difficult syntax +asserteq(comp '" x for x " for x' {2}, {' x for x '}) +asserteq(comp 'x --[=[for x\n\n]=] for x' {2}, {2}) +asserteq(comp '(function() for i = 1,1 do return x*2 end end)() for x' + {2}, {4}) +assert(comp 'sum(("_5" and x)^_1 --[[_6]] for x)' (2, {4,5}) == 4^2 + 5^2) + +-- error checking +assert(({pcall(function() comp 'x for __result' end)})[2] + :find'not contain __ prefix') + +-- environment. +-- Note: generated functions are set to the environment of the 'new' call. + asserteq(5,(function() + local env = {d = 5} + local comp = comp.new(env) + return comp 'sum(d for x)' {1} + end)()); +print 'DONE' diff --git a/Data/Libraries/Penlight/tests/test-config.lua b/Data/Libraries/Penlight/tests/test-config.lua new file mode 100644 index 0000000..13fc1ac --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-config.lua @@ -0,0 +1,320 @@ +local config = require 'pl.config' +local stringio = require 'pl.stringio' +asserteq = require 'pl.test'.asserteq + +function testconfig(test,tbl,cfg) + local f = stringio.open(test) + local c = config.read(f,cfg) + f:close() + if not tbl then + print(pretty.write(c)) + else + asserteq(c,tbl) + end +end + +testconfig ([[ + ; comment 2 (an ini file) +[section!] +bonzo.dog=20,30 +config_parm=here we go again +depth = 2 +[another] +felix="cat" +]],{ + section_ = { + bonzo_dog = { -- comma-sep values get split by default + 20, + 30 + }, + depth = 2, + config_parm = "here we go again" + }, + another = { + felix = "\"cat\"" + } +}) + + +testconfig ([[ +# this is a more Unix-y config file +fred = 1 +alice = 2 +home.dog = /bonzo/dog/etc +]],{ + home_dog = "/bonzo/dog/etc", -- note the default is {variablilize = true} + fred = 1, + alice = 2 +}) + +-- backspace line continuation works, thanks to config.lines function +testconfig ([[ +foo=frodo,a,c,d, \ + frank, alice, boyo +]], +{ + foo = { + "frodo", + "a", + "c", + "d", + "frank", + "alice", + "boyo" + } +} +) + +------ options to control default behaviour ----- + +-- want to keep key names as is! +testconfig ([[ +alpha.dog=10 +# comment here +]],{ + ["alpha.dog"]=10 +},{variabilize=false}) + +-- don't convert strings to numbers +testconfig ([[ +alpha.dog=10 +; comment here +]],{ + alpha_dog="10" +},{convert_numbers=false}) + +-- convert strings to booleans +testconfig ([[ +alpha.dog=false +alpha.cat=true +; comment here +]],{ + alpha_dog=false, + alpha_cat=true +},{convert_boolean=true}) + +-- don't split comma-lists by setting the list delimiter to something else +testconfig ([[ +extra=10,'hello',42 +]],{ + extra="10,'hello',42" +},{list_delim='@'}) + +-- Unix-style password file +testconfig([[ +lp:x:7:7:lp:/var/spool/lpd:/bin/sh +mail:x:8:8:mail:/var/mail:/bin/sh +news:x:9:9:news:/var/spool/news:/bin/sh +]], +{ + { + "lp", + "x", + 7, + 7, + "lp", + "/var/spool/lpd", + "/bin/sh" + }, + { + "mail", + "x", + 8, + 8, + "mail", + "/var/mail", + "/bin/sh" + }, + { + "news", + "x", + 9, + 9, + "news", + "/var/spool/news", + "/bin/sh" + } +}, +{list_delim=':'}) + +-- Unix updatedb.conf is in shell script form, but config.read +-- copes by extracting the variables as keys and the export +-- commands as the array part; there is an option to remove quotes +-- from values +testconfig([[ +# Global options for invocations of find(1) +FINDOPTIONS='-ignore_readdir_race' +export FINDOPTIONS +]],{ + "export FINDOPTIONS", + FINDOPTIONS = "-ignore_readdir_race" +},{trim_quotes=true}) + +-- Unix fstab format. No key/value assignments so use `ignore_assign`; +-- list values are separated by a number of spaces +testconfig([[ +# <file system> <mount point> <type> <options> <dump> <pass> +proc /proc proc defaults 0 0 +/dev/sda1 / ext3 defaults,errors=remount-ro 0 1 +]], +{ + { + "proc", + "/proc", + "proc", + "defaults", + 0, + 0 + }, + { + "/dev/sda1", + "/", + "ext3", + "defaults,errors=remount-ro", + 0, + 1 + } +}, +{list_delim='%s+',ignore_assign=true} +) + +-- Linux procfs 'files' often use ':' as the key/pair separator; +-- a custom convert_numbers handles the units properly! +-- Here is the first two lines from /proc/meminfo +testconfig([[ +MemTotal: 1024748 kB +MemFree: 220292 kB +]], +{ MemTotal = 1024748, MemFree = 220292 }, +{ + keysep = ':', + convert_numbers = function(s) + s = s:gsub(' kB$','') + return tonumber(s) + end + } +) + +-- altho this works, rather use pl.data.read for this kind of purpose. +testconfig ([[ +# this is just a set of comma-separated values +1000,444,222 +44,555,224 +]],{ + { + 1000, + 444, + 222 + }, + { + 44, + 555, + 224 + } +}) + +--- new with 1.0.3: smart configuration file reading +-- handles a number of common Unix file formats automatically + +function smart(f) + f = stringio.open(f) + return config.read(f,{smart=true}) +end + +-- /etc/fstab +asserteq (smart[[ +# /etc/fstab: static file system information. +# +# Use 'blkid -o value -s UUID' to print the universally unique identifier +# for a device; this may be used with UUID= as a more robust way to name +# devices that works even if disks are added and removed. See fstab(5). +# +# <file system> <mount point> <type> <options> <dump> <pass> +proc /proc proc nodev,noexec,nosuid 0 0 +/dev/sdb2 / ext2 errors=remount-ro 0 1 +/dev/fd0 /media/floppy0 auto rw,user,noauto,exec,utf8 0 0 +]],{ + proc = { + "/proc", + "proc", + "nodev,noexec,nosuid", + 0, + 0 + }, + ["/dev/sdb2"] = { + "/", + "ext2", + "errors=remount-ro", + 0, + 1 + }, + ["/dev/fd0"] = { + "/media/floppy0", + "auto", + "rw,user,noauto,exec,utf8", + 0, + 0 + } +}) + +-- /proc/XXXX/status +asserteq (smart[[ +Name: bash +State: S (sleeping) +Tgid: 30071 +Pid: 30071 +PPid: 1587 +TracerPid: 0 +Uid: 1000 1000 1000 1000 +Gid: 1000 1000 1000 1000 +FDSize: 256 +Groups: 4 20 24 46 105 119 122 1000 +VmPeak: 6780 kB +VmSize: 6716 kB +]],{ + Pid = 30071, + VmSize = 6716, + PPid = 1587, + Tgid = 30071, + State = "S (sleeping)", + Uid = "1000 1000 1000 1000", + Name = "bash", + Gid = "1000 1000 1000 1000", + Groups = "4 20 24 46 105 119 122 1000", + FDSize = 256, + VmPeak = 6780, + TracerPid = 0 +}) + +-- ssh_config +asserteq (smart[[ +Host * +# ForwardAgent no +# ForwardX11 no +# Tunnel no +# TunnelDevice any:any +# PermitLocalCommand no +# VisualHostKey no + SendEnv LANG LC_* + HashKnownHosts yes + GSSAPIAuthentication yes + GSSAPIDelegateCredentials no +]],{ + Host = "*", + GSSAPIAuthentication = "yes", + SendEnv = "LANG LC_*", + HashKnownHosts = "yes", + GSSAPIDelegateCredentials = "no" +}) + +-- updatedb.conf +asserteq (smart[[ +PRUNE_BIND_MOUNTS="yes" +# PRUNENAMES=".git .bzr .hg .svn" +PRUNEPATHS="/tmp /var/spool /media" +PRUNEFS="NFS nfs nfs4 rpc_pipefs afs binfmt_misc proc smbfs autofs iso9660 ncpfs coda devpts ftpfs devfs mfs shfs sysfs cifs lustre_lite tmpfs usbfs udf fuse.glusterfs fuse.sshfs ecryptfs fusesmb devtmpfs" +]],{ + PRUNEPATHS = "/tmp /var/spool /media", + PRUNE_BIND_MOUNTS = "yes", + PRUNEFS = "NFS nfs nfs4 rpc_pipefs afs binfmt_misc proc smbfs autofs iso9660 ncpfs coda devpts ftpfs devfs mfs shfs sysfs cifs lustre_lite tmpfs usbfs udf fuse.glusterfs fuse.sshfs ecryptfs fusesmb devtmpfs" +}) diff --git a/Data/Libraries/Penlight/tests/test-data.lua b/Data/Libraries/Penlight/tests/test-data.lua new file mode 100644 index 0000000..95eeba1 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-data.lua @@ -0,0 +1,245 @@ +local data = require 'pl.data' +local List = require 'pl.List' +local array = require 'pl.array2d' +local func = require 'pl.func' +local seq = require 'pl.seq' +local stringio = require 'pl.stringio' +local open = stringio. open +local asserteq = require 'pl.test' . asserteq +local T = require 'pl.test'. tuple + +--[=[ +dat,err = data.read(open [[ +1.0 0.1 +0.2 1.3 +]]) + +if err then print(err) end + +require 'pl.pretty'.dump(dat) +os.exit(0) +--]=] + +-- tab-separated data, explicit column names +local t1f = open [[ +EventID Magnitude LocationX LocationY LocationZ LocationError EventDate DataFile +981124001 2.0 18988.4 10047.1 4149.7 33.8 24/11/1998 11:18:05 981124DF.AAB +981125001 0.8 19104.0 9970.4 5088.7 3.0 25/11/1998 05:44:54 981125DF.AAB +981127003 0.5 19012.5 9946.9 3831.2 46.0 27/11/1998 17:15:17 981127DF.AAD +981127005 0.6 18676.4 10606.2 3761.9 4.4 27/11/1998 17:46:36 981127DF.AAF +981127006 0.2 19109.9 9716.5 3612.0 11.8 27/11/1998 19:29:51 981127DF.AAG +]] + +local t1 = data.read (t1f) +-- column_by_name returns a List +asserteq(t1:column_by_name 'Magnitude',List{2,0.8,0.5,0.6,0.2}) +-- can use array.column as well +asserteq(array.column(t1,2),{2,0.8,0.5,0.6,0.2}) + +-- only numerical columns (deduced from first data row) are converted by default +-- can look up indices in the list fieldnames. +local EDI = t1.fieldnames:index 'EventDate' +assert(type(t1[1][EDI]) == 'string') + +-- select method returns a sequence, in this case single-valued. +-- (Note that seq.copy returns a List) +asserteq(seq(t1:select 'LocationX where Magnitude > 0.5'):copy(),List{18988.4,19104,18676.4}) + +--[[ +--a common select usage pattern: +for event,mag in t1:select 'EventID,Magnitude sort by Magnitude desc' do + print(event,mag) +end +--]] + +-- space-separated, but with last field containing spaces. +local t2f = open [[ +USER PID %MEM %CPU COMMAND +sdonovan 2333 0.3 0.1 background --n=2 +root 2332 0.4 0.2 fred --start=yes +root 2338 0.2 0.1 backyard-process +]] + +local t2,err = data.read(t2f,{last_field_collect=true}) +if not t2 then return print (err) end + +-- the last_field_collect option is useful with space-delimited data where the last +-- field may contain spaces. Otherwise, a record count mismatch should be an error! +local lt2 = List(t2[2]) +asserteq(lt2:join ',','root,2332,0.4,0.2,fred --start=yes') + +-- fieldnames are converted into valid identifiers by substituting _ +-- (we do this to make select queries parseable by Lua) +asserteq(t2.fieldnames,List{'USER','PID','_MEM','_CPU','COMMAND'}) + +-- select queries are NOT SQL so remember to use == ! (and no 'between' operator, sorry) +--s,err = t2:select('_MEM where USER="root"') +--assert(err == [[[string "tmp"]:9: unexpected symbol near '=']]) + +local s = t2:select('_MEM where USER=="root"') +assert(s() == 0.4) +assert(s() == 0.2) +assert(s() == nil) + +-- CSV, Excel style. Double-quoted fields are allowed, and they may contain commas! +local t3f = open [[ +"Department Name","Employee ID",Project,"Hours Booked" +sales,1231,overhead,4 +sales,1255,overhead,3 +engineering,1501,development,5 +engineering,1501,maintenance,3 +engineering,1433,maintenance,10 +]] + +local t3 = data.read(t3f,{csv=true}) + +-- although fieldnames are turned in valid Lua identifiers, there is always `original_fieldnames` +asserteq(t3.fieldnames,List{'Department_Name','Employee_ID','Project','Hours_Booked'}) +asserteq(t3.original_fieldnames,List{'Department Name','Employee ID','Project','Hours Booked'}) + +-- a common operation is to select using a given list of columns, and each row +-- on some explicit condition. The select() method can take a table with these +-- parameters +local keepcols = {'Employee_ID','Hours_Booked'} + +local q = t3:select { fields = keepcols, + where = function(row) return row[1]=='engineering' end + } + +asserteq(seq.copy2(q),{{1501,5},{1501,3},{1433,10}}) + +-- another pattern is doing a select to restrict rows & columns, process some +-- fields and write out the modified rows. + +local outf = stringio.create() + +local names = {[1501]='don',[1433]='dilbert'} + +t3:write_row (outf,{'Employee','Hours_Booked'}) +q = t3:select_row {fields=keepcols,where=func.Eq(func._1[1],'engineering')} +for row in q do + row[1] = names[row[1]] + t3:write_row(outf,row) +end + +asserteq(outf:value(), +[[ +Employee,Hours_Booked +don,5 +don,3 +dilbert,10 +]]) + +-- data may not always have column headers. When creating a data object +-- from a two-dimensional array, may specify the fieldnames, as a list or a string. +-- The delimiter is deduced from the fieldname string, so a string just containing +-- the delimiter will set it, and the fieldnames will be empty. +local dat = List() +local row = List.range(1,10) +for i = 1,10 do + dat:append(row:map('*',i)) +end +dat = data.new(dat,',') +local out = stringio.create() +dat:write(out,',') +asserteq(out:value(), [[ +1,2,3,4,5,6,7,8,9,10 +2,4,6,8,10,12,14,16,18,20 +3,6,9,12,15,18,21,24,27,30 +4,8,12,16,20,24,28,32,36,40 +5,10,15,20,25,30,35,40,45,50 +6,12,18,24,30,36,42,48,54,60 +7,14,21,28,35,42,49,56,63,70 +8,16,24,32,40,48,56,64,72,80 +9,18,27,36,45,54,63,72,81,90 +10,20,30,40,50,60,70,80,90,100 +]]) + +-- you can always use numerical field indices, AWK-style; +-- note how the copy_select method gives you a data object instead of an +-- iterator over the fields +local res = dat:copy_select '$1,$3 where $1 > 5' +local L = List +asserteq(L(res),L{ + L{6, 18}, + L{7,21}, + L{8,24}, + L{9,27}, + L{10,30}, +}) + +-- the column_by_name method may take a fieldname or an index +asserteq(dat:column_by_name(2), L{2,4,6,8,10,12,14,16,18,20}) + +-- the field list may contain expressions or even constants +local q = dat:select '$3,2*$4 where $1 == 8' +asserteq(T(q()),T(24,64)) + +dat,err = data.read(open [[ +1.0 0.1 +0.2 1.3 +]]) + +if err then print(err) end + +-- if a method cannot be found, then we look up in array2d +-- array2d.flatten(t) makes a 1D list out of a 2D array, +-- and then List.minmax() gets the extrema. + +asserteq(T(dat:flatten():minmax()),T(0.1,1.3)) + +local f = open [[ +Time Message +1266840760 +# EE7C0600006F0D00C00F06010302054000000308010A00002B00407B00 +1266840760 closure data 0.000000 1972 1972 0 +1266840760 ++ 1266840760 EE 1 +1266840760 +# EE7C0600006F0D00C00F06010302054000000408020A00002B00407B00 +1266840764 closure data 0.000000 1972 1972 0 +1266840764 ++ 1266840764 EE 1 +1266840764 +# EE7C0600006F0D00C00F06010302054000000508030A00002B00407B00 +1266840768 duplicate? +1266840768 +# EE7C0600006F0D00C00F06010302054000000508030A00002B00407B00 +1266840768 closure data 0.000000 1972 1972 0 +]] + +-- the `convert` option provides custom converters for each specified column. +-- Here we convert the timestamps into Date objects and collect everything +-- else into one field +local Date = require 'pl.Date' + +local function date_convert (ds) + return Date(tonumber(ds)) +end + +local d = data.read(f,{convert={[1]=date_convert},last_field_collect=true}) + +asserteq(#d[1],2) +asserteq(d[2][1]:year(),2010) + +d = {{1,2,3},{10,20,30}} +out = stringio.create() +data.write(d,out,{'A','B','C'},',') +asserteq(out:value(), +[[ +A,B,C +1,2,3 +10,20,30 +]]) + +out = stringio.create() +d.fieldnames = {'A','B','C'} +data.write(d,out) + +asserteq(out:value(), +[[ +A B C +1 2 3 +10 20 30 +]]) + + +d = data.read(stringio.open 'One,Two\n1,\n,20\n',{csv=true}) +asserteq(d,{ + {1,0},{0,20}, + original_fieldnames={"One","Two"},fieldnames={"One","Two"},delim="," +}) diff --git a/Data/Libraries/Penlight/tests/test-data2.lua b/Data/Libraries/Penlight/tests/test-data2.lua new file mode 100644 index 0000000..e246ebc --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-data2.lua @@ -0,0 +1,23 @@ +local utils = require 'pl.utils' +local stringio = require 'pl.stringio' +local data = require 'pl.data' +local test = require 'pl.test' + +utils.on_error 'quit' + +stuff = [[ +Department Name,Employee ID,Project,Hours Booked +sales, 1231,overhead,4 +sales,1255,overhead,3 +engineering,1501,development,5 +engineering,1501,maintenance,3 +engineering,1433,maintenance,10 +]] + +t = data.read(stringio.open(stuff)) + +q = t:select 'Employee_ID,Hours_Booked where Department_Name == "engineering"' + +test.asserteq2(1501,5,q()) +test.asserteq2(1501,3,q()) +test.asserteq2(1433,10,q()) diff --git a/Data/Libraries/Penlight/tests/test-date.lua b/Data/Libraries/Penlight/tests/test-date.lua new file mode 100644 index 0000000..6a91a09 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-date.lua @@ -0,0 +1,87 @@ +local test = require 'pl.test' +local asserteq, assertmatch = test.asserteq, test.assertmatch +local dump = require 'pl.pretty'.dump +local T = require 'pl.test'.tuple + +local Date = require 'pl.Date' + +iso = Date.Format 'yyyy-mm-dd' -- ISO date +d = iso:parse '2010-04-10' +asserteq(T(d:day(),d:month(),d:year()),T(10,4,2010)) +amer = Date.Format 'mm/dd/yyyy' -- American style +s = amer:tostring(d) +dc = amer:parse(s) +asserteq(d,dc) + +d = Date() -- today +d:add { day = 1 } -- tomorrow +assert(d > Date()) + +--------- Time intervals ----- +-- new explicit Date.Interval class; also returned by Date:diff +d1 = Date.Interval(1202) +d2 = Date.Interval(1500) +asserteq(tostring(d2:diff(d1)),"4 min 58 sec ") + +-------- testing 'flexible' date parsing --------- + + +local df = Date.Format() + +function parse_date (s) + return df:parse(s) +end + +-- ISO 8601 +-- specified as UTC plus/minus offset + +function parse_utc (s) + local d = parse_date(s) + return d:toUTC() +end + +asserteq(parse_utc '2010-05-10 12:35:23Z', Date(2010,05,10,12,35,23)) +asserteq(parse_utc '2008-10-03T14:30+02', Date(2008,10,03,12,30)) +asserteq(parse_utc '2008-10-03T14:00-02:00',Date(2008,10,03,16,0)) + +---- can't do anything before 1970, which is somewhat unfortunate.... +--parse_date '20/03/59' + +asserteq(parse_date '15:30', Date {hour=15,min=30}) +asserteq(parse_date '8.05pm', Date {hour=20,min=5}) +asserteq(parse_date '28/10/02', Date {year=2002,month=10,day=28}) +asserteq(parse_date ' 5 Feb 2012 ', Date {year=2012,month=2,day=5}) +asserteq(parse_date '20 Jul ', Date {month=7,day=20}) +asserteq(parse_date '05/04/02 15:30:43', Date{year=2002,month=4,day=5,hour=15,min=30,sec=43}) +asserteq(parse_date 'march', Date {month=3}) +asserteq(parse_date '2010-05-23T0130', Date{year=2010,month=5,day=23,hour=1,min=30}) +asserteq(parse_date '2008-10-03T14:30:45', Date{year=2008,month=10,day=3,hour=14,min=30,sec=45}) + +-- allow for a comma after the month... +asserteq(parse_date '18 July, 2013 12:00:00', Date{year=2013,month=07,day=18,hour=12,min=0,sec=0}) + +-- This ISO format must result in a UTC date +local d = parse_date '2016-05-01T14:30:00Z' +asserteq(d:year(),2016) +asserteq(d:month(),5) +asserteq(d:day(),1) +asserteq(d:hour(),14) +asserteq(d:min(),30) +asserteq(d:sec(),0) + +function err (status,e) + return e +end + +assertmatch(err(parse_date('2005-10-40 01:30')),'40 is not between 1 and 31') +assertmatch(err(parse_date('14.20pm')),'14 is not between 0 and 12') + +local d = parse_date '2007-08-10' +-- '+' works like add, but can also work with intervals +local nxt = d + {month=1} +-- '-' is an alias for diff method +asserteq(tostring(nxt - d), '1 month ') + +--- Can explicitly get UTC date; these of course refer to same time +local now,utc = Date(), Date 'utc' +asserteq(tostring(now - utc),'zero') diff --git a/Data/Libraries/Penlight/tests/test-dir.lua b/Data/Libraries/Penlight/tests/test-dir.lua new file mode 100644 index 0000000..b293abc --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-dir.lua @@ -0,0 +1,201 @@ +-- This test file expects to be ran from 'run.lua' in the root Penlight directory. + +local dir = require( "pl.dir" ) +local file = require( "pl.file" ) +local path = require( "pl.path" ) +local asserteq = require( "pl.test" ).asserteq +local lfs = require("lfs") + +asserteq(dir.fnmatch("foobar", "foo*bar"), true) +asserteq(dir.fnmatch("afoobar", "foo*bar"), false) +asserteq(dir.fnmatch("foobars", "foo*bar"), false) +asserteq(dir.fnmatch("foonbar", "foo*bar"), true) +asserteq(dir.fnmatch("foo'n'bar", "foo*bar"), true) +asserteq(dir.fnmatch("foonbar", "foo?bar"), true) +asserteq(dir.fnmatch("foo'n'bar", "foo?bar"), false) +asserteq(dir.fnmatch("foo", "FOO"), path.is_windows) +asserteq(dir.fnmatch("FOO", "foo"), path.is_windows) + +local filtered = dir.filter({"foobar", "afoobar", "foobars", "foonbar"}, "foo*bar") +asserteq(filtered, {"foobar", "foonbar"}) + +local normpath = path.normpath + +local doc_files = dir.getfiles(normpath "docs/", "*.css") +asserteq(doc_files, {normpath "docs/ldoc_fixed.css"}) + +local all_doc_files = dir.getallfiles(normpath "docs/", "*.css") +asserteq(all_doc_files, {normpath "docs/ldoc_fixed.css"}) + +local test_samples = dir.getallfiles(normpath "tests/lua") +table.sort(test_samples) +asserteq(test_samples, { + normpath "tests/lua/animal.lua", + normpath "tests/lua/bar.lua", + normpath "tests/lua/foo/args.lua", + normpath "tests/lua/mod52.lua", + normpath "tests/lua/mymod.lua" +}) + +-- Test move files ----------------------------------------- + +-- Create a dummy file +local fileName = path.tmpname() .. "Xx" +file.write( fileName, string.rep( "poot ", 1000 ) ) + +local newFileName = path.tmpname() .. "Xx" +local err, msg = dir.movefile( fileName, newFileName ) + +-- Make sure the move is successful +assert( err, msg ) + +-- Check to make sure the original file is gone +asserteq( path.exists( fileName ), false ) + +-- Check to make sure the new file is there +asserteq( path.exists( newFileName ) , newFileName ) + +-- Test existence again, but explicitly check for correct casing +local files = dir.getfiles(path.dirname(newFileName)) +local found = false +for i, filename in ipairs(files) do + if filename == newFileName then + found = true + break + end +end +assert(found, "file was not found in directory, check casing: " .. newFileName) + + +-- Try to move the original file again (which should fail) +local newFileName2 = path.tmpname() +local err, msg = dir.movefile( fileName, newFileName2 ) +asserteq( err, false ) + +-- Clean up +file.delete( newFileName ) + + +-- Test copy files ----------------------------------------- + +-- Create a dummy file +local fileName = path.tmpname() +file.write( fileName, string.rep( "poot ", 1000 ) ) + +local newFileName = path.tmpname() .. "xX" +local err, msg = dir.copyfile( fileName, newFileName ) + +-- Make sure the move is successful +assert( err, msg ) + +-- Check to make sure the new file is there +asserteq( path.exists( newFileName ) , newFileName ) + +-- Test existence again, but explicitly check for correct casing +local files = dir.getfiles(path.dirname(newFileName)) +local found = false +for i, filename in ipairs(files) do + if filename == newFileName then + found = true + break + end +end +assert(found, "file was not found in directory, check casing: " .. newFileName) + + +-- Try to move a non-existant file (which should fail) +local fileName2 = 'blub' +local newFileName2 = 'snortsh' +local err, msg = dir.copyfile( fileName2, newFileName2 ) +asserteq( err, false ) + +-- Clean up the files +file.delete( fileName ) +file.delete( newFileName ) + + + +-- Test make directory ----------------------------------------- + +-- Create a dummy file +local dirName = path.tmpname() .. "xX" +local fullPath = dirName .. "/and/one/more" +if path.is_windows then + fullPath = fullPath:gsub("/", "\\") +end +local err, msg = dir.makepath(fullPath) + +-- Make sure the move is successful +assert( err, msg ) + +-- Check to make sure the new file is there +assert(path.isdir(dirName)) +assert(path.isdir(fullPath)) + +-- Test existence again, but explicitly check for correct casing +local files = dir.getdirectories(path.dirname(path.tmpname())) +local found = false +for i, filename in ipairs(files) do + if filename == dirName then + found = true + break + end +end +assert(found, "dir was not found in directory, check casing: " .. newFileName) + + +-- Try to move a non-existant file (which should fail) +local fileName2 = 'blub' +local newFileName2 = 'snortsh' +local err, msg = dir.copyfile( fileName2, newFileName2 ) +asserteq( err, false ) + +-- Clean up the files +file.delete( fileName ) +file.delete( newFileName ) + + + + +-- Test rmtree ----------------------------------------- +do + local dirName = path.tmpname() + os.remove(dirName) + assert(dir.makepath(dirName)) + assert(file.write(path.normpath(dirName .. "/file_base.txt"), "hello world")) + assert(dir.makepath(path.normpath(dirName .. "/sub1"))) + assert(file.write(path.normpath(dirName .. "/sub1/file_sub1.txt"), "hello world")) + assert(dir.makepath(path.normpath(dirName .. "/sub2"))) + assert(file.write(path.normpath(dirName .. "/sub2/file_sub2.txt"), "hello world")) + + + local linkTarget = path.tmpname() + os.remove(linkTarget) + assert(dir.makepath(linkTarget)) + local linkFile = path.normpath(linkTarget .. "/file.txt") + assert(file.write(linkFile, "hello world")) + + local linkSource = path.normpath(dirName .. "/link1") + assert(lfs.link(linkTarget, linkSource, true)) + + -- test: rmtree will not follow symlinks + local ok, err = dir.rmtree(linkSource) + asserteq(ok, false) + asserteq(err, "will not follow symlink") + + -- test: rmtree removes a tree without following symlinks in that tree + local ok, err = dir.rmtree(dirName) + asserteq(err, nil) + asserteq(ok, true) + + asserteq(path.exists(dirName), false) -- tree is gone, including symlink + assert(path.exists(linkFile), "expected linked-to file to still exist") -- symlink target file is still there + + -- cleanup + assert(dir.rmtree(linkTarget)) +end + + +-- have NO idea why forcing the return code is necessary here (Windows 7 64-bit) +os.exit(0) + diff --git a/Data/Libraries/Penlight/tests/test-func.lua b/Data/Libraries/Penlight/tests/test-func.lua new file mode 100644 index 0000000..4e43f30 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-func.lua @@ -0,0 +1,112 @@ +local utils = require 'pl.utils' +local List = require 'pl.List' +local tablex = require 'pl.tablex' +asserteq = require('pl.test').asserteq +utils.import('pl.func') + + -- _DEBUG = true + +function pprint (t) + print(pretty.write(t)) +end + +function test (e) + local v = {} + print('test',collect_values(e,v)) + if #v > 0 then pprint(v) end + local rep = repr(e) + print(rep) +end + +function teste (e,rs,ve) + local v = {} + collect_values(e,v) + if #v > 0 then asserteq(v,ve,nil,1) end + local rep = repr(e) + asserteq(rep,rs, nil, 1) +end + +teste(_1+_2('hello'),'_1 + _2(_C1)',{"hello"}) +teste(_1:method(),'_1[_C1](_1)',{"method"}) +teste(Not(_1),'not _1') + +asserteq(instantiate(_1+_2)(10,20),30) +asserteq(instantiate(_1+20)(10),30) +asserteq(instantiate(Or(Not(_1),_2))(true,true),true) + +teste(_1() + _2() + _3(),'_1() + _2() + _3()',30) +asserteq(I(_1+_2)(10,20),30) + +teste(_1() - -_2() % _3(), '_1() - - _2() % _3()') +teste((_1() - -_2()) % _3(), '(_1() - - _2()) % _3()') + +teste(_1() - _2() + _3(), '_1() - _2() + _3()') +teste(_1() - (_2() + _3()), '_1() - (_2() + _3())') +teste((_1() - _2()) + _3(), '_1() - _2() + _3()') + +teste(_1() .. _2() .. _3(), '_1() .. _2() .. _3()') +teste(_1() .. (_2() .. _3()), '_1() .. _2() .. _3()') +teste((_1() .. _2()) .. _3(), '(_1() .. _2()) .. _3()') + +teste(_1() ^ _2() ^ _3(), '_1() ^ _2() ^ _3()') +teste(_1() ^ (_2() ^ _3()), '_1() ^ _2() ^ _3()') +teste((_1() ^ _2()) ^ _3(), '(_1() ^ _2()) ^ _3()') + +teste(-_1() * _2(), '- _1() * _2()') +teste(-(_1() * _2()), '- (_1() * _2())') +teste((-_1()) * _2(), '- _1() * _2()') +teste(-_1() ^ _2(), '- _1() ^ _2()') +teste(-(_1() ^ _2()), '- _1() ^ _2()') +teste((-_1()) ^ _2(), '(- _1()) ^ _2()') + +asserteq(instantiate(_1+_2)(10,20),30) + +ls = List {1,2,3,4} +res = ls:map(10*_1 - 1) +asserteq(res,List {9,19,29,39}) + +-- note that relational operators can't be overloaded for _different_ types +ls = List {10,20,30,40} +asserteq(ls:filter(Gt(_1,20)),List {30,40}) + + +local map,map2 = tablex.map,tablex.map2 + +--~ test(Len(_1)) + +-- methods can be applied to all items in a table with map +asserteq (map(_1:sub(1,2),{'one','four'}),{'on','fo'}) + +--~ -- or you can do this using List:map +asserteq( List({'one','four'}):map(_1:sub(1,2)), List{'on','fo'}) + +--~ -- note that Len can't be represented generally by #, since this can only be overriden by userdata +asserteq( map(Len(_1),{'one','four'}), {3,4} ) + +--~ -- simularly, 'and' and 'or' are not really operators in Lua, so we need a function notation for them +asserteq (map2(Or(_1,_2),{false,'b'},{'.lua',false}),{'.lua','b'}) + +--~ -- binary operators: + - * / % ^ .. +asserteq (map2(_1.._2,{'a','b'},{'.lua','.c'}),{'a.lua','b.c'}) + +t1 = {alice=23,fred=34} +t2 = {bob=25,fred=34} + +intersection = bind(tablex.merge,_1,_2,false) + +asserteq(intersection(t1,t2),{fred=34}) + +union = bind(tablex.merge,_1,_2,true) + +asserteq(union(t1,t2),{bob=25,fred=34,alice=23}) + +asserteq(repr(_1+_2),"_1 + _2") + + + + + + + + + diff --git a/Data/Libraries/Penlight/tests/test-import_into.lua b/Data/Libraries/Penlight/tests/test-import_into.lua new file mode 100644 index 0000000..df65e8a --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-import_into.lua @@ -0,0 +1,39 @@ +local test = require 'pl.test' +local utils = require 'pl.utils' + +require 'pl.app'.require_here 'lua' + +if not utils.lua51 then + --- look at lua/mod52.lua + local m = require 'mod52' + test.asserteq(m.answer(),'{"10","20","30"}') + assert(m.utils) -- !! implementation is leaky! + + -- that's a bugger. However, if 'pl.import_into' is passed true, + -- then the returned module will _only_ contain the newly defined functions + -- So reload after setting the global STRICT + package.loaded.mod52 = nil + STRICT = true + m = require 'mod52' + assert (m.answer) -- as before + assert (not m.utils) -- cool! No underwear showing +end + +local pl = require 'pl.import_into' () + +assert(pl.utils) +assert(pl.tablex) +assert(pl.data) +assert(not _G.utils) +assert(not _G.tablex) +assert(not _G.data) + +require 'pl.import_into'(_G) +assert(_G.utils) +assert(_G.tablex) +assert(_G.data) + +require 'pl.import_into'(_G) +assert(_G.utils) +assert(_G.tablex) +assert(_G.data) diff --git a/Data/Libraries/Penlight/tests/test-lapp.lua b/Data/Libraries/Penlight/tests/test-lapp.lua new file mode 100644 index 0000000..6c0c7f9 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-lapp.lua @@ -0,0 +1,202 @@ + +local test = require 'pl.test' +local lapp = require 'pl.lapp' +local utils = require 'pl.utils' +local tablex = require 'pl.tablex' +local path = require 'pl.path' +local normpath = path.normpath + +local k = 1 +function check (spec,args,match) + local args = lapp(spec,args) + for k,v in pairs(args) do + if type(v) == 'userdata' then args[k]:close(); args[k] = '<file>' end + end + test.asserteq(args,match,nil,1) +end + +-- force Lapp to throw an error, rather than just calling os.exit() +lapp.show_usage_error = 'throw' + +function check_error(spec,args,msg) + arg = args + local ok,err = pcall(lapp,spec) + test.assertmatch(err,msg) +end + +local parmtest = [[ +Testing 'array' parameter handling + -o,--output... (string) + -v... +]] + + +check (parmtest,{'-o','one'},{output={'one'},v={false}}) +check (parmtest,{'-o','one','-v'},{output={'one'},v={true}}) +check (parmtest,{'-o','one','-vv'},{output={'one'},v={true,true}}) +check (parmtest,{'-o','one','-o','two'},{output={'one','two'},v={false}}) + + +local simple = [[ +Various flags and option types + -p A simple optional flag, defaults to false + -q,--quiet A simple flag with long name + -o (string) A required option with argument + <input> (default stdin) Optional input file parameter... +]] + +check(simple, + {'-o','in'}, + {quiet=false,p=false,o='in',input='<file>'}) + +---- value of flag may be separated by '=' or ':' +check(simple, + {'-o=in'}, + {quiet=false,p=false,o='in',input='<file>'}) + +check(simple, + {'-o:in'}, + {quiet=false,p=false,o='in',input='<file>'}) + +-- Check lapp.callback. +local calls = {} +function lapp.callback(param, arg) + table.insert(calls, {param, arg}) +end +check(simple, + {'-o','help','-q',normpath 'tests/test-lapp.lua'}, + {quiet=true,p=false,o='help',input='<file>',input_name=normpath 'tests/test-lapp.lua'}) +test.asserteq(calls, { + {'o', 'help'}, + {'quiet', '-q'}, + {'input', normpath 'tests/test-lapp.lua'} +}) +lapp.callback = nil + +local longs = [[ + --open (string) +]] + +check(longs,{'--open','folder'},{open='folder'}) + +local long_file = [[ + --open (default stdin) +]] + +check(long_file,{'--open',normpath 'tests/test-lapp.lua'},{open='<file>',open_name=normpath 'tests/test-lapp.lua'}) + +local extras1 = [[ + <files...> (string) A bunch of files +]] + +check(extras1,{'one','two'},{files={'one','two'}}) + +-- any extra parameters go into the array part of the result +local extras2 = [[ + <file> (string) A file +]] + +check(extras2,{'one','two'},{file='one','two'}) + +local extended = [[ + --foo (string default 1) + -s,--speed (slow|medium|fast default medium) + -n (1..10 default 1) + -p print + -v verbose +]] + + +check(extended,{},{foo='1',speed='medium',n=1,p=false,v=false}) +check(extended,{'-pv'},{foo='1',speed='medium',n=1,p=true,v=true}) +check(extended,{'--foo','2','-s','fast'},{foo='2',speed='fast',n=1,p=false,v=false}) +check(extended,{'--foo=2','-s=fast','-n2'},{foo='2',speed='fast',n=2,p=false,v=false}) + +check_error(extended,{'--speed','massive'},"value 'massive' not in slow|medium|fast") + +check_error(extended,{'-n','x'},"unable to convert to number: x") + +check_error(extended,{'-n','12'},"n out of range") + +local with_dashes = [[ + --first-dash dash + --second-dash dash also +]] + +check(with_dashes,{'--first-dash'},{first_dash=true,second_dash=false}) + +-- optional parameters don't have to be set +local optional = [[ + -p (optional string) +]] + +check(optional,{'-p', 'test'},{p='test'}) +check(optional,{},{}) + +-- boolean flags may have a true default... +local false_flag = [[ + -g group results + -f (default true) force result +]] + +check (false_flag,{},{f=true,g=false}) + +check (false_flag,{'-g','-f'},{f=false,g=true}) + +-- '--' indicates end of parameter parsing +check (false_flag,{'-g','--'},{f=true,g=true}) +check (false_flag,{'-g','--','-a','frodo'},{f=true,g=true; '-a','frodo'}) + +local addtype = [[ + -l (intlist) List of items +]] + +-- defining a custom type +lapp.add_type('intlist', + function(x) + return tablex.imap(tonumber, utils.split(x, '%s*,%s*')) + end, + function(x) + for _,v in ipairs(x) do + lapp.assert(math.ceil(v) == v,'not an integer!') + end + end) + +check(addtype,{'-l', '1,2,3'},{l={1,2,3}}) + +check_error(addtype,{'-l', '1.5,2,3'},"not an integer!") + +-- short flags may be immediately followed by their value +-- (previously only true for numerical values) +local short_args = [[ + -n (default 10) + -I,--include (string) +]] + +check(short_args,{'-Ifrodo','-n5'},{include='frodo',n=5}) +check(short_args,{'-I/usr/local/lua/5.1'},{include='/usr/local/lua/5.1',n=10}) + +-- ok, introducing _slack_ mode ;) +-- 'short' flags may have multiple characters! (this is otherwise an error) +-- Note that in _any case_ flags may contain hyphens, but these are turned +-- into underscores for convenience. +lapp.slack = true +local spec = [[ +Does some calculations + -vs,--video-set (string) Use the German road sign dataset + -w,--width (default 256) Width of the video + -h,--height (default 144) Height of the video + -t,--time (default 10) Seconds of video to process + -sk,--seek (default 0) Seek number of seconds + -dbg Debug! +]] + +test.asserteq(lapp(spec,{'-vs',200,'-sk',1}),{ + video_set = 200, + time = 10, + height = 144, + seek = 1, + dbg = false, + width = 256 +}) + diff --git a/Data/Libraries/Penlight/tests/test-lexer.lua b/Data/Libraries/Penlight/tests/test-lexer.lua new file mode 100644 index 0000000..1c09b85 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-lexer.lua @@ -0,0 +1,146 @@ +local asserteq = require('pl.test').asserteq +local lexer = require 'pl.lexer' +local seq = require 'pl.seq' +local List = require('pl.List') +local open = require('pl.stringio').open +local copy2 = seq.copy2 + +local function test_scan(str, filter, options, expected_tokens, lang) + local matches + if lang then + matches, filter = filter, options + else + lang = 'scan' + end + + asserteq(copy2(lexer[lang](str, matches, filter, options)), expected_tokens) + if lang == 'scan' then + asserteq(copy2(lexer[lang](open(str), matches, filter, options)), expected_tokens) + end +end + +local s = '20 = hello' +test_scan(s, {space=false}, {number=false}, { + {'number', '20'}, {'space', ' '}, {'=', '='}, {'space', ' '}, {'iden', 'hello'} +}) +test_scan(s, {space=true}, {number=true}, { + {'number', 20}, {'=', '='}, {'iden', 'hello'} +}) +s = [[ 'help' "help" "dolly you're fine" "a \"quote\" here"]] +test_scan(s, nil, nil, { + {'string', 'help'}, {'string', 'help'}, + {'string', "dolly you're fine"}, {'string', 'a \\\"quote\\\" here'} -- Escapes are preserved literally. +}) +test_scan([[\abc\]], nil, nil, { + {'\\', '\\'}, {'iden', 'abc'}, {'\\', '\\'} +}) +test_scan([["" ""]], nil, nil, { + {'string', ''}, {'string', ''} +}) +test_scan([["abc" "def\\"]], nil, nil, { + {'string', 'abc'}, {'string', 'def\\\\'} +}) +test_scan([["abc\\" "def"]], nil, nil, { + {'string', 'abc\\\\'}, {'string', 'def'} +}) +test_scan([["abc\\\" "]], nil, nil, { + {'string', 'abc\\\\\\" '} +}) + +local function test_roundtrip(str) + test_scan(str, {}, {string=false}, {{'string', str}}) +end + +test_roundtrip [["hello\\"]] +test_roundtrip [["hello\"dolly"]] +test_roundtrip [['hello\'dolly']] +test_roundtrip [['']] +test_roundtrip [[""]] + +test_scan('test(20 and a > b)', nil, nil, { + {'iden', 'test'}, {'(', '('}, {'number', 20}, {'keyword', 'and'}, + {'iden', 'a'}, {'>', '>'}, {'iden', 'b'}, {')', ')'} +}, 'lua') +test_scan('10+2.3', nil, nil, { + {'number', 10}, {'+', '+'}, {'number', 2.3} +}, 'lua') + +local txt = [==[ +-- comment +--[[ +block +comment +]][[ +hello dammit +]][[hello]] +]==] + +test_scan(txt, {}, nil, { + {'comment', '-- comment\n'}, + {'comment', '--[[\nblock\ncomment\n]]'}, + {'string', 'hello dammit\n'}, + {'string', 'hello'}, + {'space', '\n'} +}, 'lua') + +local lines = [[ +for k,v in pairs(t) do + if type(k) == 'number' then + print(v) -- array-like case + else + print(k,v) + end -- if +end +]] + +local ls = List() +for tp,val in lexer.lua(lines,{space=true,comments=true}) do + assert(tp ~= 'space' and tp ~= 'comment') + if tp == 'keyword' then ls:append(val) end +end +asserteq(ls,List{'for','in','do','if','then','else','end','end'}) + +txt = [[ +// comment +/* a long +set of words */ // more +]] + +test_scan(txt, {}, nil, { + {'comment', '// comment\n'}, + {'comment', '/* a long\nset of words */'}, + {'space', ' '}, + {'comment', '// more\n'} +}, 'cpp') + +test_scan([['' "" " \\" '\'' "'"]], nil, nil, { + {'char', ''}, -- Char literals with no or more than one characters are not a lexing error. + {'string', ''}, + {'string', ' \\\\'}, + {'char', "\\'"}, + {'string', "'"} +}, 'cpp') + +local iter = lexer.lua([[ +foo +bar +]]) + +asserteq(lexer.lineno(iter), 0) +iter() +asserteq(lexer.lineno(iter), 1) +asserteq(lexer.lineno(iter), 1) +iter() +asserteq(lexer.lineno(iter), 2) +iter() +asserteq(lexer.lineno(iter), 3) +iter() +iter() +asserteq(lexer.lineno(iter), 3) + +do -- numbers without leading zero; ".123" + local s = 'hello = +.234' + test_scan(s, {space=true}, {number=true}, { + {'iden', 'hello'}, {'=', '='}, {'number', .234} + }) +end diff --git a/Data/Libraries/Penlight/tests/test-list.lua b/Data/Libraries/Penlight/tests/test-list.lua new file mode 100644 index 0000000..4e290d6 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-list.lua @@ -0,0 +1,68 @@ +local List = require 'pl.List' +local class = require 'pl.class' +local test = require 'pl.test' +local asserteq, T = test.asserteq, test.tuple + +-- note that a _plain table_ is made directly into a list +local t = {10,20,30} +local ls = List(t) +asserteq(t,ls) + +asserteq(List({}):reverse(), {}) +asserteq(List({1}):reverse(), {1}) +asserteq(List({1,2}):reverse(), {2,1}) +asserteq(List({1,2,3}):reverse(), {3,2,1}) +asserteq(List({1,2,3,4}):reverse(), {4,3,2,1}) + +-- you may derive classes from pl.List, and the result is covariant. +-- That is, slice() etc will return a list of the derived type, not List. + +local NA = class(List) + +local function mapm(a1,op,a2) + local M = type(a2)=='table' and List.map2 or List.map + return M(a1,op,a2) +end + +--- elementwise arithmetric operations +function NA.__unm(a) return a:map '|X|-X' end +function NA.__pow(a,s) return a:map '|X,Y|X^Y' end +function NA.__add(a1,a2) return mapm(a1,'|X,Y|X+Y',a2) end +function NA.__sub(a1,a2) return mapm(a1,'|X,Y|X-Y',a2) end +function NA.__div(a1,a2) return mapm(a1,'|X,Y|X/Y',a2) end +function NA.__mul(a1,a2) return mapm(a2,'|X,Y|X*Y',a1) end + +function NA:minmax () + local min,max = math.huge,-math.huge + for i = 1,#self do + local val = self[i] + if val > max then max = val end + if val < min then min = val end + end + return min,max +end + +function NA:sum () + local res = 0 + for i = 1,#self do + res = res + self[i] + end + return res +end + +function NA:normalize () + return self:transform('|X,Y|X/Y',self:sum()) +end + +n1 = NA{10,20,30} +n2 = NA{1,2,3} +ns = n1 + 2*n2 + +asserteq(List:class_of(ns),true) +asserteq(NA:class_of(ns),true) +asserteq(ns:is_a(NA),true) +asserteq(ns,{12,24,36}) +min,max = ns:slice(1,2):minmax() +asserteq(T(min,max),T(12,24)) + +asserteq(n1:normalize():sum(),1,1e-8) diff --git a/Data/Libraries/Penlight/tests/test-list2.lua b/Data/Libraries/Penlight/tests/test-list2.lua new file mode 100644 index 0000000..174b1c7 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-list2.lua @@ -0,0 +1,60 @@ +local List = require 'pl.List' +local asserteq = require 'pl.test' . asserteq + +local s = List{1,2,3,4,5} + +-- test using: lua pylist.lua +local lst = List() +lst:append(20) +lst:extend{30,40,50} +lst:put(10) +asserteq (lst,List{10,20,30,40,50}) +asserteq (lst:len(),5) +lst:insert(3,11) +lst:remove_value(40) +asserteq (lst,List{10,20,11,30,50}) +asserteq (lst:contains(11),true) +asserteq (lst:contains(40),false) +local _ = lst:pop() +asserteq( lst:index(30),4 ) +asserteq( lst:count(10),1 ) +lst:sort() +lst:reverse() +asserteq (lst , List{30,20,11,10}) +asserteq (lst[#lst] , 10) +asserteq (lst[#lst-2] , 20) +asserteq (tostring(lst) , '{30,20,11,10}') + +lst = List {10,20,30,40,50} +asserteq (lst:slice(2),{20,30,40,50}) +asserteq (lst:slice(-2),{40,50}) +asserteq (lst:slice(nil,3),{10,20,30}) +asserteq (lst:slice(2,4),{20,30,40}) +asserteq (lst:slice(-4,-2),{20,30,40}) + +lst = List.range(0,9) +local seq = List{0,1,2,3,4,5,6,7,8,9} +asserteq(List.range(4),{1,2,3,4}) +asserteq(List.range(0,8,2),{0,2,4,6,8}) +asserteq(List.range(0,1,0.2),{0,0.2,0.4,0.6,0.8,1},1e-9) +asserteq(lst, seq) +asserteq(lst:reduce '+', 45) + +local part = seq:partition(function(v) return v % 2 end) +asserteq (part[0], List{0,2,4,6,8}) +asserteq (part[1], List{1,3,5,7,9}) + +asserteq (List('abcd'),List{'a','b','c','d'}) +local caps = List() +List('abcd'):foreach(function(v) caps:append(v:upper()) end) +asserteq (caps,List{'A','B','C','D'}) +local ls = List{10,20,30,40} +ls:slice_assign(2,3,{21,31}) +asserteq (ls , List{10,21,31,40}) +asserteq (ls:remove(2), List{10,31,40}) +asserteq (ls:clear(), List{}) +asserteq (ls:len(), 0) + +s = 'here the dog is just a dog' +assert (List.split(s) == List{'here', 'the', 'dog', 'is', 'just', 'a', 'dog'}) +assert (List.split('foo;bar;baz', ';') == List{'foo', 'bar', 'baz'}) diff --git a/Data/Libraries/Penlight/tests/test-map.lua b/Data/Libraries/Penlight/tests/test-map.lua new file mode 100644 index 0000000..1c0bee6 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-map.lua @@ -0,0 +1,134 @@ +-- testing Map functionality + +local test = require 'pl.test' +local Map = require 'pl.Map' +local tablex = require 'pl.tablex' +local Set = require 'pl.Set' +local utils = require 'pl.utils' + +local asserteq = test.asserteq +local cmp = tablex.compare_no_order + + + +-- construction, plain +local m = Map{alpha=1,beta=2,gamma=3} + +assert(cmp( + m:values(), + {1, 2, 3} +)) + +assert(cmp( + m:keys(), + {'alpha', 'beta', 'gamma'} +)) + +asserteq( + m:items(), + { + {'alpha', 1}, + {'beta', 2}, + {'gamma', 3}, + } +) + +asserteq (m:getvalues {'alpha','gamma'}, {1,3}) + + + +-- construction, from a set +local s = Set{'red','orange','green','blue'} +m = Map(s) + +asserteq( + m:items(), + { + {'blue', true}, + {'green', true}, + {'orange', true}, + {'red', true}, + } +) + + +-- iter() +m = Map{alpha=1,beta=2,gamma=3} +local t = {alpha=1,beta=2,gamma=3} +for k,v in m:iter() do + asserteq(v, t[k]) + t[k] = nil +end +assert(next(t) == nil, "expected the table to be empty by now") + + + +-- setdefault() +m = Map{alpha=1,beta=2,gamma=3} +local v = m:setdefault("charlie", 4) +asserteq(v, 4) +v = m:setdefault("alpha", 10) +asserteq(v, 1) +asserteq( + m:items(), + { + {'alpha', 1}, + {'beta', 2}, + {'charlie', 4}, + {'gamma', 3}, + } +) +v = m:set("alpha", false) +v = m:setdefault("alpha", true) -- falsy value should not be altered +asserteq(false, m:get("alpha")) + + + +-- len() +m = Map{alpha=1,beta=2,gamma=3} +asserteq(3, m:len()) +m = Map{} +asserteq(0, m:len()) +m:set("charlie", 4) +asserteq(1, m:len()) + + + +-- set() & get() +m = Map{} +m:set("charlie", 4) +asserteq(4, m:get("charlie")) +m:set("charlie", 5) +asserteq(5, m:get("charlie")) +m:set("charlie", nil) +asserteq(nil, m:get("charlie")) + + + +-- getvalues() +m = Map{alpha=1,beta=2,gamma=3} +local x = m:getvalues{"gamma", "beta"} +asserteq({3, 2}, x) + + + +-- __eq() -- equality +local m1 = Map{alpha=1,beta=2,gamma=3} +local m2 = Map{alpha=1,beta=2,gamma=3} +assert(m1 == m2) +m1 = Map() +m2 = Map() +assert(m1 == m2) + + + +-- __tostring() +m = Map() +asserteq("{}", tostring(m)) +m = Map{alpha=1} +asserteq("{alpha=1}", tostring(m)) +m = Map{alpha=1,beta=2} +assert(({ -- test 2 versions, since we cannot rely on order + ["{alpha=1,beta=2}"] = true, + ["{beta=2,alpha=1}"] = true, + })[tostring(m)]) diff --git a/Data/Libraries/Penlight/tests/test-orderedmap.lua b/Data/Libraries/Penlight/tests/test-orderedmap.lua new file mode 100644 index 0000000..1d0fc67 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-orderedmap.lua @@ -0,0 +1,98 @@ +local List = require 'pl.List' + +local asserteq = require 'pl.test' . asserteq +local asserteq2 = require 'pl.test' . asserteq2 +local OrderedMap = require 'pl.OrderedMap' + + +m = OrderedMap() +m:set('one',1) +m:set('two',2) +m:set('three',3) + +asserteq(m:values(),List{1,2,3}) + +-- usually exercized like this: +--for k,v in m:iter() do print(k,v) end + +local fn = m:iter() +asserteq2 ('one',1,fn()) +asserteq2 ('two',2,fn()) +asserteq2 ('three',3,fn()) + +-- Keys overriding methods can be used. +m:set('set', 4) +asserteq(m:values(),List{1,2,3,4}) + +local o1 = OrderedMap {{z=2},{beta=1},{name='fred'}} +asserteq(tostring(o1),'{z=2,beta=1,name="fred"}') + +-- order of keys is not preserved here! +local o2 = OrderedMap {z=4,beta=1.1,name='alice',extra='dolly'} + +o1:update(o2) +asserteq(tostring(o1),'{z=4,beta=1.1,name="alice",extra="dolly"}') + +o1:set('beta',nil) +asserteq(o1,OrderedMap{{z=4},{name='alice'},{extra='dolly'}}) + +local o3 = OrderedMap() +o3:set('dog',10) +o3:set('cat',20) +o3:set('mouse',30) + +asserteq(o3:keys(),{'dog','cat','mouse'}) + +o3:set('dog',nil) + +asserteq(o3:keys(),{'cat','mouse'}) + +-- Vadim found a problem when clearing a key which did not exist already. +-- The keys list would then contain the key, although the map would not +o3:set('lizard',nil) + +asserteq(o3:keys(),{'cat','mouse'}) +asserteq(o3:values(), {20,30}) +asserteq(tostring(o3),'{cat=20,mouse=30}') + +-- copy constructor +local o4 = OrderedMap(o3) + +asserteq(o4,o3) + +-- constructor throws an error if the argument is bad +-- (errors same as OrderedMap:update) +asserteq(false,pcall(function() + m = OrderedMap('string') +end)) + +---- changing order of key/value pairs ---- + +o3 = OrderedMap{{cat=20},{mouse=30}} + +o3:insert(1,'bird',5) -- adds key/value before specified position +o3:insert(1,'mouse') -- moves key keeping old value +asserteq(o3:keys(),{'mouse','bird','cat'}) +asserteq(tostring(o3),'{mouse=30,bird=5,cat=20}') +o3:insert(2,'cat',21) -- moves key and sets new value +asserteq(tostring(o3),'{mouse=30,cat=21,bird=5}') +-- if you don't specify a value for an unknown key, nothing happens to the map +o3:insert(3,'alligator') +asserteq(tostring(o3),'{mouse=30,cat=21,bird=5}') + +---- short-cut notation + +local o5 = OrderedMap() +o5.alpha = 1 +o5.beta = 2 +o5.gamma = 3 + +asserteq(o5,OrderedMap{{alpha=1},{beta=2},{gamma=3}}) + +o5.alpha = 10 +o5.beta = 20 +o5.gamma = 30 +o5.delta = 40 +o5.checked = false + +asserteq(o5,OrderedMap{{alpha=10},{beta=20},{gamma=30},{delta=40},{checked=false}}) diff --git a/Data/Libraries/Penlight/tests/test-path.lua b/Data/Libraries/Penlight/tests/test-path.lua new file mode 100644 index 0000000..3cdee68 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-path.lua @@ -0,0 +1,198 @@ +local path = require 'pl.path' +asserteq = require 'pl.test'.asserteq + +function quote(s) + return '"'..s..'"' +end + +function print2(s1,s2) + print(quote(s1),quote(s2)) +end + +function slash (p) + return (p:gsub('\\','/')) +end + +-- path.currentdir +do + local cp = path.currentdir() + path.chdir("docs") + asserteq(path.currentdir(), cp .. path.sep .. "docs") + path.chdir("..") + asserteq(path.currentdir(), cp) +end + +-- path.isdir +asserteq( path.isdir( "docs" ), true ) +asserteq( path.isdir( "docs/index.html" ), false ) + +-- path.isfile +asserteq( path.isfile( "docs" ), false ) +asserteq( path.isfile( "docs/index.html" ), true ) + +-- path.exists +asserteq( path.exists( "docs"), "docs") +asserteq( path.exists( "docs/index.html"), "docs/index.html") + + +do -- path.splitpath & path.splitext + function testpath(pth,p1,p2,p3) + local dir,rest = path.splitpath(pth) + local name,ext = path.splitext(rest) + asserteq(dir,p1) + asserteq(name,p2) + asserteq(ext,p3) + end + + testpath ([[/bonzo/dog_stuff/cat.txt]],[[/bonzo/dog_stuff]],'cat','.txt') + testpath ([[/bonzo/dog/cat/fred.stuff]],'/bonzo/dog/cat','fred','.stuff') + testpath ([[../../alice/jones]],'../../alice','jones','') + testpath ([[alice]],'','alice','') + testpath ([[/path-to/dog/]],[[/path-to/dog]],'','') + + asserteq({path.splitpath("some/dir/myfile.txt")}, {"some/dir", "myfile.txt"}) + asserteq({path.splitpath("some/dir/")}, {"some/dir", ""}) + asserteq({path.splitpath("some_dir")}, {"", "some_dir"}) + + asserteq({path.splitext("/bonzo/dog_stuff/cat.txt")}, {"/bonzo/dog_stuff/cat", ".txt"}) + asserteq({path.splitext("cat.txt")}, {"cat", ".txt"}) + asserteq({path.splitext("cat")}, {"cat", ""}) + asserteq({path.splitext(".txt")}, {"", ".txt"}) + asserteq({path.splitext("")}, {"", ""}) +end + + +-- TODO: path.abspath + +-- TODO: path.dirname + +-- TODO: path.basename + +-- TODO: path.extension + + +do -- path.isabs + asserteq(path.isabs("/hello/path"), true) + asserteq(path.isabs("hello/path"), false) + asserteq(path.isabs("./hello/path"), false) + asserteq(path.isabs("../hello/path"), false) + if path.is_windows then + asserteq(path.isabs("c:/"), true) + asserteq(path.isabs("c:/hello/path"), true) + asserteq(path.isabs("c:"), false) + asserteq(path.isabs("c:hello/path"), false) + asserteq(path.isabs("c:./hello/path"), false) + asserteq(path.isabs("c:../hello/path"), false) + end +end + + +do -- path.join + assert(path.join("somepath",".") == "somepath"..path.sep..".") + assert(path.join(".","readme.txt") == "."..path.sep.."readme.txt") + assert(path.join("/a_dir", "abs_path/") == "/a_dir"..path.sep.."abs_path/") + assert(path.join("a_dir", "/abs_path/") == "/abs_path/") + assert(path.join("a_dir", "/abs_path/", "/abs_path2/") == "/abs_path2/") + assert(path.join("a_dir", "/abs_path/", "not_abs_path2/") == "/abs_path/not_abs_path2/") + assert(path.join("a_dir", "/abs_path/", "not_abs_path2/", "/abs_path3/", "not_abs_path4/") == "/abs_path3/not_abs_path4/") + assert(path.join("first","second","third") == "first"..path.sep.."second"..path.sep.."third") + assert(path.join("first","second","") == "first"..path.sep.."second"..path.sep) + assert(path.join("first","","third") == "first"..path.sep.."third") + assert(path.join("","second","third") == "second"..path.sep.."third") + assert(path.join("","") == "") +end + + +do -- path.normcase + if path.iswindows then + asserteq('c:\\hello\\world', 'c:\\hello\\world') + asserteq('C:\\Hello\\wORLD', 'c:\\hello\\world') + asserteq('c:/hello/world', 'c:\\hello\\world') + else + asserteq('/Hello/wORLD', '/Hello/wORLD') + end +end + + +do -- path.normpath + local norm = path.normpath + local p = norm '/a/b' + + asserteq(norm '/a/fred/../b',p) + asserteq(norm '/a//b',p) + + function testnorm(p1,p2) + asserteq(norm(p1):gsub('\\','/'), p2) + end + + testnorm('a/b/..','a') + testnorm('a/b/../..','.') + testnorm('a/b/../c/../../d','d') + testnorm('a/.','a') + testnorm('a/./','a') + testnorm('a/b/.././..','.') + testnorm('../../a/b','../../a/b') + testnorm('../../a/b/../../','../..') + testnorm('../../a/b/../c','../../a/c') + testnorm('./../../a/b/../c','../../a/c') + testnorm('a/..b', 'a/..b') + testnorm('./a', 'a') + testnorm('a/.', 'a') + testnorm('a/', 'a') + testnorm('/a', '/a') + testnorm('', ".") + + if path.is_windows then + testnorm('C://a', 'C:/a') + testnorm('C:/../a', 'C:/../a') + asserteq(norm [[\a\.\b]], p) + -- UNC paths + asserteq(norm [[\\bonzo\..\dog]], [[\\dog]]) + asserteq(norm [[\\?\c:\bonzo\dog\.\]], [[\\?\c:\bonzo\dog]]) + else + testnorm('//a', '//a') + testnorm('///a', '/a') + end + + asserteq(norm '1/2/../3/4/../5',norm '1/3/5') + asserteq(norm '1/hello/../3/hello/../HELLO',norm '1/3/HELLO') +end + + +do -- path.relpath + local testpath = '/a/B/c' + + function try (p,r) + asserteq(slash(path.relpath(p,testpath)),r) + end + + try('/a/B/c/one.lua','one.lua') + try('/a/B/c/bonZO/two.lua','bonZO/two.lua') + try('/a/B/three.lua','../three.lua') + try('/a/four.lua','../../four.lua') + try('one.lua','one.lua') + try('../two.lua','../two.lua') +end + + +-- TODO: path.expanduser + +-- TODO: path.tmpname + + +do -- path.common_prefix + asserteq(slash(path.common_prefix("../anything","../anything/goes")),"../anything") + asserteq(slash(path.common_prefix("../anything/goes","../anything")),"../anything") + asserteq(slash(path.common_prefix("../anything/goes","../anything/goes")),"../anything") + asserteq(slash(path.common_prefix("../anything/","../anything/")),"../anything") + asserteq(slash(path.common_prefix("../anything","../anything")),"..") + asserteq(slash(path.common_prefix("/hello/world","/hello/world/filename.doc")),"/hello/world") + asserteq(slash(path.common_prefix("/hello/filename.doc","/hello/filename.doc")),"/hello") + if path.is_windows then + asserteq(path.common_prefix("c:\\hey\\there","c:\\hey"),"c:\\hey") + asserteq(path.common_prefix("c:/HEy/there","c:/hEy"),"c:\\hEy") -- normalized separators, original casing + end +end + + +-- TODO: path.package_path diff --git a/Data/Libraries/Penlight/tests/test-pretty.lua b/Data/Libraries/Penlight/tests/test-pretty.lua new file mode 100644 index 0000000..99058e3 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-pretty.lua @@ -0,0 +1,117 @@ +local pretty = require 'pl.pretty' +local utils = require 'pl.utils' +local test = require 'pl.test' +local asserteq, assertmatch = test.asserteq, test.assertmatch + +t1 = { + 'one','two','three',{1,2,3}, + alpha=1,beta=2,gamma=3,['&']=true,[0]=false, + _fred = {true,true}, + s = [[ +hello dolly +you're so fine +]] +} + +s = pretty.write(t1) --,' ',true) +t2,err = pretty.read(s) +if err then return print(err) end +asserteq(t1,t2) + +res,err = pretty.read [[ + { + ['function'] = true, + ['do'] = true, + } +]] +assert(res) + +res,err = pretty.read [[ + { + ['function'] = true, + ['do'] = "no function here...", + } +]] +assert(res) + +res,err = pretty.read [[ + { + ['function'] = true, + ['do'] = function() return end + } +]] +assertmatch(err,'cannot have functions in table definition') + +res,err = pretty.load([[ +-- comments are ok +a = 2 +bonzo = 'dog' +t = {1,2,3} +]]) + +asserteq(res,{a=2,bonzo='dog',t={1,2,3}}) + +--- another potential problem is string functions called implicitly as methods-- +res,err = pretty.read [[ +{s = ('woo'):gsub('w','wwwwww'):gsub('w','wwwwww')} +]] + +assertmatch(err,(_VERSION ~= "Lua 5.2") and 'attempt to index a string value' or "attempt to index constant 'woo'") + +---- pretty.load has a _paranoid_ option +res,err = pretty.load([[ +k = 0 +for i = 1,1e12 do k = k + 1 end +]],{},true) + +assertmatch(err,'looping not allowed') + +-- Check to make sure that no spaces exist when write is told not to +local tbl = { "a", 2, "c", false, 23, 453, "poot", 34 } +asserteq( pretty.write( tbl, "" ), [[{"a",2,"c",false,23,453,"poot",34}]] ) + +-- Check that write correctly prevents cycles + +local t1,t2 = {},{} +t1[1] = t1 +asserteq( pretty.write(t1,""), [[{<cycle>}]] ) +t1[1],t1[2],t2[1] = 42,t2,t1 +asserteq( pretty.write(t1,""), [[{42,{<cycle>}}]] ) + +-- Check false positives in write's cycles prevention + +t2 = {} +t1[1],t1[2] = t2,t2 +asserteq( pretty.write(t1,""), [[{{},{}}]] ) + +-- Check that write correctly print table with non number or string as keys + +t1 = { [true] = "boolean", a = "a", b = "b", [1] = 1, [0] = 0 } +asserteq( pretty.write(t1,""), [[{1,["true"]="boolean",a="a",b="b",[0]=0}]] ) + + +-- Check number formatting +asserteq(pretty.write({1/0, -1/0, 0/0, 1, 1/2}, ""), "{Inf,-Inf,NaN,1,0.5}") + +if _VERSION == "Lua 5.3" then + asserteq(pretty.write({1.0}, ""), "{1.0}") +else + asserteq(pretty.write({1.0}, ""), "{1}") +end + +do -- issue #203, item 3 + local t = {}; t[t] = 1 + pretty.write(t) -- should not crash +end + + +-- pretty.write fails if an __index metatable raises an error #257 +-- only applies to 5.3+ where iterators respect metamethods +do + local t = setmetatable({},{ + __index = function(self, key) + error("oops... couldn't find " .. tostring(key)) + end + }) + asserteq(pretty.write(t), "{\n}") +end diff --git a/Data/Libraries/Penlight/tests/test-seq.lua b/Data/Libraries/Penlight/tests/test-seq.lua new file mode 100644 index 0000000..e59a4be --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-seq.lua @@ -0,0 +1,223 @@ +local input = require 'pl.input' +local seq = require 'pl.seq' +local asserteq = require('pl.test').asserteq +local utils = require 'pl.utils' +local stringio = require 'pl.stringio' +local unpack = utils.unpack + +local L = utils.string_lambda +local S = seq.list +local C = seq.copy +local C2 = seq.copy2 + + +asserteq (seq.sum(input.numbers '10 20 30 40 50'),150) +local x,y = unpack(C(input.numbers('10 20'))) +assert (x == 10 and y == 20) + + +local test = {{1,10},{2,20},{3,30}} +asserteq(C2(ipairs{10,20,30}),test) +local res = C2(input.fields({1,2},',','1,10\n2,20\n3,30\n')) +asserteq(res,test) + +asserteq( + seq.copy(seq.filter(seq.list{10,20,5,15},seq.greater_than(10))), + {20,15} +) + +asserteq( + seq.copy(seq.filter(seq.list{10,20,5,15},seq.less_than(15))), + {10,5} +) + +asserteq( + #C(seq.filter(seq.list{10,20,5,10,15},seq.equal_to(10))), + 2 +) + +asserteq( + #seq{'green','yellow','red','blue','red'}:filter(seq.equal_to'red'):copy(), + 2 +) + +asserteq( + seq{'apple','orange','pineapple'}:filter(seq.matching'apple'):copy(), + {'apple','pineapple'} +) + +asserteq( + C(seq.sort(seq.keys{[11] = true, [17]= true, [23] = true})), + {11,17,23} +) + +asserteq( + C(seq.range(2,5)), + {2,3,4,5} +) + +asserteq(seq.reduce('-',{1,2,3,4,5}),-13) + +asserteq(seq.count(S{10,20,30,40},L'|x| x > 20'), 2) + +asserteq(C2(seq.zip({1,2,3},{10,20,30})),test) + +asserteq(C(seq.splice({10,20},{30,40})),{10,20,30,40}) + +asserteq(C(seq.map(L'#_',{'one','tw'})),{3,2}) + +--for l1,l2 in seq.last{10,20,30} do print(l1,l2) end + +asserteq( C2(seq.last{10,20,30}),{{20,10},{30,20}} ) + +asserteq( C2(seq.last{40}),{} ) + +asserteq( C2(seq.last{}),{} ) + +asserteq( + seq{10,20,30}:map(L'_+1'):copy(), + {11,21,31} +) + +asserteq( + seq {1,2,3,4,5}:reduce ('*'), 120 +) + +-- test reduce with an initial value +asserteq( + seq {1,2,3,4,5}:reduce ('+', 42), 57 +) + +-- test reduce with a short sequence +asserteq( + seq {7}:reduce ('+'), 7 +) + +asserteq( + seq {5}:reduce ('/', 40), 8 +) + +asserteq( + seq {}:reduce ('+', 42), 42 +) + +asserteq( + seq {}:reduce ('-'), nil +) + +asserteq( + seq{'one','two'}:upper():copy(), + {'ONE','TWO'} +) + +asserteq( + seq{'one','two','three'}:skip(1):copy(), + {'two','three'} +) + +-- test skipping pass sequence +asserteq( + seq{'one','two','three'}:skip(4):copy(), + {} +) + +asserteq( + seq{7,8,9,10}:take(3):copy(), + {7,8,9} +) + +asserteq( + seq{7,8,9,10}:take(6):copy(), + {7,8,9,10} +) + +asserteq( + seq{7,8,9,10}:take(0):copy(), + {} +) + +asserteq( + seq{7,8,9,10}:take(-1):copy(), + {} +) + +local l, u = 50, 100 +local rand_seq = seq(seq.random(7, l, u)) +asserteq( + #rand_seq:filter(seq.less_than(u+1)):filter(seq.greater_than(l-1)):copy(), + 7 +) + +rand_seq = seq(seq.random(7, u)) +asserteq( + #rand_seq:filter(seq.less_than(u+1)):filter(seq.greater_than(0)):copy(), + 7 +) + +rand_seq = seq(seq.random(7)) +asserteq( + #rand_seq:filter(seq.less_than(1)):filter(seq.greater_than(0)):copy(), + 7 +) + +test = {275,127,286,590,961,687,802,453,705,182} +asserteq( + C(seq.sort{seq(test):minmax()}), + {127,961} +) + +asserteq( + seq(test):take(5):enum():copy_tuples(), + {{1,275},{2,127},{3,286},{4,590},{5,961}} +) + +asserteq( + C(seq.unique(seq.list{1,2,3,2,1})), + {1,2,3} +) + +local actualstr = {} +local expectedstr = "275.00 127.00 286.00 590.00 961.00 687.00 802.00\n".. + "453.00 705.00 182.00 \n" +local function proxywrite_printall(head, ...) + table.insert(actualstr, tostring(head)) + if select('#', ...) == 0 then return true end + return proxywrite_printall(...) +end + +local iowrite = io.write +io.write = proxywrite_printall +seq(test):printall(nil,nil,'%.2f') +io.write = iowrite +asserteq( + table.concat(actualstr), + expectedstr +) + + +local f = stringio.open '1 2 3 4' + +-- seq.lines may take format specifiers if using Lua 5.2, or a 5.2-compatible +-- file object like that returned by stringio. +asserteq( + seq.lines(f,'*n'):copy(), + {1,2,3,4} +) + +-- the seq() constructor can now take an iterator which consists of two parts, +-- a function and an object - as returned e.g. by lfs.dir() + +local function my_iter(T) + local idx = 0 + return function(self) + idx = idx + 1 + return self[idx] + end, + T +end + +asserteq( + seq(my_iter{10,20,30}):copy(), + {10,20,30} +) + diff --git a/Data/Libraries/Penlight/tests/test-sip.lua b/Data/Libraries/Penlight/tests/test-sip.lua new file mode 100644 index 0000000..cf4b344 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-sip.lua @@ -0,0 +1,105 @@ +local sip = require 'pl.sip' +local tablex = require 'pl.tablex' +local test = require 'pl.test' + +local function check(pat,line,tbl) + local parms = {} + if type(pat) == 'string' then + pat = sip.compile(pat) + end + if pat(line,parms) then + test.asserteq(parms,tbl) + else -- only should happen if we're passed a nil! + assert(tbl == nil) + end +end + +local c = sip.compile('ref=$S{file}:$d{line}') +check(c,'ref=bonzo:23',{file='bonzo',line=23}) +check(c,'here we go ref=c:\\bonzo\\dog.txt:53',{file='c:\\bonzo\\dog.txt',line=53}) +check(c,'here is a line ref=xxxx:xx',nil) + +c = sip.compile('($i{x},$i{y},$i{z})') +check(c,'(10,20,30)',{x=10,y=20,z=30}) +check(c,' (+233,+99,-40) ',{x=233,y=99,z=-40}) + +local pat = '$v{name} = $q{str}' +--assert(sip.create_pattern(pat) == [[([%a_][%w_]*)%s*=%s*(["'])(.-)%2]]) +local m = sip.compile(pat) + +check(m,'a = "hello"',{name='a',str='hello'}) +check(m,"a = 'hello'",{name='a',str='hello'}) +check(m,'_fred="some text"',{name='_fred',str='some text'}) + +-- some cases broken in 0.6b release +check('$v is $v','bonzo is dog for sure',{'bonzo','dog'}) +check('$v is $','bonzo is dog for sure',{'bonzo','dog for sure'}) + +-- spaces +check('$v $d','age 23',{'age',23}) +check('$v $d','age 23',{'age',23}) +check('$v $d','age23') -- the space is 'imcompressible' +check('a b c $r', 'a bc d') +check('a b c $r', 'a b c d',{'d'}) + +-- the spaces in this pattern, however, are compressible. +check('$v = $d','age=23',{'age',23}) + +-- patterns without patterns +check('just a string', 'just a string', {}) +check('just a string', 'not that string') + +local months={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"} + +local function adjust_year(res) + if res.year < 100 then + if res.year < 70 then + res.year = res.year + 2000 + else + res.year = res.year + 1900 + end + end +end + +local shortdate = sip.compile('$d{day}/$d{month}/$d{year}') +local longdate = sip.compile('$d{day} $v{month} $d{year}') +local isodate = sip.compile('$d{year}-$d{month}-$d{day}') + +local function dcheck (d1,d2) + adjust_year(d1) + test.asserteq(d1, d2) +end + +local function dates(str,tbl) + local res = {} + if shortdate(str,res) then + dcheck(res,tbl) + elseif isodate(str,res) then + dcheck(res,tbl) + elseif longdate(str,res) then + res.month = tablex.find(months,res.month) + dcheck(res,tbl) + else + assert(tbl == nil) + end +end + +dates ('10/12/2007',{year=2007,month=12,day=10}) +dates ('2006-03-01',{year=2006,month=3,day=1}) +dates ('25/07/05',{year=2005,month=7,day=25}) +dates ('20 Mar 1959',{year=1959,month=3,day=20}) + +local sio = require 'pl.stringio' +local lines = [[ +dodge much amazement +kitteh cheezburger +]] +sip.read(sio.open(lines),{ + {'dodge $',function(rest) test.asserteq(rest,'much amazement') end}, + {'kitteh $',function(rest) test.asserteq(rest,'cheezburger') end} +}) + + + + + diff --git a/Data/Libraries/Penlight/tests/test-strict.lua b/Data/Libraries/Penlight/tests/test-strict.lua new file mode 100644 index 0000000..12b0fad --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-strict.lua @@ -0,0 +1,145 @@ +require 'pl.compat' -- require this one before loading strict +local strict = require 'pl.strict' +local test = require 'pl.test' +local app = require 'pl.app' + +-- in strict mode, you must assign to a global first, even if just nil. +test.assertraise(function() + print(x) + print 'ok?' +end,"variable 'x' is not declared") + +-- can assign to globals in main (or from C extensions) but not anywhere else! +test.assertraise(function() + Boo = 3 +end,"assign to undeclared global 'Boo'") + +Boo = true +Boo2 = nil + +-- once declared, you can assign to globals from anywhere +(function() Boo = 42; Boo2 = 6*7 end)() + +--- a module may use strict.module() to generate a simularly strict environment +-- (see lua/mymod.lua) +app.require_here 'lua' +local M = require 'mymod' + +--- these are fine +M.answer() +M.question() + +-- spelling mistakes become errors... +test.assertraise(function() + print(M.Answer()) +end,"variable 'Answer' is not declared in 'mymod'") + +--- for the extra paranoid, you can choose to make all global tables strict... +strict.make_all_strict(_G) + +test.assertraise(function() + print(math.sine(1.2)) +end,"variable 'sine' is not declared in 'math'") + + + +-- module +do + local testmodule = { + hello = function() return "supremacy" end + } + -- make strict and allow extra field "world" + strict.module("my_test", testmodule, { world = true }) + + test.asserteq(testmodule.hello(), "supremacy") + test.assertraise(function() + print(testmodule.not_allowed_key) + end, "variable 'not_allowed_key' is not declared in 'my_test'") + + test.asserteq(testmodule.world, nil) + testmodule.world = "supremacy" + test.asserteq(testmodule.world, "supremacy") + + + -- table with a __newindex method + local mod1 = strict.module("mod1", setmetatable( + { + hello = "world", + }, { + __newindex = function(self, key, value) + if key == "Lua" then + rawset(self, key, value) + end + end, + } + )) + test.asserteq(mod1.hello, "world") + mod1.Lua = "hello world" + test.asserteq(mod1.Lua, "hello world") + test.assertraise(function() + print(mod1.not_allowed_key) + end, "variable 'not_allowed_key' is not declared in 'mod1'") + + + -- table with a __index method + local mod1 = strict.module("mod1", setmetatable( + { + hello = "world", + }, { + __index = function(self, key) + if key == "Lua" then + return "rocks" + end + end, + } + )) + test.asserteq(mod1.hello, "world") + test.asserteq(mod1.Lua, "rocks") + test.assertraise(function() + print(mod1.not_allowed_key) + end, "variable 'not_allowed_key' is not declared in 'mod1'") + + + -- table with a __index table + local mod1 = strict.module("mod1", setmetatable( + { + hello = "world", + }, { + __index = { + Lua = "rocks!" + } + } + )) + test.asserteq(mod1.hello, "world") + test.asserteq(mod1.Lua, "rocks!") + test.assertraise(function() + print(mod1.not_allowed_key) + end, "variable 'not_allowed_key' is not declared in 'mod1'") + +end + + +do + -- closed_module + -- what does this do? this does not seem a usefull function??? + + local testmodule = { + hello = function() return "supremacy" end + } + local M = strict.closed_module(testmodule, "my_test") + + -- read acces to original is granted, but not to the new one + test.asserteq(testmodule.hello(), "supremacy") + test.assertraise(function() + print(M.hello()) + end, "variable 'hello' is not declared in 'my_test'") + + -- write access to both is granted + testmodule.world = "domination" + M.world = "domination" + + -- read acces to set field in original is granted, but not set + test.asserteq(testmodule.world, nil) + test.asserteq(M.world, "domination") + +end diff --git a/Data/Libraries/Penlight/tests/test-stringio.lua b/Data/Libraries/Penlight/tests/test-stringio.lua new file mode 100644 index 0000000..93a1990 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-stringio.lua @@ -0,0 +1,72 @@ +local stringio = require 'pl.stringio' +local test = require 'pl.test' +local asserteq = test.asserteq +local T = test.tuple + +function fprintf(f,fmt,...) + f:write(fmt:format(...)) +end + +fs = stringio.create() +for i = 1,100 do + fs:write('hello','\n','dolly','\n') +end +asserteq(#fs:value(),1200) + +fs = stringio.create() +fs:writef("%s %d",'answer',42) -- note writef() extension method +asserteq(fs:value(),"answer 42") + +inf = stringio.open('10 20 30') +asserteq(T(inf:read('*n','*n','*n')),T(10,20,30)) + +local txt = [[ +Some lines +here are they +not for other +english? + +]] + +inf = stringio.open (txt) +fs = stringio.create() +for l in inf:lines() do + fs:write(l,'\n') +end +asserteq(txt,fs:value()) + +inf = stringio.open '1234567890ABCDEF' +asserteq(T(inf:read(3), inf:read(5), inf:read()),T('123','45678','90ABCDEF')) + +s = stringio.open 'one\ntwo' +asserteq(s:read() , 'one') +asserteq(s:read() , 'two') +asserteq(s:read() , nil) +s = stringio.open 'one\ntwo' +iter = s:lines() +asserteq(iter() , 'one') +asserteq(iter() , 'two') +asserteq(iter() , nil) +s = stringio.open 'ABC' +iter = s:lines(1) +asserteq(iter() , 'A') +asserteq(iter() , 'B') +asserteq(iter() , 'C') +asserteq(iter() , nil) + +s = stringio.open '20 5.2e-2 52.3' +x,y,z = s:read('*n','*n','*n') +out = stringio.create() +fprintf(out,"%5.2f %5.2f %5.2f!",x,y,z) +asserteq(out:value(),"20.00 0.05 52.30!") + +s = stringio.open 'one\ntwo\n\n' +iter = s:lines '*L' +asserteq(iter(),'one\n') +asserteq(iter(),'two\n') +asserteq(iter(),'\n') +asserteq(iter(),nil) + + + + diff --git a/Data/Libraries/Penlight/tests/test-stringx.lua b/Data/Libraries/Penlight/tests/test-stringx.lua new file mode 100644 index 0000000..20ebe17 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-stringx.lua @@ -0,0 +1,381 @@ +local stringx = require 'pl.stringx' +local utils = require 'pl.utils' +local asserteq = require 'pl.test' . asserteq +local T = require 'pl.test'.tuple + +local function FIX(s) + io.stderr:write('FIX:' .. s .. '\n') +end + + +-- isalpha +asserteq(T(stringx.isalpha''), T(false)) +asserteq(T(stringx.isalpha' '), T(false)) +asserteq(T(stringx.isalpha'0'), T(false)) +asserteq(T(stringx.isalpha'\0'), T(false)) +asserteq(T(stringx.isalpha'azAZ'), T(true)) +asserteq(T(stringx.isalpha'az9AZ'), T(false)) + +-- isdigit +asserteq(T(stringx.isdigit''), T(false)) +asserteq(T(stringx.isdigit' '), T(false)) +asserteq(T(stringx.isdigit'a'), T(false)) +asserteq(T(stringx.isdigit'0123456789'), T(true)) + +-- isalnum +asserteq(T(stringx.isalnum''), T(false)) +asserteq(T(stringx.isalnum' '), T(false)) +asserteq(T(stringx.isalnum('azAZ01234567890')), T(true)) + +-- isspace +asserteq(T(stringx.isspace''), T(false)) +asserteq(T(stringx.isspace' '), T(true)) +asserteq(T(stringx.isspace' \r\n\f\t'), T(true)) +asserteq(T(stringx.isspace' \r\n-\f\t'), T(false)) + +-- islower +asserteq(T(stringx.islower''), T(false)) +asserteq(T(stringx.islower'az'), T(true)) +asserteq(T(stringx.islower'aMz'), T(false)) +asserteq(T(stringx.islower'a z'), T(true)) + +-- isupper +asserteq(T(stringx.isupper''), T(false)) +asserteq(T(stringx.isupper'AZ'), T(true)) +asserteq(T(stringx.isupper'AmZ'), T(false)) +asserteq(T(stringx.isupper'A Z'), T(true)) + +-- startswith +local startswith = stringx.startswith +asserteq(T(startswith('', '')), T(true)) +asserteq(T(startswith('', 'a')), T(false)) +asserteq(T(startswith('a', '')), T(true)) +asserteq(T(startswith('a', 'a')), T(true)) +asserteq(T(startswith('a', 'b')), T(false)) +asserteq(T(startswith('a', 'ab')), T(false)) +asserteq(T(startswith('abc', 'ab')), T(true)) +asserteq(T(startswith('abc', 'bc')), T(false)) -- off by one +asserteq(T(startswith('abc', '.')), T(false)) -- Lua pattern char +asserteq(T(startswith('a\0bc', 'a\0b')), T(true)) -- '\0' + +asserteq(startswith('abcfoo',{'abc','def'}),true) +asserteq(startswith('deffoo',{'abc','def'}),true) +asserteq(startswith('cdefoo',{'abc','def'}),false) + + +-- endswith +-- http://snippets.luacode.org/sputnik.lua?p=snippets/Check_string_ends_with_other_string_74 +local endswith = stringx.endswith +asserteq(T(endswith("", "")), T(true)) +asserteq(T(endswith("", "a")), T(false)) +asserteq(T(endswith("a", "")), T(true)) +asserteq(T(endswith("a", "a")), T(true)) +asserteq(T(endswith("a", "A")), T(false)) -- case sensitive +asserteq(T(endswith("a", "aa")), T(false)) +asserteq(T(endswith("abc", "")), T(true)) +asserteq(T(endswith("abc", "ab")), T(false)) -- off by one +asserteq(T(endswith("abc", "c")), T(true)) +asserteq(T(endswith("abc", "bc")), T(true)) +asserteq(T(endswith("abc", "abc")), T(true)) +asserteq(T(endswith("abc", " abc")), T(false)) +asserteq(T(endswith("abc", "a")), T(false)) +asserteq(T(endswith("abc", ".")), T(false)) -- Lua pattern char +asserteq(T(endswith("ab\0c", "b\0c")), T(true)) -- \0 +asserteq(T(endswith("ab\0c", "b\0d")), T(false)) -- \0 + +asserteq(endswith('dollar.dot',{'.dot','.txt'}),true) +asserteq(endswith('dollar.txt',{'.dot','.txt'}),true) +asserteq(endswith('dollar.rtxt',{'.dot','.txt'}),false) + +-- join +asserteq(stringx.join(' ', {1,2,3}), '1 2 3') + +-- splitlines +asserteq(stringx.splitlines(''), {}) +asserteq(stringx.splitlines('a'), {'a'}) +asserteq(stringx.splitlines('\n'), {''}) +asserteq(stringx.splitlines('\n\n'), {'', ''}) +asserteq(stringx.splitlines('\r\r'), {'', ''}) +asserteq(stringx.splitlines('\r\n'), {''}) +asserteq(stringx.splitlines('ab\ncd\n'), {'ab', 'cd'}) +asserteq(stringx.splitlines('ab\ncd\n', true), {'ab\n', 'cd\n'}) +asserteq(stringx.splitlines('\nab\r\r\ncd\n', true), {'\n', 'ab\r', '\r\n', 'cd\n'}) + +-- expandtabs +---FIX[[raises error +asserteq(T(stringx.expandtabs('',0)), T('')) +asserteq(T(stringx.expandtabs('',1)), T('')) +asserteq(T(stringx.expandtabs(' ',1)), T(' ')) +-- expandtabs now works like Python's str.expandtabs (up to next tab stop) +asserteq(T(stringx.expandtabs(' \t ')), T((' '):rep(1+8))) +asserteq(T(stringx.expandtabs(' \t ',2)), T(' ')) +--]] + +-- lfind +asserteq(T(stringx.lfind('', '')), T(1)) +asserteq(T(stringx.lfind('a', '')), T(1)) +asserteq(T(stringx.lfind('ab', 'b')), T(2)) +asserteq(T(stringx.lfind('abc', 'cd')), T(nil)) +asserteq(T(stringx.lfind('abcbc', 'bc')), T(2)) +asserteq(T(stringx.lfind('ab..cd', '.')), T(3)) -- pattern char +asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3)), T(4)) +asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3, 4)), T(nil)) +asserteq(T(stringx.lfind('abcbcbbc', 'bc', 3, 5)), T(4)) +asserteq(T(stringx.lfind('abcbcbbc', 'bc', nil, 5)), T(2)) + +-- rfind +asserteq(T(stringx.rfind('', '')), T(1)) +asserteq(T(stringx.rfind('ab', '')), T(3)) +asserteq(T(stringx.rfind('abc', 'cd')), T(nil)) +asserteq(T(stringx.rfind('abcbc', 'bc')), T(4)) +asserteq(T(stringx.rfind('abcbcb', 'bc')), T(4)) +asserteq(T(stringx.rfind('ab..cd', '.')), T(4)) -- pattern char +asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3)), T(7)) +asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3, 4)), T(nil)) +asserteq(T(stringx.rfind('abcbcbbc', 'bc', 3, 5)), T(4)) +asserteq(T(stringx.rfind('abcbcbbc', 'bc', nil, 5)), T(4)) +asserteq(T(stringx.rfind('banana', 'ana')), T(4)) + +-- replace +asserteq(T(stringx.replace('', '', '')), T('')) +asserteq(T(stringx.replace(' ', '', '')), T(' ')) +asserteq(T(stringx.replace(' ', '', ' ')), T(' ')) +asserteq(T(stringx.replace(' ', ' ', '')), T('')) +asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC')), T('aBCaBCaBC')) +asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC', 1)), T('aBCabcabc')) +asserteq(T(stringx.replace('abcabcabc', 'bc', 'BC', 0)), T('abcabcabc')) +asserteq(T(stringx.replace('abc', 'd', 'e')), T('abc')) +asserteq(T(stringx.replace('a.b', '.', '%d')), T('a%db')) + +-- split +local split = stringx.split +asserteq(split('', ''), {''}) +asserteq(split('', 'z'), {}) --FIX:intended and specified behavior? +asserteq(split('a', ''), {'a'}) --FIX:intended and specified behavior? +asserteq(split('a', 'a'), {''}) +-- stringx.split now follows the Python pattern, so it uses a substring, not a pattern. +-- If you need to split on a pattern, use utils.split() +-- asserteq(split('ab1cd23ef%d', '%d+'), {'ab', 'cd', 'ef%d'}) -- pattern chars +-- note that leading space is ignored by the default +asserteq(split(' 1 2 3 '),{'1','2','3'}) +asserteq(split('a*bb*c*ddd','*'),{'a','bb','c','ddd'}) +asserteq(split('dog:fred:bonzo:alice',':',3), {'dog','fred','bonzo:alice'}) +asserteq(split('dog:fred:bonzo:alice:',':',3), {'dog','fred','bonzo:alice:'}) +asserteq(split('///','/'),{'','','',''}) +-- capitalize +asserteq(T(stringx.capitalize('')), T('')) +asserteq(T(stringx.capitalize('abC deF1')), T('Abc Def1')) -- Python behaviour + +-- count +asserteq(T(stringx.count('', '')), T(0)) --infinite loop]] +asserteq(T(stringx.count(' ', '')), T(2)) --infinite loop]] +asserteq(T(stringx.count('a..c', '.')), T(2)) -- pattern chars +asserteq(T(stringx.count('a1c', '%d')), T(0)) -- pattern chars +asserteq(T(stringx.count('Anna Anna Anna', 'Anna')), T(3)) -- no overlap +asserteq(T(stringx.count('banana', 'ana', false)), T(1)) -- no overlap +asserteq(T(stringx.count('banana', 'ana', true)), T(2)) -- overlap + +-- ljust +asserteq(T(stringx.ljust('', 0)), T('')) +asserteq(T(stringx.ljust('', 2)), T(' ')) +asserteq(T(stringx.ljust('ab', 3)), T('ab ')) +asserteq(T(stringx.ljust('ab', 3, '%')), T('ab%')) +asserteq(T(stringx.ljust('abcd', 3)), T('abcd')) -- agrees with Python + +-- rjust +asserteq(T(stringx.rjust('', 0)), T('')) +asserteq(T(stringx.rjust('', 2)), T(' ')) +asserteq(T(stringx.rjust('ab', 3)), T(' ab')) +asserteq(T(stringx.rjust('ab', 3, '%')), T('%ab')) +asserteq(T(stringx.rjust('abcd', 3)), T('abcd')) -- agrees with Python + +-- center +asserteq(T(stringx.center('', 0)), T('')) +asserteq(T(stringx.center('', 1)), T(' ')) +asserteq(T(stringx.center('', 2)), T(' ')) +asserteq(T(stringx.center('a', 1)), T('a')) +asserteq(T(stringx.center('a', 2)), T('a ')) +asserteq(T(stringx.center('a', 3)), T(' a ')) + + +-- ltrim +-- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 +local trim = stringx.lstrip +asserteq(T(trim''), T'') +asserteq(T(trim' '), T'') +asserteq(T(trim' '), T'') +asserteq(T(trim'a'), T'a') +asserteq(T(trim' a'), T'a') +asserteq(T(trim'a '), T'a ') +asserteq(T(trim' a '), T'a ') +asserteq(T(trim' a '), T'a ') +asserteq(T(trim' ab cd '), T'ab cd ') +asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T'a\000b \r\t\n\f\v') +-- more + + +-- rtrim +-- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 +local trim = stringx.rstrip +asserteq(T(trim''), T'') +asserteq(T(trim' '), T'') +asserteq(T(trim' '), T'') +asserteq(T(trim'a'), T'a') +asserteq(T(trim' a'), T' a') +asserteq(T(trim'a '), T'a') +asserteq(T(trim' a '), T' a') +asserteq(T(trim' a '), T' a') +asserteq(T(trim' ab cd '), T' ab cd') +asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T' \t\r\n\f\va\000b') +-- more + + +-- trim +-- http://snippets.luacode.org/sputnik.lua?p=snippets/trim_whitespace_from_string_76 +local trim = stringx.strip +asserteq(T(trim''), T'') +asserteq(T(trim' '), T'') +asserteq(T(trim' '), T'') +asserteq(T(trim'a'), T'a') +asserteq(T(trim' a'), T'a') +asserteq(T(trim'a '), T'a') +asserteq(T(trim' a '), T'a') +asserteq(T(trim' a '), T'a') +asserteq(T(trim' ab cd '), T'ab cd') +asserteq(T(trim' \t\r\n\f\va\000b \r\t\n\f\v'), T'a\000b') +local long = 'a' .. string.rep(' ', 200000) .. 'a' +asserteq(T(trim(long)), T(long)) +-- more + + +asserteq({stringx.splitv("hello dolly")}, {"hello", "dolly"}) + + +-- partition +-- as per str.partition in Python, delimiter must be non-empty; +-- interpreted as a plain string +--asserteq(T(stringx.partition('', '')), T('', '', '')) -- error]] +--asserteq(T(stringx.partition('a', '')), T('', '', 'a')) --error]] +asserteq(T(stringx.partition('a', 'a')), T('', 'a', '')) +asserteq(T(stringx.partition('abc', 'b')), T('a', 'b', 'c')) +asserteq(T(stringx.partition('abc', '.+')), T('abc','','')) +asserteq(T(stringx.partition('a,b,c', ',')), T('a',',','b,c')) +asserteq(T(stringx.partition('abc', '/')), T('abc', '', '')) +-- rpartition +asserteq(T(stringx.rpartition('a/b/c', '/')), T('a/b', '/', 'c')) +asserteq(T(stringx.rpartition('abc', 'b')), T('a', 'b', 'c')) +asserteq(T(stringx.rpartition('a', 'a')), T('', 'a', '')) +asserteq(T(stringx.rpartition('abc', '/')), T('', '', 'abc')) + + +-- at (works like s:sub(idx,idx), so negative indices allowed +asserteq(T(stringx.at('a', 1)), T('a')) +asserteq(T(stringx.at('ab', 2)), T('b')) +asserteq(T(stringx.at('abcd', -1)), T('d')) +asserteq(T(stringx.at('abcd', 10)), T('')) -- not found + +-- lines +local function merge(it, ...) + assert(select('#', ...) == 0) + local ts = {} + for val in it do ts[#ts+1] = val end + return ts +end +asserteq(merge(stringx.lines('')), {''}) +asserteq(merge(stringx.lines('ab')), {'ab'}) +asserteq(merge(stringx.lines('ab\ncd')), {'ab', 'cd'}) + +asserteq(stringx.capitalize("hello world"), "Hello World") +asserteq(stringx.title("hello world"), "Hello World") + +-- shorten +-- The returned string is always equal or less to the given size. +asserteq(T(stringx.shorten('', 0)), T'') +asserteq(T(stringx.shorten('a', 1)), T'a') +asserteq(T(stringx.shorten('ab', 1)), T'.') --FIX:ok? +asserteq(T(stringx.shorten('abc', 3)), T'abc') +asserteq(T(stringx.shorten('abcd', 3)), T'...') +asserteq(T(stringx.shorten('abcde', 5)), T'abcde') +asserteq(T(stringx.shorten('abcde', 4)), T'a...') +asserteq(T(stringx.shorten('abcde', 3)), T'...') +asserteq(T(stringx.shorten('abcde', 2)), T'..') +asserteq(T(stringx.shorten('abcde', 0)), T'') +asserteq(T(stringx.shorten('', 0, true)), T'') +asserteq(T(stringx.shorten('a', 1, true)), T'a') +asserteq(T(stringx.shorten('ab', 1, true)), T'.') +asserteq(T(stringx.shorten('abcde', 5, true)), T'abcde') +asserteq(T(stringx.shorten('abcde', 4, true)), T'...e') +asserteq(T(stringx.shorten('abcde', 3, true)), T'...') +asserteq(T(stringx.shorten('abcde', 2, true)), T'..') +asserteq(T(stringx.shorten('abcde', 0, true)), T'') + +-- strip +asserteq(stringx.strip(' hello '),'hello') +asserteq(stringx.strip('--[hello] -- - ','-[] '),'hello') +asserteq(stringx.rstrip('--[hello] -- - ','-[] '),'--[hello') +asserteq(stringx.strip('hello'..((" "):rep(500))), "hello") + +-- + +local assert_str_round_trip = function(s) + + local qs = stringx.quote_string(s) + local compiled, err = utils.load("return "..qs) + + if not compiled then + print( + ("stringx.quote_string assert failed: invalid string created: Received:\n%s\n\nCompiled to\n%s\n\nError:\t%s\n"): + format(s, qs, err) + ) + error() + else + compiled = compiled() + end + + if compiled ~= s then + print("stringx.quote_string assert Failed: String compiled but did not round trip.") + print("input string:\t\t",s, #s) + print("compiled string:\t", compiled, #compiled) + print("output string:\t\t",qs, #qs) + error() + else + -- print("input string:\t\t",s) + -- print("compiled string:\t", compiled) + -- print("output string:\t\t",qs) + end +end + +assert_str_round_trip( "normal string with nothing weird.") +assert_str_round_trip( "Long string quoted with escaped quote \\\" and a long string pattern match [==[ found near the end.") + +assert_str_round_trip( "Unescapped quote \" in the middle") +assert_str_round_trip( "[[Embedded long quotes \\\". Escaped must stay! ]]") +assert_str_round_trip( [[Long quoted string with a slash prior to quote \\\". ]]) +assert_str_round_trip( "[[Completely normal\n long quote. ]]") +assert_str_round_trip( "String with a newline\nending with a closing bracket]") +assert_str_round_trip( "[[String with opening brackets ending with part of a long closing bracket]=") +assert_str_round_trip( "\n[[Completely normal\n long quote. Except that we lead with a return! Tricky! ]]") +assert_str_round_trip( '"balance [======[ doesn\'t ]====] mater when searching for embedded long-string quotes.') +assert_str_round_trip( "Any\0 \t control character other than a return will be handled by the %q mechanism.") +assert_str_round_trip( "This\tincludes\ttabs.") +assert_str_round_trip( "But not returns.\n Returns are easier to see using long quotes.") +assert_str_round_trip( "The \z escape does not trigger a control pattern, however.") + +assert_str_round_trip( "[==[If a string is long-quoted, escaped \\\" quotes have to stay! ]==]") +assert_str_round_trip('"A quoted string looks like what?"') +assert_str_round_trip( "'I think that it should be quoted, anyway.'") +assert_str_round_trip( "[[Even if they're long quoted.]]") +assert_str_round_trip( "]=]==]") + +assert_str_round_trip( "\"\\\"\\' pathalogical:starts with a quote ]\"\\']=]]==][[]]]=========]") +assert_str_round_trip( "\\\"\\\"\\' pathalogical: quote is after this text with a quote ]\"\\']=]]==][[]]]=========]") +assert_str_round_trip( "\\\"\\\"\\' pathalogical: quotes are all escaped. ]\\\"\\']=]]==][[]]]=========]") +assert_str_round_trip( "") +assert_str_round_trip( " ") +assert_str_round_trip( "\n") --tricky. +assert_str_round_trip( "\r") +assert_str_round_trip( "\r\n") +assert_str_round_trip( "\r1\n") +assert_str_round_trip( "[[") +assert_str_round_trip( "''") +assert_str_round_trip( '""') diff --git a/Data/Libraries/Penlight/tests/test-stringx2.lua b/Data/Libraries/Penlight/tests/test-stringx2.lua new file mode 100644 index 0000000..c690e58 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-stringx2.lua @@ -0,0 +1,20 @@ +local asserteq = require 'pl.test' . asserteq + + +-- strings --- +require 'pl.stringx'.import() ---> convenient! +local s = '123' +assert (s:isdigit()) +assert (not s:isspace()) +s = 'here the dog is just a dog' +assert (s:startswith('here')) +assert (s:endswith('dog')) +assert (s:count('dog') == 2) +s = ' here we go ' +asserteq (s:lstrip() , 'here we go ') +asserteq (s:rstrip() , ' here we go') +asserteq (s:strip() , 'here we go') +asserteq (('hello'):center(20,'+') , '+++++++hello++++++++') + +asserteq (('hello dolly'):title() , 'Hello Dolly') +asserteq (('h bk bonzo TOK fred m'):title() , 'H Bk Bonzo Tok Fred M') diff --git a/Data/Libraries/Penlight/tests/test-tablex.lua b/Data/Libraries/Penlight/tests/test-tablex.lua new file mode 100644 index 0000000..c8bbf01 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-tablex.lua @@ -0,0 +1,348 @@ +local tablex = require 'pl.tablex' +local utils = require ('pl.utils') +local L = utils.string_lambda +local test = require('pl.test') +-- bring tablex funtions into global namespace +utils.import(tablex) +local asserteq = test.asserteq + +local cmp = deepcompare + +function asserteq_no_order (x,y) + if not compare_no_order(x,y) then + test.complain(x,y,"these lists contained different elements") + end +end + + +asserteq( + copy {10,20,30}, + {10,20,30} +) + +asserteq( + deepcopy {10,20,{30,40}}, + {10,20,{30,40}} +) + +local t = { + a = "hello", + b = { + c = "world" + } +} +t.b.d = t.b + +local tcopy = { + a = "hello", + b = { + c = "world" + } +} +tcopy.b.d = tcopy.b + +asserteq( + deepcopy(t), + tcopy +) + +asserteq( + pairmap(function(i,v) return v end,{10,20,30}), + {10,20,30} +) + +asserteq_no_order( + pairmap(L'_',{fred=10,bonzo=20}), + {'fred','bonzo'} +) + +asserteq_no_order( + pairmap(function(k,v) return v end,{fred=10,bonzo=20}), + {10,20} +) + +asserteq_no_order( + pairmap(function(i,v) return v,i end,{10,20,30}), + {10,20,30} +) + +asserteq( + pairmap(function(k,v) return {k,v},k end,{one=1,two=2}), + {one={'one',1},two={'two',2}} +) +-- same as above, using string lambdas +asserteq( + pairmap(L'|k,v|{k,v},k',{one=1,two=2}), + {one={'one',1},two={'two',2}} +) + + +asserteq( + map(function(v) return v*v end,{10,20,30}), + {100,400,900} +) + +-- extra arguments to map() are passed to the function; can use +-- the abbreviations provided by pl.operator +asserteq( + map('+',{10,20,30},1), + {11,21,31} +) + +asserteq( + map(L'_+1',{10,20,30}), + {11,21,31} +) + +-- map2 generalizes for operations on two tables +asserteq( + map2(math.max,{1,2,3},{0,4,2}), + {1,4,3} +) + +-- mapn operates over an arbitrary number of input tables (but use map2 for n=2) +asserteq( + mapn(function(x,y,z) return x+y+z end, {1,2,3},{10,20,30},{100,200,300}), + {111,222,333} +) + +asserteq( + mapn(math.max, {1,20,300},{10,2,3},{100,200,100}), + {100,200,300} +) + +asserteq( + count_map({"foo", "bar", "foo", "baz"}), + {foo = 2, bar = 1, baz = 1} +) + +asserteq( + zip({10,20,30},{100,200,300}), + {{10,100},{20,200},{30,300}} +) + +assert(compare_no_order({1,2,3,4},{2,1,4,3})==true) +assert(compare_no_order({1,2,3,4},{2,1,4,4})==false) + +asserteq(range(10,9),{}) +asserteq(range(10,10),{10}) +asserteq(range(10,11),{10,11}) + +-- update inserts key-value pairs from the second table +t1 = {one=1,two=2} +t2 = {three=3,two=20,four=4} +asserteq(update(t1,t2),{one=1,three=3,two=20,four=4}) + +-- the difference between move and icopy is that the second removes +-- any extra elements in the destination after end of copy +-- 3rd arg is the index to start in the destination, defaults to 1 +asserteq(move({1,2,3,4,5,6},{20,30}),{20,30,3,4,5,6}) +asserteq(move({1,2,3,4,5,6},{20,30},2),{1,20,30,4,5,6}) +asserteq(icopy({1,2,3,4,5,6},{20,30},2),{1,20,30}) +-- 5th arg determines how many elements to copy (default size of source) +asserteq(icopy({1,2,3,4,5,6},{20,30},2,1,1),{1,20}) +-- 4th arg is where to stop copying from the source (default s to 1) +asserteq(icopy({1,2,3,4,5,6},{20,30},2,2,1),{1,30}) + +-- whereas insertvalues works like table.insert, but inserts a range of values +-- from the given table. +asserteq(insertvalues({1,2,3,4,5,6},2,{20,30}),{1,20,30,2,3,4,5,6}) +asserteq(insertvalues({1,2},{3,4}),{1,2,3,4}) + +-- the 4th arg of move and icopy gives the start index in the source table +asserteq(move({1,2,3,4,5,6},{20,30},2,2),{1,30,3,4,5,6}) +asserteq(icopy({1,2,3,4,5,6},{20,30},2,2),{1,30}) + +t = {1,2,3,4,5,6} +move(t,{20,30},2,1,1) +asserteq(t,{1,20,3,4,5,6}) +set(t,0,2,3) +asserteq(t,{1,0,0,4,5,6}) +insertvalues(t,1,{10,20}) +asserteq(t,{10,20,1,0,0,4,5,6}) + +asserteq(merge({10,20,30},{nil, nil, 30, 40}), {[3]=30}) +asserteq(merge({10,20,30},{nil, nil, 30, 40}, true), {10,20,30,40}) + + +-- Function to check that the order of elements returned by the iterator +-- match the order of the elements in the list. +function assert_iter_order(iter,l) + local i = 0 + for k,v in iter do + i = i + 1 + asserteq(k,l[i][1]) + asserteq(v,l[i][2]) + end +end + +local t = {a=10,b=9,c=8,d=7,e=6,f=5,g=4,h=3,i=2,j=1} + +assert_iter_order( + sort(t), + {{'a',10},{'b',9},{'c',8},{'d',7},{'e',6},{'f',5},{'g',4},{'h',3},{'i',2},{'j',1}}) + +assert_iter_order( + sortv(t), + {{'j',1},{'i',2},{'h',3},{'g',4},{'f',5},{'e',6},{'d',7},{'c',8},{'b',9},{'a',10}}) + + +asserteq(difference({a = true, b = true},{a = true, b = true}),{}) + +-- no longer confused by false values ;) +asserteq(difference({v = false},{v = false}),{}) + +asserteq(difference({a = true},{b = true}),{a=true}) + +-- symmetric difference +asserteq(difference({a = true},{b = true},true),{a=true,b=true}) + +--basic index_map test +asserteq(index_map({10,20,30}), {[10]=1,[20]=2,[30]=3}) +--test that repeated values return multiple indices +asserteq(index_map({10,20,30,30,30}), {[10]=1,[20]=2,[30]={3,4,5}}) + +-- Reduce +asserteq(tablex.reduce('-', {}, 2), 2) +asserteq(tablex.reduce('-', {}), nil) +asserteq(tablex.reduce('-', {1,2,3,4,5}), -13) +asserteq(tablex.reduce('-', {1,2,3,4,5}, 1), -14) + + +-- tablex.compare +do + asserteq(tablex.compare({},{}, "=="), true) + asserteq(tablex.compare({1,2,3}, {1,2,3}, "=="), true) + asserteq(tablex.compare({1,"hello",3}, {1,2,3}, "=="), false) + asserteq(tablex.compare( + {1,2,3, hello = "world"}, + {1,2,3}, + function(v1, v2) return v1 == v2 end), + true) -- only compares the list part +end + + +-- tablex.rfind +do + local rfind = tablex.rfind + local lst = { "Rudolph", "the", "red-nose", "raindeer" } + asserteq(rfind(lst, "Santa"), nil) + asserteq(rfind(lst, "raindeer", -2), nil) + asserteq(rfind(lst, "raindeer"), 4) + asserteq(rfind(lst, "Rudolph"), 1) + asserteq(rfind(lst, "the", -3), 2) + asserteq(rfind(lst, "the", -30), nil) + asserteq(rfind({10,10,10},10), 3) +end + + +-- tablex.find_if +do + local fi = tablex.find_if + local lst = { "Rudolph", true, false, 15 } + asserteq({fi(lst, "==", "Rudolph")}, {1, true}) + asserteq({fi(lst, "==", true)}, {2, true}) + asserteq({fi(lst, "==", false)}, {3, true}) + asserteq({fi(lst, "==", 15)}, {4, true}) + + local cmp = function(v1, v2) return v1 == v2 and v2 end + asserteq({fi(lst, cmp, "Rudolph")}, {1, "Rudolph"}) + asserteq({fi(lst, cmp, true)}, {2, true}) + asserteq({fi(lst, cmp, false)}, {}) -- 'false' cannot be returned! + asserteq({fi(lst, cmp, 15)}, {4, 15}) +end + + +-- tablex.map_named_method +do + local Car = {} + Car.__index = Car + function Car.new(car) + return setmetatable(car or {}, Car) + end + Car.speed = 0 + function Car:faster(increase) + self.speed = self.speed + (increase or 1) + return self.speed + end + function Car:slower(self, decrease) + self.speed = self.speed - (decrease or 1) + return self.speed + end + + local ferrari = Car.new{ name = "Ferrari" } + local lamborghini = Car.new{ name = "Lamborghini", speed = 50 } + local cars = { ferrari, lamborghini } + + asserteq(ferrari.speed, 0) + asserteq(lamborghini.speed, 50) + asserteq(tablex.map_named_method("faster", cars, 10), {10, 60}) + asserteq(ferrari.speed, 10) + asserteq(lamborghini.speed, 60) + +end + + +-- tablex.foreach +do + local lst = { "one", "two", "three", hello = "world" } + tablex.foreach(lst, function(v, k, sep) + lst[k] = tostring(k) .. sep .. v + end, " = ") + asserteq(lst, {"1 = one", "2 = two", "3 = three", hello = "hello = world"}) +end + + +-- tablex.foreachi +do + local lst = { "one", "two", "three", hello = "world" } + tablex.foreachi(lst, function(v, k, sep) + lst[k] = tostring(k) .. sep .. v + end, " = ") + asserteq(lst, {"1 = one", "2 = two", "3 = three", hello = "world"}) +end + + +-- tablex.new +asserteq(tablex.new(3, "hi"), { "hi", "hi", "hi" }) + + +-- tablex.search +do + local t = { + penlight = { + battery = { + type = "AA", + capacity = "1500mah", + }, + }, + hello = { + world = { + also = "AA" + } + } + } + asserteq(tablex.search(t, "1500mah"), "penlight.battery.capacity") + asserteq(tablex.search(t, "AA", {t.penlight} ), "hello.world.also") + asserteq(tablex.search(t, "xxx"), nil) +end + + +-- tablex.readonly +do + local ro = tablex.readonly { 1,2,3, hello = "world" } + asserteq(pcall(function() ro.hello = "hi there" end), false) + asserteq(getmetatable(ro), false) + + if not utils.lua51 then + asserteq(#ro, 3) + + local r = {} + for k,v in pairs(ro) do r[k] = v end + asserteq(r, { 1,2,3, hello = "world" }) + + r = {} + for k,v in ipairs(ro) do r[k] = v end + asserteq(r, { 1,2,3 }) + end +end diff --git a/Data/Libraries/Penlight/tests/test-tablex3.lua b/Data/Libraries/Penlight/tests/test-tablex3.lua new file mode 100644 index 0000000..6f4ea18 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-tablex3.lua @@ -0,0 +1,12 @@ +-- tablex.move when the tables are the same +-- and there are overlapping ranges +T = require 'pl.tablex' +asserteq = require 'pl.test'.asserteq + +t1 = {1,2,3,4,5,6,7,8,9,10} +t2 = T.copy(t1) +t3 = T.copy(t1) + +T.move(t1,t2,4,1,4) +T.move(t3,t3,4,1,4) +asserteq(t1,t3) diff --git a/Data/Libraries/Penlight/tests/test-template.lua b/Data/Libraries/Penlight/tests/test-template.lua new file mode 100644 index 0000000..fb13108 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-template.lua @@ -0,0 +1,245 @@ +local template = require 'pl.template' +local subst = template.substitute +local List = require 'pl.List' +local asserteq = require 'pl.test'.asserteq +local utils = require 'pl.utils' + + + +asserteq(subst([[ +# for i = 1,2 do +<p>Hello $(tostring(i))</p> +# end +]],_G),[[ +<p>Hello 1</p> +<p>Hello 2</p> +]]) + + + +asserteq(subst([[ +<ul> +# for name in ls:iter() do + <li>$(name)</li> +#end +</ul> +]],{ls = List{'john','alice','jane'}}),[[ +<ul> + <li>john</li> + <li>alice</li> + <li>jane</li> +</ul> +]]) + + + +-- can change the default escape from '#' so we can do C/C++ output. +-- note that the environment can have a parent field. +asserteq(subst([[ +> for i,v in ipairs{'alpha','beta','gamma'} do + cout << obj.${v} << endl; +> end +]],{_parent=_G, _brackets='{}', _escape='>'}),[[ + cout << obj.alpha << endl; + cout << obj.beta << endl; + cout << obj.gamma << endl; +]]) + + + +-- handle templates with a lot of substitutions +asserteq(subst(("$(x)\n"):rep(300), {x = "y"}), ("y\n"):rep(300)) + + + +-------------------------------------------------- +-- Test using no leading nor trailing linebreak +local tmpl = [[<ul> +# for i,val in ipairs(T) do +<li>$(i) = $(val:upper())</li> +# end +</ul>]] + +local my_env = { + ipairs = ipairs, + T = {'one','two','three'}, + _debug = true, +} +local res, err = template.substitute(tmpl, my_env) + +--print(res, err) +asserteq(res, [[<ul> +<li>1 = ONE</li> +<li>2 = TWO</li> +<li>3 = THREE</li> +</ul>]]) + + + +-------------------------------------------------- +-- Test using both leading and trailing linebreak +local tmpl = [[ +<ul> +# for i,val in ipairs(T) do +<li>$(i) = $(val:upper())</li> +# end +</ul> +]] + +local my_env = { + ipairs = ipairs, + T = {'one','two','three'}, + _debug = true, +} +local res, err = template.substitute(tmpl, my_env) + +--print(res, err) +asserteq(res, [[ +<ul> +<li>1 = ONE</li> +<li>2 = TWO</li> +<li>3 = THREE</li> +</ul> +]]) + + + +-------------------------------------------------- +-- Test reusing a compiled template +local tmpl = [[ +<ul> +# for i,val in ipairs(T) do +<li>$(i) = $(val:upper())</li> +# end +</ul> +]] + +local my_env = { + ipairs = ipairs, + T = {'one','two','three'} +} +local t, err = template.compile(tmpl, { debug = true }) +local res, err, code = t:render(my_env) +--print(res, err, code) +asserteq(res, [[ +<ul> +<li>1 = ONE</li> +<li>2 = TWO</li> +<li>3 = THREE</li> +</ul> +]]) + + +-- now reuse with different env +local my_env = { + ipairs = ipairs, + T = {'four','five','six'} +} +local t, err = template.compile(tmpl, { debug = true }) +local res, err, code = t:render(my_env) +--print(res, err, code) +asserteq(res, [[ +<ul> +<li>1 = FOUR</li> +<li>2 = FIVE</li> +<li>3 = SIX</li> +</ul> +]]) + + + +-------------------------------------------------- +-- Test the newline parameter +local tmpl = [[ +some list: $(T[1]:upper()) +# for i = 2, #T do +,$(T[i]:upper()) +# end +]] + +local my_env = { + ipairs = ipairs, + T = {'one','two','three'} +} +local t, err = template.compile(tmpl, { debug = true, newline = "" }) +local res, err, code = t:render(my_env) +--print(res, err, code) +asserteq(res, [[some list: ONE,TWO,THREE]]) + + + +-------------------------------------------------- +-- Test template run-time error +local tmpl = [[ +header: $("hello" * 10) +]] + +local t, err = template.compile(tmpl, { debug = true, newline = "" }) +local res, err, code = t:render() +--print(res, err, code) +assert(res == nil, "expected nil here because of the runtime error") +asserteq(type(err), "string") +asserteq(type(utils.load(code)), "function") + + + +-------------------------------------------------- +-- Test template run-time, doesn't fail on table value +-- table.concat fails if we insert a non-string (table) value +local tmpl = [[ +header: $(myParam) +]] + +local t, err = template.compile(tmpl, { debug = true, newline = "" }) +local myParam = {} +local res, err, code = t:render( {myParam = myParam } ) -- insert a table +--print(res, err, code) +asserteq(res, "header: "..tostring(myParam)) +asserteq(type(err), "nil") + + + +-------------------------------------------------- +-- Test template compile-time error +local tmpl = [[ +header: $(this doesn't work) +]] + +local my_env = { + ipairs = ipairs, + T = {'one','two','three'} +} +local t, err, code = template.compile(tmpl, { debug = true, newline = "" }) +--print(t, err, code) +assert(t==nil, "expected t to be nil here because of the syntax error") +asserteq(type(err), "string") +asserteq(type(code), "string") + + + +-------------------------------------------------- +-- Test using template being a single static string +local tmpl = [[ +<ul> +<p>a paragraph</p> +<p>a paragraph</p> +</ul> +]] + +local t, err = template.compile(tmpl, { debug = true }) +local res, err, code = t:render(my_env) +--print(res, err, code) + +asserteq(res, [[<ul> +<p>a paragraph</p> +<p>a paragraph</p> +</ul> +]]) +asserteq(code, [[return "<ul>\ +<p>a paragraph</p>\ +<p>a paragraph</p>\ +</ul>\ +"]]) + + +print("template: success") diff --git a/Data/Libraries/Penlight/tests/test-text.lua b/Data/Libraries/Penlight/tests/test-text.lua new file mode 100644 index 0000000..9419b22 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-text.lua @@ -0,0 +1,177 @@ +local T = require 'pl.text' +local utils = require 'pl.utils' +local Template = T.Template +local asserteq = require 'pl.test'.asserteq +local OrderedMap = require 'pl.OrderedMap' + +local t1 = Template [[ +while true do + $contents +end +]] + +assert(t1:substitute {contents = 'print "hello"'},[[ +while true do + print "hello" +end +]]) + +assert(t1:indent_substitute {contents = [[ +for i = 1,10 do + gotcha(i) +end +]]},[[ +while true do + for i = 1,10 do + gotcha(i) + end +end +]]) + +asserteq(T.dedent [[ + one + two + three +]],[[ +one +two +three +]]) +asserteq(T.fill ([[ +It is often said of Lua that it does not include batteries. That is because the goal of Lua is to produce a lean expressive language that will be used on all sorts of machines, (some of which don't even have hierarchical filesystems). The Lua language is the equivalent of an operating system kernel; the creators of Lua do not see it as their responsibility to create a full software ecosystem around the language. That is the role of the community. +]],20),[[ +It is often said of Lua +that it does not include +batteries. That is because +the goal of Lua is to +produce a lean expressive +language that will be +used on all sorts of machines, +(some of which don't +even have hierarchical +filesystems). The Lua +language is the equivalent +of an operating system +kernel; the creators of +Lua do not see it as their +responsibility to create +a full software ecosystem +around the language. That +is the role of the community. +]]) + +local template = require 'pl.template' + +local t = [[ +# for i = 1,3 do + print($(i+1)) +# end +]] + +asserteq(template.substitute(t),[[ + print(2) + print(3) + print(4) +]]) + +t = [[ +> for i = 1,3 do + print(${i+1}) +> end +]] + +asserteq(template.substitute(t,{_brackets='{}',_escape='>'}),[[ + print(2) + print(3) + print(4) +]]) + +t = [[ +#@ for i = 1,3 do + print(@{i+1}) +#@ end +]] + +asserteq(template.substitute(t,{_brackets='{}',_escape='#@',_inline_escape='@'}),[[ + print(2) + print(3) + print(4) +]]) + +--- iteration using pairs is usually unordered. But using OrderedMap +--- we can get the exact original ordering. + +t = [[ +# for k,v in pairs(T) do + "$(k)", -- $(v) +# end +]] + +if utils.lua51 then + -- easy enough to define a general pairs in Lua 5.1 + local rawpairs = pairs + function pairs(t) + local mt = getmetatable(t) + local f = mt and mt.__pairs + if f then + return f(t) + else + return rawpairs(t) + end + end +end + + +local Tee = OrderedMap{{Dog = 'Bonzo'}, {Cat = 'Felix'}, {Lion = 'Leo'}} + +-- note that the template will also look up global functions using _parent +asserteq(template.substitute(t,{T=Tee,_parent=_G}),[[ + "Dog", -- Bonzo + "Cat", -- Felix + "Lion", -- Leo +]]) + +-- for those with a fondness for Python-style % formatting... +T.format_operator() +asserteq('[%s]' % 'home', '[home]') +asserteq('%s = %d' % {'fred',42},'fred = 42') + +-- mostly works like string.format, except that %s forces use of tostring() +-- rather than throwing an error +local List = require 'pl.List' +asserteq('TBL:%s' % List{1,2,3},'TBL:{1,2,3}') + +-- table with keys and format with $ +asserteq('<$one>' % {one=1}, '<1>') +-- (second arg may also be a function, like os.getenv) +function subst(k) + if k == 'A' then return 'ay' + elseif k == 'B' then return 'bee' + else return '?' + end +end +asserteq( + '$A & $B' % subst,'ay & bee' +) + +t = [[ +a whole lot +of love +]] + +asserteq(T.indent(t,4),[[ + a whole lot + of love +]]) + +asserteq(T.indent([[ +easy + +enough! +]],2,'*'),[[ +**easy +** +**enough! +]]) + + diff --git a/Data/Libraries/Penlight/tests/test-types.lua b/Data/Libraries/Penlight/tests/test-types.lua new file mode 100644 index 0000000..bfb3c8f --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-types.lua @@ -0,0 +1,124 @@ +---- testing types +local types = require 'pl.types' +local asserteq = require 'pl.test'.asserteq +local List = require 'pl.List' + +local list = List() +local array = {10,20,30} +local map = {one=1,two=2} + +-- extended type() function +asserteq(types.type(array),'table') +asserteq(types.type('hello'),'string') +-- knows about Lua file objects +asserteq(types.type(io.stdin),'file') +local f = io.open("tests/test-types.lua") +asserteq(types.type(f),'file') +f:close() +-- and class names +asserteq(types.type(list),'List') +-- tables with unknown metatable +asserteq(types.type(setmetatable({},{})), "unknown table") +-- userdata with unknown metatable +if newproxy then + asserteq(types.type(newproxy(true)), "unknown userdata") +end + +asserteq(types.is_integer(10),true) +asserteq(types.is_integer(10.1),false) +asserteq(types.is_integer(-10),true) +asserteq(types.is_integer(-10.1),false) +-- do note that for Lua < 5.3, 10.0 is the same as 10; an integer. + +asserteq(types.is_callable(asserteq),true) +asserteq(types.is_callable(List),true) + +asserteq(types.is_indexable(array),true) +asserteq(types.is_indexable('hello'),nil) +asserteq(types.is_indexable(10),nil) +if newproxy then + local v = newproxy(true) + local mt = getmetatable(v) + mt.__len = true + mt.__index = true + asserteq(types.is_indexable(v), true) +end +if newproxy then + local v = newproxy(true) + asserteq(types.is_indexable(v), nil) +end + +asserteq(types.is_iterable(array),true) +asserteq(types.is_iterable(true),nil) +asserteq(types.is_iterable(42),nil) +asserteq(types.is_iterable("array"),nil) +if newproxy then + local v = newproxy(true) + local mt = getmetatable(v) + mt.__pairs = true + asserteq(types.is_iterable(v), true) +end +if newproxy then + local v = newproxy(true) + asserteq(types.is_iterable(v), nil) +end + +asserteq(types.is_writeable(array),true) +asserteq(types.is_writeable(true),nil) +asserteq(types.is_writeable(42),nil) +asserteq(types.is_writeable("array"),nil) +if newproxy then + local v = newproxy(true) + local mt = getmetatable(v) + mt.__newindex = true + asserteq(types.is_writeable(v), true) +end +if newproxy then + local v = newproxy(true) + asserteq(types.is_writeable(v), nil) +end + +asserteq(types.is_empty(nil),true) +asserteq(types.is_empty({}),true) +asserteq(types.is_empty({[false] = false}),false) +asserteq(types.is_empty(""),true) +asserteq(types.is_empty(" ",true),true) +asserteq(types.is_empty(" "),false) +asserteq(types.is_empty(true),true) +-- Numbers +asserteq(types.is_empty(0), true) +asserteq(types.is_empty(20), true) +-- Booleans +asserteq(types.is_empty(false), true) +asserteq(types.is_empty(true), true) +-- Functions +asserteq(types.is_empty(print), true) +-- Userdata +--asserteq(types.is_empty(newproxy()), true) --newproxy was removed in Lua 5.2 + +-- a more relaxed kind of truthiness.... +asserteq(types.to_bool('yes'),true) +asserteq(types.to_bool('true'),true) +asserteq(types.to_bool('y'),true) +asserteq(types.to_bool('t'),true) +asserteq(types.to_bool('YES'),true) +asserteq(types.to_bool('1'),true) +asserteq(types.to_bool('no'),false) +asserteq(types.to_bool('false'),false) +asserteq(types.to_bool('n'),false) +asserteq(types.to_bool('f'),false) +asserteq(types.to_bool('NO'),false) +asserteq(types.to_bool('0'),false) +asserteq(types.to_bool(1),true) +asserteq(types.to_bool(0),false) +local de_fr = { 'ja', 'oui' } +asserteq(types.to_bool('ja', de_fr),true) +asserteq(types.to_bool('OUI', de_fr),true) +local t_e = {} +local t_ne = { "not empty" } +asserteq(types.to_bool(t_e,nil,false),false) +asserteq(types.to_bool(t_e,nil,true),false) +asserteq(types.to_bool(t_ne,nil,false),false) +asserteq(types.to_bool(t_ne,nil,true),true) +asserteq(types.to_bool(coroutine.create(function() end),nil,true),true) +asserteq(types.to_bool(coroutine.create(function() end),nil,false),false) diff --git a/Data/Libraries/Penlight/tests/test-url.lua b/Data/Libraries/Penlight/tests/test-url.lua new file mode 100644 index 0000000..9d9af6a --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-url.lua @@ -0,0 +1,37 @@ +local url = require 'pl.url' +local asserteq = require 'pl.test' . asserteq + +asserteq(url.quote(''), '') +asserteq(url.quote('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') +asserteq(url.quote('abcdefghijklmnopqrstuvwxyz'), 'abcdefghijklmnopqrstuvwxyz') +asserteq(url.quote('0123456789'), '0123456789') +asserteq(url.quote(' -_./'), '%20-_./') +asserteq(url.quote('`~!@#$%^&*()'), '%60%7E%21%40%23%24%25%5E%26%2A%28%29') +asserteq(url.quote('%2'), '%252') +asserteq(url.quote('2R%1%%'), '2R%251%25%25') + +asserteq(url.quote('', true), '') +asserteq(url.quote('ABCDEFGHIJKLMNOPQRSTUVWXYZ', true), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') +asserteq(url.quote('abcdefghijklmnopqrstuvwxyz', true), 'abcdefghijklmnopqrstuvwxyz') +asserteq(url.quote('0123456789'), '0123456789', true) +asserteq(url.quote(' -_./', true), '+-_.%2F') +asserteq(url.quote('`~!@#$%^&*()', true), '%60%7E%21%40%23%24%25%5E%26%2A%28%29') +asserteq(url.quote('%2', true), '%252') +asserteq(url.quote('2R%1%%', true), '2R%251%25%25') + +asserteq(url.unquote(''), '') +asserteq(url.unquote('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ') +asserteq(url.unquote('abcdefghijklmnopqrstuvwxyz'), 'abcdefghijklmnopqrstuvwxyz') +asserteq(url.unquote('0123456789'), '0123456789') +asserteq(url.unquote(' -_./'), ' -_./') +asserteq(url.unquote('+-_.%2F'), ' -_./') +asserteq(url.unquote('%20-_./'), ' -_./') +asserteq(url.unquote('%60%7E%21%40%23%24%25%5E%26%2A%28%29'), '`~!@#$%^&*()') +asserteq(url.unquote('%252'), '%2') +asserteq(url.unquote('2%52%1%%'), '2R%1%%') +asserteq(url.unquote('2R%251%25%25'), '2R%1%%') + +asserteq(url.quote(true), true) +asserteq(url.quote(42), 42) +asserteq(url.unquote(true), true) +asserteq(url.unquote(42), 42) diff --git a/Data/Libraries/Penlight/tests/test-utils.lua b/Data/Libraries/Penlight/tests/test-utils.lua new file mode 100644 index 0000000..cff400a --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-utils.lua @@ -0,0 +1,320 @@ +local utils = require 'pl.utils' +local path = require 'pl.path' +local test = require 'pl.test' +local asserteq, T = test.asserteq, test.tuple + + +local function quote(s) + if utils.is_windows then + return '"'..s..'"' + else + return "'"..s.."'" + end +end + +-- construct command to run external lua, we need to to be able to run some +-- tests on the same lua engine, but also need to pass on the LuaCov flag +-- if it was used, to make sure we report the proper coverage. +local cmd = "-e " +do + local i = 0 + while arg[i-1] do + local a = arg[i-1] + if a:find("package%.path") and a:sub(1,1) ~= "'" then + a = quote(a) + end + cmd = a .. " " .. cmd + i = i - 1 + end +end + + +--- quitting +do + local luacode = quote("require([[pl.utils]]).quit([[hello world]])") + local success, code, stdout, stderr = utils.executeex(cmd..luacode) + asserteq(success, false) + if utils.is_windows then + asserteq(code, -1) + else + asserteq(code, 255) + end + asserteq(stdout, "") + asserteq(stderr, "hello world\n") + + local luacode = quote("require([[pl.utils]]).quit(2, [[hello world]])") + local success, code, stdout, stderr = utils.executeex(cmd..luacode) + asserteq(success, false) + asserteq(code, 2) + asserteq(stdout, "") + asserteq(stderr, "hello world\n") + + local luacode = quote("require([[pl.utils]]).quit(2, [[hello %s]], 42)") + local success, code, stdout, stderr = utils.executeex(cmd..luacode) + asserteq(success, false) + asserteq(code, 2) + asserteq(stdout, "") + asserteq(stderr, "hello 42\n") + + local luacode = quote("require([[pl.utils]]).quit(2)") + local success, code, stdout, stderr = utils.executeex(cmd..luacode) + asserteq(success, false) + asserteq(code, 2) + asserteq(stdout, "") + asserteq(stderr, "") +end + +----- importing module tables wholesale --- +utils.import(math) +asserteq(type(sin),"function") +asserteq(type(abs),"function") + +--- useful patterns +local P = utils.patterns +asserteq(("+0.1e10"):match(P.FLOAT) ~= nil, true) +asserteq(("-23430"):match(P.INTEGER) ~= nil, true) +asserteq(("my_little_pony99"):match(P.IDEN) ~= nil, true) + +--- escaping magic chars +local escape = utils.escape +asserteq(escape '[a]','%[a%]') +asserteq(escape '$(bonzo)','%$%(bonzo%)') + +--- choose +asserteq(utils.choose(true, 1, 2), 1) +asserteq(utils.choose(false, 1, 2), 2) + +--- splitting strings --- +local split = utils.split +asserteq(split("hello dolly"),{"hello","dolly"}) +asserteq(split("hello,dolly",","),{"hello","dolly"}) +asserteq(split("hello,dolly,",","),{"hello","dolly"}) + +local first,second = utils.splitv("hello:dolly",":") +asserteq(T(first,second),T("hello","dolly")) +local first,second = utils.splitv("hello:dolly:parton",":", false, 2) +asserteq(T(first,second),T("hello","dolly:parton")) +local first,second,third = utils.splitv("hello=dolly:parton","[:=]") +asserteq(T(first,second,third),T("hello","dolly","parton")) +local first,second = utils.splitv("hello=dolly:parton","[:=]", false, 2) +asserteq(T(first,second),T("hello","dolly:parton")) + +----- table of values to table of strings +asserteq(utils.array_tostring{1,2,3},{"1","2","3"}) +-- writing into existing table +local tmp = {} +utils.array_tostring({1,2,3},tmp) +asserteq(tmp,{"1","2","3"}) + +--- memoizing a function +local kount = 0 +local f = utils.memoize(function(x) + kount = kount + 1 + return x*x +end) +asserteq(f(2),4) +asserteq(f(10),100) +asserteq(f(2),4) +-- actual function only called twice +asserteq(kount,2) + +-- string lambdas +local L = utils.string_lambda +local g = L"|x| x:sub(1,1)" +asserteq(g("hello"),"h") + +local f = L"|x,y| x - y" +asserteq(f(10,2),8) + +-- alternative form for _one_ argument +asserteq(L("2 * _")(4), 8) + +local List = require 'pl.List' +local ls = List{10,20,30} + +-- string lambdas can be used throughout Penlight +asserteq(ls:map"_+1", {11,21,31}) + +-- because they use this common function +local function test_fn_arg(f) + f = utils.function_arg(1,f) + asserteq(f(10),11) +end + +test_fn_arg (function (x) return x + 1 end) +test_fn_arg '_ + 1' +test.assertraise(function() test_fn_arg {} end, 'not a callable object') +test.assertraise(function() test_fn_arg (0) end, 'must be callable') + +-- partial application + +local f1 = utils.bind1(f,10) +asserteq(f1(2), 8) + +local f2 = utils.bind2(f,2) +asserteq(f2(10), 8) + +--- extended type checking + +local is_type = utils.is_type +-- anything without a metatable works as regular type() function +asserteq(is_type("one","string"),true) +asserteq(is_type({},"table"),true) + +-- but otherwise the type of an object is considered to be its metatable +asserteq(is_type(ls,List),true) + +-- compatibility functions +local chunk = utils.load 'return 42' +asserteq(chunk(),42) + +chunk = utils.load 'a = 42' +chunk() +asserteq(a,42) + +local t = {} +chunk = utils.load ('b = 42','<str>','t',t) +chunk() +asserteq(t.b,42) + +chunk,err = utils.load ('a = ?','<str>') +assert(err,[[[string "<str>"]:1: unexpected symbol near '?']]) + +asserteq(utils.quote_arg("foo"), [[foo]]) +if path.is_windows then + asserteq(utils.quote_arg(""), '^"^"') + asserteq(utils.quote_arg('"'), '^"') + asserteq(utils.quote_arg([[ \]]), [[^" \\^"]]) + asserteq(utils.quote_arg([[foo\\ bar\\" baz\]]), [[^"foo\\ bar\\\\\^" baz\\^"]]) + asserteq(utils.quote_arg("%path% ^^!()"), [[^"^%path^% ^^^^^!()^"]]) +else + asserteq(utils.quote_arg(""), "''") + asserteq(utils.quote_arg("'"), [[''\''']]) + asserteq(utils.quote_arg([['a\'b]]), [[''\''a\'\''b']]) +end + +-- packing and unpacking arguments in a nil-safe way +local t = utils.pack(nil, nil, "hello", nil) +asserteq(t.n, 4) -- the last nil does count as an argument + +local arg1, arg2, arg3, arg4 = utils.unpack(t) +assert(arg1 == nil) +assert(arg2 == nil) +asserteq("hello", arg3) +assert(arg4 == nil) + + +-- Assert arguments assert_arg +local ok, err = pcall(function() + utils.assert_arg(4,'!@#$%^&*','string',require("pl.path").isdir,'not a directory') +end) +asserteq(ok, false) +asserteq(err:match("(argument .+)$"), "argument 4: '!@#$%^&*' not a directory") + +local ok, err = pcall(function() + utils.assert_arg(1, "hello", "table") +end) +asserteq(ok, false) +asserteq(err:match("(argument .+)$"), "argument 1 expected a 'table', got a 'string'") + +local ok, err = pcall(function() + return utils.assert_arg(1, "hello", "string") +end) +asserteq(ok, true) +asserteq(err, "hello") + +-- assert_string +local success, err = pcall(utils.assert_string, 2, 5) +asserteq(success, false) +asserteq(err:match("(argument .+)$"), "argument 2 expected a 'string', got a 'number'") + +local x = utils.assert_string(2, "5") +asserteq(x, "5") + + +do + -- printf -- without template + local luacode = quote("require([[pl.utils]]).printf([[hello world]])") + local success, code, stdout, stderr = utils.executeex(cmd..luacode) + asserteq(success, true) + asserteq(code, 0) + asserteq(stdout, "hello world") + asserteq(stderr, "") + + -- printf -- with template + local luacode = quote("require([[pl.utils]]).printf([[hello %s]], [[world]])") + local success, code, stdout, stderr = utils.executeex(cmd..luacode) + asserteq(success, true) + asserteq(code, 0) + asserteq(stdout, "hello world") + asserteq(stderr, "") + + -- printf -- with bad template + local luacode = quote("require([[pl.utils]]).printf(42)") + local success, code, stdout, stderr = utils.executeex(cmd..luacode) + asserteq(success, false) + asserteq(code, 1) + asserteq(stdout, "") + assert(stderr:find("argument 1 expected a 'string', got a 'number'")) +end + +do + -- on_error, raise -- default + utils.on_error("default") + local ok, err = utils.raise("some error") + asserteq(ok, nil) + asserteq(err, "some error") + local ok, err = pcall(utils.on_error, "bad one") + asserteq(ok, false) + asserteq(err, "Bad argument expected string; 'default', 'quit', or 'error'. Got 'bad one'") + + -- on_error, raise -- error + utils.on_error("error") + local ok, err = pcall(utils.raise, "some error") + asserteq(ok, false) + asserteq(err, "some error") + local ok, err = pcall(utils.on_error, "bad one") + asserteq(ok, false) + assert(err:find("Bad argument expected string; 'default', 'quit', or 'error'. Got 'bad one'")) + + -- on_error, raise -- quit + utils.on_error("quit") + local luacode = quote("local u=require([[pl.utils]]) u.on_error([[quit]]) u.raise([[some error]])") + local success, code, stdout, stderr = utils.executeex(cmd..luacode) + asserteq(success, false) + if utils.is_windows then + asserteq(code, -1) + else + asserteq(code, 255) + end + asserteq(stdout, "") + asserteq(stderr, "some error\n") + + local luacode = quote("local u=require([[pl.utils]]) u.on_error([[quit]]) u.on_error([[bad one]])") + local success, code, stdout, stderr = utils.executeex(cmd..luacode) + asserteq(success, false) + if utils.is_windows then + asserteq(code, -1) + else + asserteq(code, 255) + end + asserteq(stdout, "") + asserteq(stderr, "Bad argument expected string; 'default', 'quit', or 'error'. Got 'bad one'\n") + + utils.on_error("default") -- cleanup by restoring behaviour after on_error + raise tests +end + +do + -- readlines + local f = utils.readlines("tests/test-utils.lua") + asserteq(type(f), "table") + local v = "some extraordinary string this is only in this file for test purposes so we can go and find it" + local found = false + for i, line in ipairs(f) do + if line:find(v) then + found = true + break + end + end + asserteq(found, true) +end diff --git a/Data/Libraries/Penlight/tests/test-utils2.lua b/Data/Libraries/Penlight/tests/test-utils2.lua new file mode 100644 index 0000000..897da3e --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-utils2.lua @@ -0,0 +1,53 @@ +local path = require 'pl.path' +local utils = require 'pl.utils' +local asserteq = require 'pl.test'.asserteq + +local echo_lineending = "\n" +if path.is_windows then + echo_lineending = " \n" +end + +local function test_executeex(cmd, expected_successful, expected_retcode, expected_stdout, expected_stderr) +--print("\n"..cmd) +--print(os.execute(cmd)) +--print(utils.executeex(cmd)) + local successful, retcode, stdout, stderr = utils.executeex(cmd) + asserteq(successful, expected_successful) + asserteq(retcode, expected_retcode) + asserteq(stdout, expected_stdout) + asserteq(stderr, expected_stderr) +end + +-- Check the return codes +if utils.is_windows then + test_executeex("exit", true, 0, "", "") + test_executeex("exit 0", true, 0, "", "") + test_executeex("exit 1", false, 1, "", "") + test_executeex("exit 13", false, 13, "", "") + test_executeex("exit 255", false, 255, "", "") + test_executeex("exit 256", false, 256, "", "") + test_executeex("exit 257", false, 257, "", "") + test_executeex("exit 3809", false, 3809, "", "") + test_executeex("exit -1", false, -1, "", "") + test_executeex("exit -13", false, -13, "", "") + test_executeex("exit -255", false, -255, "", "") + test_executeex("exit -256", false, -256, "", "") + test_executeex("exit -257", false, -257, "", "") + test_executeex("exit -3809", false, -3809, "", "") +else + test_executeex("exit", true, 0, "", "") + test_executeex("exit 0", true, 0, "", "") + test_executeex("exit 1", false, 1, "", "") + test_executeex("exit 13", false, 13, "", "") + test_executeex("exit 255", false, 255, "", "") + -- on posix anything other than 0-255 is undefined + test_executeex("exit 256", true, 0, "", "") + test_executeex("exit 257", false, 1, "", "") + test_executeex("exit 3809", false, 225, "", "") +end + +-- Check output strings +test_executeex("echo stdout", true, 0, "stdout" .. echo_lineending, "") +test_executeex("(echo stderr 1>&2)", true, 0, "", "stderr" .. echo_lineending) +test_executeex("(echo stdout && (echo stderr 1>&2))", true, 0, "stdout" .. echo_lineending, "stderr" .. echo_lineending) + diff --git a/Data/Libraries/Penlight/tests/test-utils3.lua b/Data/Libraries/Penlight/tests/test-utils3.lua new file mode 100644 index 0000000..a635de5 --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-utils3.lua @@ -0,0 +1,76 @@ +--- testing Lua 5.1/5.2 compatibility functions +-- these are global side-effects of pl.utils +local utils = require 'pl.utils' +local test = require 'pl.test' +local asserteq = test.asserteq +local _,lua = require 'pl.app'. lua() +local setfenv,getfenv = utils.setfenv, utils.getfenv + +-- utils.execute is a compromise between 5.1 and 5.2 for os.execute changes +-- can we call Lua ? +local ok,code = utils.execute(lua..' -v') +asserteq(ok,true) +asserteq(code,0) +-- does it return false when it fails ? +asserteq(utils.execute('most-likely-nonexistent-command'),false) + +-- table.pack is defined for 5.1 +local t = table.pack(1,nil,'hello') +asserteq(t.n,3) +assert(t[1] == 1 and t[3] == 'hello') + +-- unpack is not globally available for 5.2 unless in compat mode. +-- But utils.unpack is always defined. +local a,b = utils.unpack{10,'wow'} +assert(a == 10 and b == 'wow') + +-- utils.load() is Lua 5.2 style +chunk = utils.load('return x+y','tmp','t',{x=1,y=2}) +asserteq(chunk(),3) + +-- can only load a binary chunk if the mode permits! +local f = string.dump(function() end) +local res,err = utils.load(f,'tmp','t') +test.assertmatch(err,'attempt to load') + +-- package.searchpath for Lua 5.1 +-- nota bene: depends on ./?.lua being in the package.path! +-- So we hack it if not found +if not package.path:find '.[/\\]%?' then + package.path = './?.lua;'..package.path +end + +asserteq( + package.searchpath('tests.test-utils3',package.path):gsub('\\','/'), + './tests/test-utils3.lua' +) + +-- testing getfenv and setfenv for both interpreters + +function test() + return X + Y + Z +end + +t = {X = 1, Y = 2, Z = 3} + +setfenv(test,t) + +assert(test(),6) + +t.X = 10 + +assert(test(),15) + +local getfenv,_G = getfenv,_G + +function test2() + local env = {x=2} + setfenv(1,env) + asserteq(getfenv(1),env) + asserteq(x,2) +end + +test2() + + + diff --git a/Data/Libraries/Penlight/tests/test-xml.lua b/Data/Libraries/Penlight/tests/test-xml.lua new file mode 100644 index 0000000..cd8253a --- /dev/null +++ b/Data/Libraries/Penlight/tests/test-xml.lua @@ -0,0 +1,526 @@ +local xml = require 'pl.xml' +local asserteq = require 'pl.test'.asserteq +local dump = require 'pl.pretty'.dump + +-- Prosody stanza.lua style XML building + +d = xml.new 'top' : addtag 'child' : text 'alice' : up() : addtag 'child' : text 'bob' + +d = xml.new 'children' : + addtag 'child' : + addtag 'name' : text 'alice' : up() : addtag 'age' : text '5' : up() : addtag('toy',{type='fluffy'}) : up() : + up() : + addtag 'child': + addtag 'name' : text 'bob' : up() : addtag 'age' : text '6' : up() : addtag('toy',{type='squeaky'}) + +asserteq( +xml.tostring(d,'',' '), +[[ + +<children> + <child> + <name>alice</name> + <age>5</age> + <toy type='fluffy'/> + </child> + <child> + <name>bob</name> + <age>6</age> + <toy type='squeaky'/> + </child> +</children>]]) + +-- Orbit-style 'xmlification' + +local children,child,toy,name,age = xml.tags 'children, child, toy, name, age' + +d1 = children { + child {name 'alice', age '5', toy {type='fluffy'}}, + child {name 'bob', age '6', toy {type='squeaky'}} +} + +assert(xml.compare(d,d1)) + +-- or we can use a template document to convert Lua data to LOM + +templ = child {name '$name', age '$age', toy{type='$toy'}} + +d2 = children(templ:subst{ + {name='alice',age='5',toy='fluffy'}, + {name='bob',age='6',toy='squeaky'} +}) + +assert(xml.compare(d1,d2)) + +-- Parsing Google Weather service results -- + +local joburg = [[ +<?xml version="1.0"?> +<xml_api_reply version='1'> + <weather module_id='0' tab_id='0' mobile_zipped='1' section='0' row='0' mobile_row='0'> + <forecast_information> + <city data='Johannesburg, Gauteng'/> + <postal_code data='Johannesburg,ZA'/> + <latitude_e6 data=''/> + <longitude_e6 data=''/> + <forecast_date data='2010-10-02'/> + <current_date_time data='2010-10-02 18:30:00 +0000'/> + <unit_system data='US'/> + </forecast_information> + <current_conditions> + <condition data='Clear'/> + <temp_f data='75'/> + <temp_c data='24'/> + <humidity data='Humidity: 19%'/> + <icon data='/ig/images/weather/sunny.gif'/> + <wind_condition data='Wind: NW at 7 mph'/> + </current_conditions> + <forecast_conditions> + <day_of_week data='Sat'/> + <low data='60'/> + <high data='89'/> + <icon data='/ig/images/weather/sunny.gif'/> + <condition data='Clear'/> + </forecast_conditions> + <forecast_conditions> + <day_of_week data='Sun'/> + <low data='53'/> + <high data='86'/> + <icon data='/ig/images/weather/sunny.gif'/> + <condition data='Clear'/> + </forecast_conditions> + <forecast_conditions> + <day_of_week data='Mon'/> + <low data='57'/> + <high data='87'/> + <icon data='/ig/images/weather/sunny.gif'/> + <condition data='Clear'/> + </forecast_conditions> + <forecast_conditions> + <day_of_week data='Tue'/> + <low data='60'/> + <high data='84'/> + <icon data='/ig/images/weather/sunny.gif'/> + <condition data='Clear'/> + </forecast_conditions> + </weather> +</xml_api_reply> + +]] + +-- we particularly want to test the built-in XML parser here, not lxp.lom +local function parse (str) + return xml.parse(str,false,true) +end + +local d = parse(joburg) + + +function match(t,xpect) + local res,ret = d:match(t) + asserteq(res,xpect,0,1) ---> note extra level, so we report on calls to this function! +end + +t1 = [[ + <weather> + <current_conditions> + <condition data='$condition'/> + <temp_c data='$temp'/> + </current_conditions> + </weather> +]] + + + +match(t1,{ + condition = "Clear", + temp = "24", +} ) + +t2 = [[ + <weather> + {{<forecast_conditions> + <day_of_week data='$day'/> + <low data='$low'/> + <high data='$high'/> + <condition data='$condition'/> + </forecast_conditions>}} + </weather> +]] + +local conditions = { + { + low = "60", + high = "89", + day = "Sat", + condition = "Clear", + }, + { + low = "53", + high = "86", + day = "Sun", + condition = "Clear", + }, + { + low = "57", + high = "87", + day = "Mon", + condition = "Clear", + }, + { + low = "60", + high = "84", + day = "Tue", + condition = "Clear", + } +} + +match(t2,conditions) + + +config = [[ +<config> + <alpha>1.3</alpha> + <beta>10</beta> + <name>bozo</name> +</config> +]] +d,err = parse(config) +if not d then print(err); os.exit(1) end + +-- can match against wildcard tag names (end with -) +-- can be names +match([[ +<config> + {{<key->$value</key->}} +</config> +]],{ + {key="alpha", value = "1.3"}, + {key="beta", value = "10"}, + {key="name",value = "bozo"}, +}) +-- can be numerical indices +match([[ +<config> + {{<1->$2</1->}} +</config> +]],{ + {"alpha","1.3"}, + {"beta","10"}, + {"name","bozo"}, +}) +-- _ is special; means 'this value is key of captured table' +match([[ +<config> + {{<_->$1</_->}} +</config> +]],{ + alpha = {"1.3"}, + beta = {"10"}, + name = {"bozo"}, +}) + +-- the numerical index 0 is special: a capture of {[0]=val} becomes simply the value val +match([[ +<config> + {{<_->$0</_->}} +</config> +]],{ + alpha = "1.3", + name = "bozo", + beta = "10" +}) + +-- this can of course also work with attributes, but then we don't want to collapse! + +config = [[ +<config> + <alpha type='number'>1.3</alpha> + <beta type='number'>10</beta> + <name type='string'>bozo</name> +</config> +]] +d,err = parse(config) +if not d then print(err); os.exit(1) end + +match([[ +<config> + {{<_- type='$1'>$2</_->}} +</config> +]],{ + alpha = {"number","1.3"}, + beta = {"number","10"}, + name = {"string","bozo"}, +}) + +d,err = parse [[ + +<configuremap> + <configure name="NAME" value="ImageMagick"/> + <configure name="LIB_VERSION" value="0x651"/> + <configure name="LIB_VERSION_NUMBER" value="6,5,1,3"/> + <configure name="RELEASE_DATE" value="2009-05-01"/> + <configure name="VERSION" value="6.5.1"/> + <configure name="CC" value="vs7"/> + <configure name="HOST" value="windows-unknown-linux-gnu"/> + <configure name="DELEGATES" value="bzlib freetype jpeg jp2 lcms png tiff x11 xml wmf zlib"/> + <configure name="COPYRIGHT" value="Copyright (C) 1999-2009 ImageMagick Studio LLC"/> + <configure name="WEBSITE" value="http://www.imagemagick.org"/> + +</configuremap> +]] +if not d then print(err); os.exit(1) end +--xml.debug = true + +res,err = d:match [[ +<configuremap> + {{<configure name="$_" value="$0"/>}} +</configuremap> +]] + +asserteq(res,{ + HOST = "windows-unknown-linux-gnu", + COPYRIGHT = "Copyright (C) 1999-2009 ImageMagick Studio LLC", + NAME = "ImageMagick", + LIB_VERSION = "0x651", + VERSION = "6.5.1", + RELEASE_DATE = "2009-05-01", + WEBSITE = "http://www.imagemagick.org", + LIB_VERSION_NUMBER = "6,5,1,3", + CC = "vs7", + DELEGATES = "bzlib freetype jpeg jp2 lcms png tiff x11 xml wmf zlib" +}) + +-- short excerpt from +-- /usr/share/mobile-broadband-provider-info/serviceproviders.xml + +d = parse [[ +<serviceproviders format="2.0"> +<country code="za"> + <provider> + <name>Cell-c</name> + <gsm> + <network-id mcc="655" mnc="07"/> + <apn value="internet"> + <username>Cellcis</username> + <dns>196.7.0.138</dns> + <dns>196.7.142.132</dns> + </apn> + </gsm> + </provider> + <provider> + <name>MTN</name> + <gsm> + <network-id mcc="655" mnc="10"/> + <apn value="internet"> + <dns>196.11.240.241</dns> + <dns>209.212.97.1</dns> + </apn> + </gsm> + </provider> + <provider> + <name>Vodacom</name> + <gsm> + <network-id mcc="655" mnc="01"/> + <apn value="internet"> + <dns>196.207.40.165</dns> + <dns>196.43.46.190</dns> + </apn> + <apn value="unrestricted"> + <name>Unrestricted</name> + <dns>196.207.32.69</dns> + <dns>196.43.45.190</dns> + </apn> + </gsm> + </provider> + <provider> + <name>Virgin Mobile</name> + <gsm> + <apn value="vdata"> + <dns>196.7.0.138</dns> + <dns>196.7.142.132</dns> + </apn> + </gsm> + </provider> +</country> + +</serviceproviders> +]] + +res = d:match [[ + <serviceproviders> + {{<country code="$_"> + {{<provider> + <name>$0</name> + </provider>}} + </country>}} + </serviceproviders> +]] + +asserteq(res,{ + za = { + "Cell-c", + "MTN", + "Vodacom", + "Virgin Mobile" + } +}) + +res = d:match [[ +<serviceproviders> + <country code="$country"> + <provider> + <name>$name</name> + <gsm> + <apn value="$apn"> + <dns>196.43.46.190</dns> + </apn> + </gsm> + </provider> + </country> +</serviceproviders> +]] + +asserteq(res,{ + name = "Vodacom", + country = "za", + apn = "internet" +}) + +d = parse[[ +<!DOCTYPE xml> +<params> +<param> + <name>XXX</name> + <value></value> +</param> +<param> + <name>YYY</name> + <value>1</value> +</param> +</params> +]] + +match([[ +<params> +{{<param> + <name>$_</name> + <value>$0</value> +</param>}} +</params> +]],{XXX = '',YYY = '1'}) + + +-- can always use xmlification to generate your templates... + +local SP, country, provider, gsm, apn, dns = xml.tags 'serviceprovider, country, provider, gsm, apn, dns' + +t = SP{country{code="$country",provider{ + name '$name', gsm{apn {value="$apn",dns '196.43.46.190'}} + }}} + +out = xml.tostring(t,' ',' ') +asserteq(out,[[ + + <serviceprovider> + <country code='$country'> + <provider> + <name>$name</name> + <gsm> + <apn value='$apn'> + <dns>196.43.46.190</dns> + </apn> + </gsm> + </provider> + </country> + </serviceprovider>]]) + +----- HTML is a degenerate form of XML ;) +-- attribute values don't need to be quoted, tags are case insensitive, +-- and some are treated as self-closing + +doc = xml.parsehtml [[ +<BODY a=1> +Hello dolly<br> +HTML is <b>slack</b><br> +</BODY> +]] + +asserteq(xml.tostring(doc),[[ +<body a='1'> +Hello dolly<br/> +HTML is <b>slack</b><br/></body>]]) + +doc = xml.parsehtml [[ +<!DOCTYPE html> +<html lang=en> +<head><!--head man--> +</head> +<body> +</body> +</html> +]] + +asserteq(xml.tostring(doc),"<html lang='en'><head/><body/></html>") + +-- note that HTML mode currently barfs if there isn't whitespace around things +-- like '<' and '>' in scripts. +doc = xml.parsehtml [[ +<html> +<head> +<script>function less(a,b) { return a < b; }</script> +</head> +<body> +<h2>hello dammit</h2> +</body> +</html> +]] + +script = doc:get_elements_with_name 'script' +asserteq(script[1]:get_text(), 'function less(a,b) { return a < b; }') + + +-- test attribute order + +local test_attrlist = xml.new('AttrList',{ + Attr3="Value3", + ['Attr1'] = "Value1", + ['Attr2'] = "Value2", + [1] = 'Attr1', [2] = 'Attr2', [3] = 'Attr3' +}) +asserteq( +xml.tostring(test_attrlist), +"<AttrList Attr1='Value1' Attr2='Value2' Attr3='Value3'/>" +) + + +-- commments +str = [[ +<hello> +<!-- any <i>momentous</i> stuff here --> +dolly +</hello> +]] +doc = parse(str) +asserteq(xml.tostring(doc),[[ +<hello> +dolly +</hello>]]) + + +-- underscores and dashes in attributes + +str = [[ +<hello> + <tag my_attribute='my_value'>dolly</tag> +</hello> +]] +doc = parse(str) + +print(doc) +print(xml.tostring(doc)) + +asserteq(xml.tostring(doc),[[ +<hello><tag my_attribute='my_value'>dolly</tag></hello>]]) + + |