Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Penlight/lua/pl/test.lua
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
164 lines (146 sloc)
5.03 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --- Useful test utilities. | |
| -- | |
| -- test.asserteq({1,2},{1,2}) -- can compare tables | |
| -- test.asserteq(1.2,1.19,0.02) -- compare FP numbers within precision | |
| -- T = test.tuple -- used for comparing multiple results | |
| -- test.asserteq(T(string.find(" me","me")),T(2,3)) | |
| -- | |
| -- Dependencies: `pl.utils`, `pl.tablex`, `pl.pretty`, `pl.path`, `debug` | |
| -- @module pl.test | |
| local tablex = require 'pl.tablex' | |
| local utils = require 'pl.utils' | |
| local pretty = require 'pl.pretty' | |
| local path = require 'pl.path' | |
| local type,unpack,pack = type,utils.unpack,utils.pack | |
| local clock = os.clock | |
| local debug = require 'debug' | |
| local io = io | |
| local function dump(x) | |
| if type(x) == 'table' and not (getmetatable(x) and getmetatable(x).__tostring) then | |
| return pretty.write(x,' ',true) | |
| elseif type(x) == 'string' then | |
| return '"'..x..'"' | |
| else | |
| return tostring(x) | |
| end | |
| end | |
| local test = {} | |
| ---- error handling for test results. | |
| -- By default, this writes to stderr and exits the program. | |
| -- Re-define this function to raise an error and/or redirect output | |
| function test.error_handler(file,line,got_text, needed_text,msg) | |
| local err = io.stderr | |
| err:write(path.basename(file)..':'..line..': assertion failed\n') | |
| err:write("got:\t",got_text,'\n') | |
| err:write("needed:\t",needed_text,'\n') | |
| utils.quit(1,msg or "these values were not equal") | |
| end | |
| local function complain (x,y,msg,where) | |
| local i = debug.getinfo(3 + (where or 0)) | |
| test.error_handler(i.short_src,i.currentline,dump(x),dump(y),msg) | |
| end | |
| --- general test complain message. | |
| -- Useful for composing new test functions (see tests/tablex.lua for an example) | |
| -- @param x a value | |
| -- @param y value to compare first value against | |
| -- @param msg message | |
| -- @param where extra level offset for errors | |
| -- @function complain | |
| test.complain = complain | |
| --- like assert, except takes two arguments that must be equal and can be tables. | |
| -- If they are plain tables, it will use tablex.deepcompare. | |
| -- @param x any value | |
| -- @param y a value equal to x | |
| -- @param eps an optional tolerance for numerical comparisons | |
| -- @param where extra level offset | |
| function test.asserteq (x,y,eps,where) | |
| local res = x == y | |
| if not res then | |
| res = tablex.deepcompare(x,y,true,eps) | |
| end | |
| if not res then | |
| complain(x,y,nil,where) | |
| end | |
| end | |
| --- assert that the first string matches the second. | |
| -- @param s1 a string | |
| -- @param s2 a string | |
| -- @param where extra level offset | |
| function test.assertmatch (s1,s2,where) | |
| if not s1:match(s2) then | |
| complain (s1,s2,"these strings did not match",where) | |
| end | |
| end | |
| --- assert that the function raises a particular error. | |
| -- @param fn a function or a table of the form {function,arg1,...} | |
| -- @param e a string to match the error against | |
| -- @param where extra level offset | |
| function test.assertraise(fn,e,where) | |
| local ok, err | |
| if type(fn) == 'table' then | |
| ok, err = pcall(unpack(fn)) | |
| else | |
| ok, err = pcall(fn) | |
| end | |
| if ok or err:match(e)==nil then | |
| complain (err,e,"these errors did not match",where) | |
| end | |
| end | |
| --- a version of asserteq that takes two pairs of values. | |
| -- <code>x1==y1 and x2==y2</code> must be true. Useful for functions that naturally | |
| -- return two values. | |
| -- @param x1 any value | |
| -- @param x2 any value | |
| -- @param y1 any value | |
| -- @param y2 any value | |
| -- @param where extra level offset | |
| function test.asserteq2 (x1,x2,y1,y2,where) | |
| if x1 ~= y1 then complain(x1,y1,nil,where) end | |
| if x2 ~= y2 then complain(x2,y2,nil,where) end | |
| end | |
| -- tuple type -- | |
| local tuple_mt = { | |
| unpack = unpack | |
| } | |
| tuple_mt.__index = tuple_mt | |
| function tuple_mt.__tostring(self) | |
| local ts = {} | |
| for i=1, self.n do | |
| local s = self[i] | |
| ts[i] = type(s) == 'string' and ('%q'):format(s) or tostring(s) | |
| end | |
| return 'tuple(' .. table.concat(ts, ', ') .. ')' | |
| end | |
| function tuple_mt.__eq(a, b) | |
| if a.n ~= b.n then return false end | |
| for i=1, a.n do | |
| if a[i] ~= b[i] then return false end | |
| end | |
| return true | |
| end | |
| function tuple_mt.__len(self) | |
| return self.n | |
| end | |
| --- encode an arbitrary argument list as a tuple. | |
| -- This can be used to compare to other argument lists, which is | |
| -- very useful for testing functions which return a number of values. | |
| -- Unlike regular array-like tables ('sequences') they may contain nils. | |
| -- Tuples understand equality and know how to print themselves out. | |
| -- The # operator is defined to be the size, irrespecive of any nils, | |
| -- and there is an `unpack` method. | |
| -- @usage asserteq(tuple( ('ab'):find 'a'), tuple(1,1)) | |
| function test.tuple(...) | |
| return setmetatable(pack(...), tuple_mt) | |
| end | |
| --- Time a function. Call the function a given number of times, and report the number of seconds taken, | |
| -- together with a message. Any extra arguments will be passed to the function. | |
| -- @string msg a descriptive message | |
| -- @int n number of times to call the function | |
| -- @func fun the function | |
| -- @param ... optional arguments to fun | |
| function test.timer(msg,n,fun,...) | |
| local start = clock() | |
| for i = 1,n do fun(...) end | |
| utils.printf("%s: took %7.2f sec\n",msg,clock()-start) | |
| end | |
| return test |