This repository has been archived by the owner on Nov 20, 2020. It is now read-only.
forked from keplerproject/shake
-
Notifications
You must be signed in to change notification settings - Fork 0
/
shake.lua
313 lines (272 loc) · 10.6 KB
/
shake.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
-------------------------------------------------------------------------------
-- Shake, a simple test engine for Lua
--
-- Authors: Andre Carregal, Humberto dos Anjos
-- Copyright (c) 2007 Kepler Project
--
-- $Id: shake.lua,v 1.16 2008/07/04 21:11:59 carregal Exp $
-------------------------------------------------------------------------------
local io = require "io"
local lfs = require "lfs"
local table = require "table"
local string = require "string"
local _G, error, unpack, loadstring, pcall, xpcall, ipairs, setmetatable, setfenv, loadfile, dofile =
_G, error, unpack, loadstring, pcall, xpcall, ipairs, setmetatable, setfenv, loadfile, dofile
require "shake.stir"
-- tries to get the debug table
local debug = debug
if not next(debug) then
-- uses a stub when debug information is not available
debug = {
getinfo = function() return {linenumber = "???"} end,
traceback = function() return "No traceback information: debug is not available" end,
}
end
local getinfo = debug.getinfo
local traceback = debug.traceback
module(...)
_COPYRIGHT = "Copyright (C) 2007 Kepler Project"
_DESCRIPTION = "Shake is a simple and transparent test engine for Lua that assumes that tests only use standard assert and print calls."
_VERSION = "Shake 1.0.2"
----------- local functions ------------
-- Version of loadstring that stirs the file before compiling it
local function _loadstring(s, filename)
s = string.gsub(s, "^#![^\n]*\n", "-- keeps one line in place of an eventual one with a #! at the start\n")
s = stir(s)
return loadstring(s, filename)
end
-- Version of loadfile that stirs the file before compiling it
local function _loadfile(filename)
local file, func, errmsg
file, errmsg = io.open(filename)
if not file then
return nil, errmsg
else
local str = file:read'*a'
func, errmsg = _loadstring(str, filename)
end
return func, errmsg
end
-- Version of dostring that stirs the string before executing it
local function _dostring(s, filename)
local results = {pcall(_loadstring(s, filename))}
if results[1] then
table.remove(results, 1)
end
return unpack(results)
end
-- Version of dofile that stirs the file before executing it
local function _dofile(filename)
local results = {pcall(_loadfile(filename))}
if results[1] then
table.remove(results, 1)
end
return unpack(results)
end
-- Returns a new suite
local function _newsuite(filename, title, errmsg)
local source = {}
if not errmsg then
for line in io.lines(filename) do
source[#source + 1] = line
end
end
return {title = title, filename = filename, passed = 0, failed = 0, error = errmsg, source = source, contexts = {} }
end
-- Returns a new context
local function _newcontext(...)
return {output = {...}, passed = 0, failed = 0, tests = {} }
end
-- Returns a contextualized assert()
function _newassert(suite, context)
return function(val1, op, val2, msg, exp1, exp2, comments)
if comments then
context = _newcontext(comments)
suite.contexts[#suite.contexts + 1] = context
end
local test = { message = msg or "" }
context.tests[#context.tests + 1] = test
local PASSED = false
if not op then
PASSED = val1
elseif op == '==' then
PASSED = val1 == val2
elseif op == '~=' then
PASSED = val1 ~= val2
end
if PASSED then
context.passed = context.passed + 1
suite.passed = suite.passed + 1
else
context.failed = context.failed + 1
suite.failed = suite.failed + 1
end
test.val1 = val1
test.op = op
test.val2 = val2
test.exp1 = exp1
test.exp2 = exp2
test.msg = msg
test.passed = PASSED
test.linenumber = getinfo(2, "l").currentline
test.traceback = traceback("", 2)
return PASSED, msg
end
end
-------------------------------------------------------------------------------
-- Runs a suite of tests from filename using a title
-- Test results are added to the results table
-------------------------------------------------------------------------------
local function _test(self, filename, title)
f, errmsg = _loadfile(filename)
local results = self.results
title = title or ""
if not f then
-- error loading the file
errmsg = string.gsub(errmsg, '%[string "'..filename..'"%]', filename)
results.suites[#results.suites + 1] = _newsuite(filename, title, errmsg)
results.errors = results.errors + 1
else
-- runs the test suite
local _print = _G.print
local _write = _G.io.write
local ___STIR_assert = _G.___STIR_assert
local lf = _G.loadfile
local df = _G.dofile
local ls = _G.loadstring
local ds = _G.dostring
_G.loadfile = _loadfile
_G.dofile = _dofile
_G.loadstring = _loadstring
_G.dostring = _dostring
local suite = _newsuite(filename, title)
local context = _newcontext("")
_G.___STIR_assert = _newassert(suite, context) -- so assertions works even without a previous context
suite.contexts[#suite.contexts + 1] = context
-- separate contexts at every print or io.write
-- keeping the output stored in the context table
_G.print = function(...)
local context = suite.contexts[#suite.contexts]
if context.passed + context.failed > 0 then
-- create a new context if there was an assert before the previous context
context = _newcontext(...)
suite.contexts[#suite.contexts + 1] = context
else
-- converts all parameters to strings
local temp = {}
for i = 1, _G.select('#',...) do
table.insert(temp, _G.tostring(_G.select(i,...)))
end
-- and concatenates them
context.output[#context.output + 1] = table.concat(temp, "\t")
end
_G.___STIR_assert = _newassert(suite, context)
end
_G.io.write = _G.print
-- executes the suite
local res, errmsg = xpcall(f, function(err) return err end)
if not res then
-- error executing the suite
errmsg = errmsg or ""
suite.error = string.gsub(errmsg, '%[string "'..filename..'"%]', filename)
results.errors = results.errors + 1
end
results.passed = results.passed + suite.passed
results.failed = results.failed + suite.failed
results.suites[#results.suites + 1] = suite
-- restores the environment
_G.loadfile = lf
_G.dofile = df
_G.loadstring = ls
_G.dostring = ds
_G.print = _print
_G.io.write = _write
_G.___STIR_assert = ___STIR_assert
end
end
-------------------------------------------------------------------------------
-- Displays values as nice strings
-------------------------------------------------------------------------------
local function _tostring(s)
if type(s) == "string" then
return [["]]..s..[["]]
else
return _G.tostring(s)
end
end
-------------------------------------------------------------------------------
-- Returns a summary of the test results using an optional line separator
-------------------------------------------------------------------------------
local function _summary(self, sep)
local out = {}
local results = self.results
sep = sep or "\n"
for cs, suite in ipairs(results.suites) do
if suite.error then
out[#out + 1] = ">>>>>>>>>>>>>>>> "..suite.title.." "..suite.filename.." has an error!!!".." <<<<<<<<<<<<<<<<"
out[#out + 1] = ""
out[#out + 1] = suite.error
out[#out + 1] = ""
elseif suite.failed > 0 then
out[#out + 1] = "---------------- "..suite.title.." "..suite.filename.." failed!".." ----------------"
for cg, context in ipairs(suite.contexts) do
if context.failed > 0 then
out[#out + 1] = ""
for _, output in ipairs(context.output) do
out[#out + 1] = _G.tostring(output)
end
if context.comments then
out[#out + 1] = context.comments
end
for ct, test in ipairs (context.tests) do
if not test.passed then
if suite.source[test.linenumber] then
out[#out + 1] = " #"..test.linenumber.." "..suite.source[test.linenumber]
end
if not isTerminal(test.exp1, test.val1) then
out[#out + 1] = " "..test.exp1.." -> ".._tostring(test.val1)
end
if not isTerminal(test.exp2, test.val2) then
out[#out + 1] = " "..test.exp2.." -> ".._tostring(test.val2)
end
end
end
end
end
else
out[#out + 1] = "-> "..suite.title.." "..suite.filename.." OK!"
end
end
out[#out + 1] = "_________________"
out[#out + 1] = ""
out[#out + 1] = "Tests: "..results.failed + results.passed
out[#out + 1] = "Failures: "..results.failed
out[#out + 1] = "Errors: "..results.errors
out[#out + 1] = ""
return table.concat(out, sep)
end
---------- Public functions --------------
-------------------------------------------------------------------------------
-- Returns a new runner with the functions
-- test(filename)
-- summary()
-------------------------------------------------------------------------------
function runner()
local runner = {results = {passed = 0, failed = 0, errors = 0, suites = {} } }
setmetatable(runner, {__index = {test = _test, summary = _summary} })
return runner
end
-------------------------------------------------------------------------------
-- Checks if an expression string represents a terminal value
-------------------------------------------------------------------------------
function isTerminal(exp, val)
if not exp then return true end
local chunk = loadstring('return '..exp)
local env = {}
setmetatable(env, {__index = function() return "___nil___" end})
setfenv(chunk, env)
local status, ret = pcall(chunk)
if status then
return ret == val
end
end