summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Silverstone <dsilvers@digital-scurf.org>2012-05-13 20:22:02 +0100
committerDaniel Silverstone <dsilvers@digital-scurf.org>2012-05-13 20:22:02 +0100
commitd7f07925dc1546e4f881d08d1bc6604258317998 (patch)
tree3951edd578462b3429dc4ae3c50417d5bb69216e
parent1eba7c5abf6e24725fff3d85eb27a30880b6dee5 (diff)
downloadlace-d7f07925dc1546e4f881d08d1bc6604258317998.tar.gz
Add basic runtime engine and tests
-rw-r--r--lib/lace.lua2
-rw-r--r--lib/lace/engine.lua84
-rw-r--r--test/test-lace.engine.lua125
-rw-r--r--test/test-lace.lua10
4 files changed, 221 insertions, 0 deletions
diff --git a/lib/lace.lua b/lib/lace.lua
index 3822b38..ae108ac 100644
--- a/lib/lace.lua
+++ b/lib/lace.lua
@@ -10,9 +10,11 @@
local lex = require "lace.lex"
local compiler = require "lace.compiler"
local builtin = require "lace.builtin"
+local engine = require "lace.engine"
return {
lex = lex,
compiler = compiler,
builtin = builtin,
+ engine = engine,
}
diff --git a/lib/lace/engine.lua b/lib/lace/engine.lua
new file mode 100644
index 0000000..5b3d845
--- /dev/null
+++ b/lib/lace/engine.lua
@@ -0,0 +1,84 @@
+-- lib/lace/engine.lua
+--
+-- Lua Access Control Engine -- Ruleset runtime engine
+--
+-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
+--
+-- For licence terms, see COPYING
+--
+
+local function _error(str, words)
+ return { msg = str, words = words }
+end
+
+local function _dlace(ctx)
+ local ret = ctx[".lace"] or {}
+ ctx[".lace"] = ret
+ return ret
+end
+
+local function set_define(exec_context, name, defn)
+ local dlace = _dlace(exec_context)
+ dlace.defs = dlace.defs or {}
+ if dlace.defs[name] then
+ return false, _error("Attempted to redefine " .. name)
+ end
+ dlace.defs[name] = defn
+ return true
+end
+
+local function test_define(exec_context, name)
+ local dlace = _dlace(exec_context)
+ dlace.defs = dlace.defs or {}
+ local defn = dlace.defs[name]
+ if not defn then
+ return nil, _error("Unknown definition: " .. name)
+ end
+ -- Otherwise we evaluate the definition and return it
+ return defn.fn(exec_context, unpack(defn.args))
+end
+
+local function internal_run_ruleset(ruleset, exec_context)
+ -- We iterate the ruleset, returning the first time
+ -- a rule either errors, or returns a stopping result
+ local dlace = _dlace(exec_context)
+ dlace.ruleset = ruleset
+ for i = 1, #ruleset.rules do
+ local rule = ruleset.rules[i]
+ dlace.linenr = rule.linenr
+ local result, msg = rule.fn(exec_context, unpack(rule.args))
+ if not result then
+ return false, msg
+ elseif result ~= true then
+ -- Explicit result, return it
+ return result, msg
+ end
+ end
+ dlace.linenr = nil
+
+ -- Internally we use the empty string to indicate no result.
+ return ""
+end
+
+local function run_ruleset(ruleset, exec_context)
+ local ok, ret, msg = xpcall(function()
+ return internal_run_ruleset(ruleset, exec_context)
+ end, debug.traceback)
+ if not ok then
+ return nil, ret
+ end
+
+ if ret == "" then
+ -- Empty string indicates no error but no result, we don't like
+ -- that here so we return an error
+ return false, "Ruleset did not explicitly allow or deny"
+ end
+
+ return ret, msg
+end
+
+return {
+ run = run_ruleset,
+ test = test_define,
+ define = set_define,
+}
diff --git a/test/test-lace.engine.lua b/test/test-lace.engine.lua
new file mode 100644
index 0000000..a063c2e
--- /dev/null
+++ b/test/test-lace.engine.lua
@@ -0,0 +1,125 @@
+-- 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
+
+local luacov = require 'luacov'
+
+local lace = require 'lace'
+
+local testnames = {}
+
+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_empty_ruleset_fails()
+ local compctx = {[".lace"]={}}
+ local ruleset, msg = lace.compiler.compile(compctx, "src", "")
+ assert(type(ruleset) == "table", "Could not compile empty ruleset")
+ local execctx = {}
+ local result, msg = lace.engine.run(ruleset, execctx)
+ assert(result == false, "Empty failure returns false")
+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")
+ 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 false, "EXPLODE" end, args = {} }
+ end
+ local compctx = {[".lace"]={commands={explode=_explode}}}
+ local ruleset, msg = lace.compiler.compile(compctx, "src", "explode")
+ 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
+
+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("Engine: " .. tostring(count_ok) .. "/" .. tostring(#testnames) .. " OK")
+
+os.exit(count_ok == #testnames and 0 or 1)
diff --git a/test/test-lace.lua b/test/test-lace.lua
index d4f458f..eb65864 100644
--- a/test/test-lace.lua
+++ b/test/test-lace.lua
@@ -14,6 +14,8 @@ local luacov = require 'luacov'
local lace = require 'lace'
local lex = require 'lace.lex'
local compiler = require 'lace.compiler'
+local builtin = require 'lace.builtin'
+local engine = require 'lace.engine'
local testnames = {}
@@ -32,6 +34,14 @@ function suite.compiler_passed()
assert(lace.compiler == compiler, "Lace's compiler entry is not lace.compiler")
end
+function suite.builtin_passed()
+ assert(lace.builtin == builtin, "Lace's builtin entry is not lace.builtin")
+end
+
+function suite.engine_passed()
+ assert(lace.engine == engine, "Lace's engine entry is not lace.engine")
+end
+
local count_ok = 0
for _, testname in ipairs(testnames) do
print("Run: " .. testname)