summaryrefslogtreecommitdiff
path: root/test/test-lace.engine.lua
blob: 7b8a055e236e496761e529af817afe01746b0b01 (plain)
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
314
315
316
317
318
319
320
321
322
323
-- test/test-lace.engine.lua
--
-- Lua Access Control Engine -- Tests for the ruleset runtime engine
--
-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
--
-- For Licence terms, see COPYING
--

-- Step one, start coverage

pcall(require, 'luacov')

local lace = require 'lace'

local testnames = {}

local real_assert = assert
local total_asserts = 0
local function assert(...)
   real_assert(...)
   total_asserts = total_asserts + 1
end

local function add_test(suite, name, value)
   rawset(suite, name, value)
   testnames[#testnames+1] = name
end

local suite = setmetatable({}, {__newindex = add_test})

function suite.check_can_define_something()
   local ctx = {}
   local result, msg = lace.engine.define(ctx, "this", "that")
   assert(result == true, "Couldn't define something")
end

function suite.check_cannot_redefine_something()
   local ctx = {}
   local result, msg = lace.engine.define(ctx, "this", "that")
   assert(result == true, "Couldn't define something")
   local result, msg = lace.engine.define(ctx, "this", "that")
   assert(result == false, "Should not have been able to redefine this")
   assert(type(msg) == "table", "Internal errors should be tables")
   assert(type(msg.msg) == "string", "Internal errors should have message strings")
   assert(msg.msg:match("to redefine"), "Error didn't mention redefinition")
end

function suite.check_cannot_test_unknown_values()
   local ctx = {}
   local result, msg = lace.engine.test(ctx, "this")
   assert(result == nil, "Internal errors should return nil")
   assert(type(msg) == "table", "Internal errors should return tables")
   assert(type(msg.msg) == "string", "Internal errors should have message strings")
   assert(msg.msg:match("nknown definition"), "Error did not mention unknown definitions")
end

function suite.check_can_test_known_functions()
   local ctx = {}
   local function run_test(ctx, arg)
      assert(arg == "FISH", "Argument not passed properly")
      ctx.ran = true
      return "fish", "blah"
   end
   local result, msg = lace.engine.define(ctx, "this", { fn = run_test, args = { "FISH" } })
   assert(result == true, "Could not make definition?")
   local result, msg = lace.engine.test(ctx, "this")
   assert(result == "fish", "Expected result was not returned")
   assert(msg == "blah", "Expected message was not returned")
   assert(ctx.ran, "Context was not passed properly")
end

function suite.check_bad_exec_fn_returns_nil()
   local function _explode()
      return { fn = function() error("EXPLODE") end, args = {} }
   end
   local compctx = {_lace={commands={explode=_explode}}}
   local ruleset, msg = lace.compiler.compile(compctx, "src", "explode\nallow because")
   assert(type(ruleset) == "table", "Could not compile exploding ruleset")
   local execctx = {}
   local result, msg = lace.engine.run(ruleset, execctx)
   assert(result == nil, "Lua failures should return nil")
   assert(msg:match("EXPLODE"), "Expected explosion not detected")
end

function suite.check_error_propagates()
   local function _explode()
      return { fn = function() return lace.error.error("EXPLODE", {1}) end, args = {} }
   end
   local compctx = {_lace={commands={explode=_explode}}}
   local ruleset, msg = lace.compiler.compile(compctx, "src", "explode\nallow because")
   assert(type(ruleset) == "table", "Could not compile exploding ruleset")
   local execctx = {}
   local result, msg = lace.engine.run(ruleset, execctx)
   assert(result == false, "Internal failures should return false")
   assert(msg:match("EXPLODE"), "Expected explosion not detected")
end

function suite.check_deny_works()
   local compctx = {_lace={}}
   local ruleset, msg = lace.compiler.compile(compctx, "src", "deny everything")
   assert(type(ruleset) == "table", "Could not compile exploding ruleset")
   local execctx = {}
   local result, msg = lace.engine.run(ruleset, execctx)
   assert(result == "deny", "Denial not returned")
   assert(msg:match("everything"), "Expected reason not detected")
end

-- More complete engine tests from here

local comp_context = {
   _lace = {
      loader = function(ctx, name)
		  if name == "THROW_ERROR" then
		     error("THROWN")
		  end
		  local fh = io.open("test/test-lace.engine-" .. name .. ".rules", "r")
		  if not fh then
		     return lace.error.error("LOADER: Unknown: " .. name, {1})
		  end
		  local content = fh:read("*a")
		  fh:close()
		  return "real-" .. name, content
	       end,
      commands = {
      },
      controltype = {
	 equal = function(ctx, eq, key, value)
		    return {
		       fn = function(ectx, ekey, evalue)
			       return ectx[ekey] == evalue
			    end,
		       args = { key, value },
		    }
		 end,
	 error = function(ctx, err)
		    return {
		       fn = function(ectx)
			       if ectx.error then
				  return nil, { msg = "woah", words = {1} }
			       end
			       return false
			    end,
		       args = {},
		    }
		 end,
      },
   },
}

function suite.test_plainallow_works()
   local ruleset, msg = lace.compiler.compile(comp_context, "plainallow")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == "allow", "Should allow")
   assert(msg == "because", "Because")
end

function suite.test_allow_with_define_works()
   local ruleset, msg = lace.compiler.compile(comp_context, "allowwithdefine")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == "allow", "Should allow")
   assert(msg == "because", "Because")
end

function suite.test_allow_with_define_used_works()
   local ruleset, msg = lace.compiler.compile(comp_context, "allowwithdefineused")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == "deny", "Should deny")
   assert(msg == "Default behaviour", "Because allow failed")
end

