-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhammermoon_tests.lua
executable file
·284 lines (276 loc) · 10.4 KB
/
hammermoon_tests.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
#!/usr/bin/env ./luajit
local cmd=arg[1]
if cmd~='run' and cmd~='clean' and cmd~='runall' and cmd~='pad' then
print('arg needed: "run", "runall", "clean", "pad"')
os.exit(1,1)
end
package.cpath="./?.so;./lib/?.so"
package.path="./?.lua;./?/init.lua;./lib/?.lua;./lib/?/init.lua"
local cwd=debug.getinfo(1).source:match("@?(.*)/") or '.'
package.cpath=package.cpath..';'..cwd..'/lib/?.so'
local lfs=require'lfs'
lfs.chdir(cwd)
local TESTSDIR='tests'
local TIMESTAMPS='tests/.timestamps'
require'hm'
require'hammermoon_app'(function()
hm._core.log.level=4
hm.logger.setGlobalLogLevel(4)
hm.logger.defaultLogLevel=4
local inspect=require'lib.inspect'
local getmetatable,setmetatable,tonumber,tostring,pairs,select,pcall,xpcall,rawset,loadstring,type,setfenv
= getmetatable,setmetatable,tonumber,tostring,pairs,select,pcall,xpcall,rawset,loadstring,type,setfenv
local unpack,tconcat,tremove,tinsert=unpack,table.concat,table.remove,table.insert
local sformat,srep,ipairs=string.format,string.rep,ipairs
local capn,capv={},{}
-- local testGlobals=setmetatable({},{__index=_G})
local baseSandbox=setmetatable({},{__index=_G})
for i=1,10 do
baseSandbox['pl'..i]=function(a)return inspect(a,{depth=i,newline=' ',indent=''})end
baseSandbox['p'..i]=function(a)return inspect(a,{depth=i})end
-- baseSandbox['pl'..i]=function(a)return inspect(a,true,i)end
-- baseSandbox['p'..i]=function(a)return inspect(a,false,i)end
end
local log=hm.logger.new('tests',5)
local require=hm._core.rawrequire
baseSandbox.pl=baseSandbox.pl3 baseSandbox.p=baseSandbox.p3
getmetatable(baseSandbox).__newindex=function()error'!'end
local nullSandbox=setmetatable({print=function()end,require=function()end,test=function()end,terr=function()end},
{__index=baseSandbox,__newindex=function(t,k,v)capn[#capn+1]=k capv[#capv+1]=v end})
local trace=debug.traceback
local sleep
local function cleanfile(lines)
end
local function runfile(lines)
local path=lines.path
log.i('running',path)
local s,output='',{}
local chunkStart,chunkLen=0,0
local inlinePrint=function(...)
local args={...}
for i,a in pairs(args) do
local na,nls=tostring(a):gsub('\n','\n--> ')
args[i]=na chunkLen=chunkLen+nls
end
local nArgs=select('#',...)
for i=1,nArgs do args[i]=args[i] or 'nil' end
local s=sformat(srep('%s ',nArgs),unpack(args))
local first=true
repeat
local i=121
repeat
i=i-1
local c=s:sub(i,i)
until c=='' or c==' ' or c==',' or i<80
output[#output+1]=(first and '--> ' or '--~ ')..s:sub(1,i) chunkLen=chunkLen+1
s=s:sub(i+1)
first=nil
until #s==0
end
local total,passed,failed=0,0,0
local inlineError=function(msg)
failed=failed+1
msg=trace(msg)
log.e(path,msg)
for l in msg:gmatch('(.-)\n') do
if l:sub(1,5)~='stack' then
if l:find('[C]',1,true) then break end
local pre,st,offs,post=l:match('(.-)%[string %"%_%_(%d+)%"%]%:(%d+)(%:.+)')
inlinePrint(pre and (pre..path:sub(2)..':'..(tonumber(st)+tonumber(offs))..post) or l)
end
end
output[#output+1]='--:'..'FAILED:'
end
local inlineTest=function(pred)
total=total+1
local out=output[#output]:gsub('%s+%-%-:.-$','')
if type(pred)=='function' then pred=pred() end
if not pred then log.e(path,' - test failed:',out) end
passed=passed+(pred and 1 or 0)
failed=failed+(pred and 0 or 1)
output[#output]=out..(pred and ' --:ok:' or ' --:FAILED:')
if output[#output-1]=='' then
tremove(output,#output-1)
chunkLen=chunkLen-1
end
end
local fileSandbox
local inlineTestError=function(f,err)
return inlineTest(function()
if type(f)=='string' then f=loadstring(f) end
local ok,perr=pcall(setfenv(f,fileSandbox))
-- local perr=not ok and perr:match('.-:%d+: ([^\n]-)%s*$')
-- if err then print(#err,#perr) end
-- print(err)print(perr)
return ok==false and (err==nil or err==perr:match('.-:%d+: (.-)%s*$'))
end)
end
local sandboxPackages={}
local function sandboxRequire(m)
local isLocal=package.loaded[m]==nil
local r=require(m)
if isLocal then
sandboxPackages[m]=r
log.d('loaded package',m)
end
return r
end
local function setGlobal(t,k,v)
log.v('set global',k)
capn[#capn+1]=k capv[#capv+1]=v
-- testGlobals[k]=v
rawset(t,k,v)
end
fileSandbox=setmetatable({
test=inlineTest,terr=inlineTestError,sleep=sleep,print=inlinePrint,rawprint=print,require=sandboxRequire},
-- {__index=nullSandbox,--[[__newindex=_G--]]}) -- sandbox; don't escape globals (rest is running live anyway!)
{__index=baseSandbox,__newindex=setGlobal}) -- sandbox; DO escape globals as required for whole-pad evalling
nullSandbox.require=sandboxRequire
getmetatable(nullSandbox).__index=fileSandbox
local lastError
for _,line in ipairs(lines) do
-- chunkLines=chunkLines+1
-- line=line:gsub('%-%-%>.*','') -- remove generated output
local lineprefix=line:sub(1,3)
if lineprefix~='-->' and lineprefix~='--~' and lineprefix~='--:' then
-- line=line:gsub('%-%-%>.-\n','\n') --remove test results
s=s..line..'\n'
chunkLen=chunkLen+1
-- remove all comments and newlines
local evals=s:gsub('%-%-%[%[.-%-%-%]%]',''):gsub('%-%-.-\n','\n'):gsub('%s+',' ')
local chunk,err
if #evals:gsub('%s','')==0 then
output[#output+1]=s:sub(1,-2)
s='' chunkStart=chunkStart+chunkLen chunkLen=0
else
chunk,err=loadstring(s,'__'..chunkStart)
if not chunk then lastError=err else --valid, proceed with eval
lastError=nil
output[#output+1]=s:sub(1,-2)
-- chunkLen=chunkLen+1
local retChunk,err=loadstring('return ('..evals..')')
if retChunk then -- it's an expression, print the result
log.v(' eval exp:',evals)
local ok,res=xpcall(setfenv(retChunk,fileSandbox),inlineError)
if ok and res~=nil then
-- if type(res)~='string' then res=baseSandbox.pl1(res) end
inlinePrint(tostring(res))
end
else
log.v(' eval: ',evals)
local ok,res=xpcall(setfenv(chunk,fileSandbox),inlineError)
if ok then
for i=1, #capn do
-- local s=tostring(capv[i])
-- if type(s)~='string' then s=nullSandbox.pl1(capv[i]) end
-- inlinePrint(capn[i]..' = '..nullSandbox.pl1(capv[i]))
inlinePrint(capn[i]..' = '..tostring(capv[i]))
end
-- if ok and capn then print(capn..': '..tostring(capv))
else --TODO sethook, getlocal
-- inlinePrint'?'
inlinePrint(res)
end
end
capn,capv={},{}
s='' chunkStart=chunkStart+chunkLen chunkLen=0
end
end
end
end
if lastError then
-- out=out..s
output[#output+1]='' output[#output+1]=s:sub(1,-2)
inlinePrint(lastError)
end
inlinePrint(total,'total tests,',passed,'passed,',failed,'failed')
log.i(total,'total tests,',passed,'passed,',failed,'failed')
for m in pairs(sandboxPackages) do package.loaded[m]=nil log.d('unloaded package',m) end
local f=io.open(path,'w')
f:write(tconcat(output,'\n')..'\n') f:flush() f:close()
return total,passed,failed
end
if cmd=='pad' then
hm.timer.log.level=1
local timestamp=0
local path=arg[2] or 'scratchpad.lua'
while true do
repeat
hm.timer.sleep(1)
local pathmod=lfs.attributes(path,'modification')
until pathmod>timestamp
local lines={path=path}
for line in io.lines(path) do lines[#lines+1]=line end
runfile(lines)
timestamp=os.time()
end
os.exit(1,1)
end
local total,passed,failed=0,0,0
local timestamps={}
-- if not lfs.attributes(TIMESTAMPS) then
-- local f=io.open(TIMESTAMPS,'w') f:write'' f:close()
-- end
for line in io.lines(TIMESTAMPS) do
local testfile,testtime=line:match('^([%l._/]+)%s*(%d+)%s*$')
-- assert(testfile,testtime)
timestamps[testfile]=tonumber(testtime)
if cmd=="runall" or cmd=="clean" then timestamps[testfile]=0 end
end
local files={}
for file in lfs.dir(TESTSDIR) do
local path=TESTSDIR..'/'..file
if lfs.attributes(path,'mode')=='file' and path:sub(-9)=='.test.lua' then
local lines={}
local mtime=lfs.attributes(path,'modification')
timestamps[path]=timestamps[path] or 0
if timestamps[path]<mtime then lines.path=path end
-- files[#files+1]=path
for line in io.lines(path) do
local reqpath=line:match('^%-%-@file ([%l._/]+)')
if reqpath then
local mtime=lfs.attributes(reqpath,'modification')
if mtime>timestamps[path] then lines.path=path end
end
lines[#lines+1]=line
end
if lines.path then files[#files+1]=lines end
end
end
local nfiles=#files
if cmd=="clean" then
local f=io.open(TIMESTAMPS,'w') f:write'' f:close()
for _,lines in ipairs(files) do
local out={}
for _,line in ipairs(lines) do
local subbed
line=line:gsub('%-%-[~>:].+$',function()subbed=true return '' end)
if not subbed or #line>0 then out[#out+1]=line end
end
local f=io.open(lines.path,'w')
f:write(tconcat(out,'\n')..'\n') f:flush() f:close()
end
os.exit(1,1)
else
local runnerCoro=coroutine.wrap(function()
while files[1] do
local t,p,f=runfile(files[1])
timestamps[files[1].path]=f>0 and 0 or os.time()
total=total+t passed=passed+p failed=failed+f
tremove(files,1)
end
local f=io.open(TIMESTAMPS,'w')
for testfile,timestamp in pairs(timestamps) do f:write(sformat('%s %d\n',testfile,timestamp)) end
f:flush() f:close()
log.w(nfiles,' files processed')
log.w(total,'total tests,',passed,'passed,',failed,'failed')
os.exit(1,1)
end)
local sleepTimer=hm.timer.new(runnerCoro)
sleep=function(s)
sleepTimer:runIn(s)
coroutine.yield(true)
end
runnerCoro()
end
end)