function suite.test_allow_with_define_used_works_and_passes()
   local ruleset, msg = lace.compiler.compile(comp_context, "allowwithdefineused")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {this="that"}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == "allow", "Should allow")
   assert(msg == "because", "Because")
end

function suite.test_complex_ruleset()
   local ruleset, msg = lace.compiler.compile(comp_context, "complexruleset")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   for _, s in ipairs{"one","two","three","four"} do
      local expect = (s == "one" or s == "two") and "allow" or "deny"
      local ectx = {state=s}
      local result, msg = lace.engine.run(ruleset, ectx)
      assert(result == expect, "Expected " .. expect)
      assert(msg == s, "Reason expected " .. s)
   end
end

function suite.test_runtime_error()
   local ruleset, msg = lace.compiler.compile(comp_context, "runtimeerror")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {error=true}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == false, "Did not error out")
   assert(type(msg) == "string", "Generated a non-string error")
   assert(msg:find("woah"), "Did not generate the right error: " .. msg)
end

function suite.doubledefine()
   local ruleset, msg = lace.compiler.compile(comp_context, "doubledefine")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {error = true}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == false, "Did not error out")
   local line1, line2, line3, line4 = msg:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$")
   assert(line1:find("redefine fish"), "The first line must mention the error")
   assert(line2 == "real-doubledefine :: 5", "The second line is where the error happened")
   assert(line3 == "define fish equal state two", "The third line is the original line")
   assert(line4 == "       ^^^^                ", "The fourth line highlights relevant words")
end

function suite.unknowndefine()
   local ruleset, msg = lace.compiler.compile(comp_context, "unknowndefine")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {error = true}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == false, "Did not error out")
   local line1, line2, line3, line4 = msg:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$")
   assert(line1:find("definition: fish"), "The first line must mention the error")
   assert(line2 == "real-unknowndefine :: 3", "The second line is where the error happened")
   assert(line3 == "allow anyway fish", "The third line is the original line")
   assert(line4 == "             ^^^^", "The fourth line highlights relevant words")
end

function suite.unknownanyof()
   local ruleset, msg = lace.compiler.compile(comp_context, "unknownanyof")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {error = true}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == false, "Did not error out")
   local line1, line2, line3, line4 = msg:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$")
   assert(line1:find("definition: pants"), "The first line must mention the error")
   assert(line2 == "real-unknownanyof :: 5", "The second line is where the error happened")
   assert(line3 == "allow anyway fish", "The third line is the original line")
   assert(line4 == "             ^^^^", "The fourth line highlights relevant words")
end

function suite.subdefine_works()
   local ruleset, msg = lace.compiler.compile(comp_context, "subdefine-works")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {jeff = "geoff"}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result, msg)
end

function suite.subdefine_err_reported()
   local ruleset, msg = lace.compiler.compile(comp_context, "subdefine-error")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {error = true}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == false, "Did not error out")
   local line1, line2, line3, line4 = msg:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$")
   assert(line1 == "woah", "The first line must mention the error")
   assert(line2 == "real-subdefine-error :: 2", "The second line is where the error happened")
   assert(line3 == 'allow "Yay" [error]', "The third line is the original line")
   assert(line4 == "             ^^^^^ ", "The fourth line highlights relevant words")
end

function suite.subsubdefine_works()
   local ruleset, msg = lace.compiler.compile(comp_context, "subsubdefine-works")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {jeff = "geoff"}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result, msg)
   assert(result == "allow", "Result should be allow")
   assert(msg == "PASS", "Message should be pass")
end

function suite.subsubdefine_err_reported()
   local ruleset, msg = lace.compiler.compile(comp_context, "subsubdefine-error")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {error = true}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == false, "Did not error out")
   local line1, line2, line3, line4 = msg:match("^([^\n]*)\n([^\n]*)\n([^\n]*)\n([^\n]*)$")
   assert(line1 == "woah", "The first line must mention the error")
   assert(line2 == "real-subsubdefine-error :: 1", "The second line is where the error happened")
   assert(line3 == 'allow "FAIL" [anyof [equal jeff banana] [error]]', "The third line is the original line")
   assert(line4 == "                                         ^^^^^  ", "The fourth line highlights relevant words")
end

function suite.subdefine_chained_err_reported()
   local ruleset, msg = lace.compiler.compile(comp_context, "chaindefine-error")
   assert(type(ruleset) == "table", "Ruleset did not compile")
   local ectx = {error = true}
   local result, msg = lace.engine.run(ruleset, ectx)
   assert(result == false, "Did not error out")
   local lines = {}
   msg:gsub("([^\n]*)\n?", function(c) table.insert(lines, c) end)
   assert(lines[1] == "woah", "The first line must mention the error")
   assert(lines[2] == "real-chaindefine-error :: 2", "The second line is where the error happened")
   assert(lines[3] == 'allow "FAIL" [anyof [equal jeff banana] bogus]', "The third line is the original line")
   assert(lines[4] == "                                        ^^^^^ ", "The fourth line highlights relevant words")
   assert(lines[5] == "real-chaindefine-error :: 1", "The fifth line is where the definition that errored comes from")
   assert(lines[6] == 'define bogus error', "The sixth line is the define")
   assert(lines[7] == "             ^^^^^", "The seventh line highlights relevant words")
end

local count_ok = 0
for _, testname in ipairs(testnames) do
--   print("Run: " .. testname)
   local ok, err = xpcall(suite[testname], debug.traceback)
   if not ok then
      print(err)
      print()
   else
      count_ok = count_ok + 1
   end
end

print(tostring(count_ok) .. "/" .. tostring(#testnames) .. " [" .. tostring(total_asserts) .. "] OK")

os.exit(count_ok == #testnames and 0 or 1)