-- example/simple-example.lua
--
-- Sandbox (for) Untrusted Procedure Partitioning (in) Lua Engine
--
-- Simple example
--
-- Copyright 2012 Daniel Silverstone <dsilvers@digital-scurf.org>
--
-- For licence terms, see COPYING
--

supple = require'supple'
luxio = require'luxio'

-- Code string to run in the untrusted environment
subcode = [[
local t = ...
local tot = 0
for i = 1, #t do
  tot = tot + t[i]()
end
t.tot = tot
return tot, -t
]]

-- Generate a function to return 'n'
local function give_n(n)
      return function() return n end
end

-- A useful table for the test
local tab = {
      give_n(4),
      give_n(8),
      give_n(12),
}

-- A metatable which defines -tab to equal 'JEFF'
local mt = {
   __unm = function () return "JEFF" end
}

setmetatable(tab, mt)

local tests, passed = 0, 0

local function lprint(expect, result, ...)
   tests = tests + 1
   luxio.write(1, ("[%d]\r"):format(tests))
   local foo = {n=select("#",...),...}
   if expect and not result then
      print "Want OK, got FAIL"
   elseif (not expect) and result then
      print "Want FAIL, got OK"
   end
   if expect ~= result then
      for i = 1, foo.n do
	 if type(foo[i]) == "table" then
	    print("{")
	    for k, v in pairs(foo[i]) do
	       print("",k,v)
	    end
	    print("}")
	 else
	    print(foo[i])
	 end
      end
      print()
   else
      passed = passed + 1
   end
end

local function lprint_ok(...)
   return lprint(true, ...)
end

local function lprint_fail(...)
   return lprint(false, ...)
end

-- Finally, run the subcode
lprint_ok(supple.host.run(subcode, "@test-code", tab))
assert(tab.tot == 24)

-- Now run a supple command which we expect to error out.
lprint_fail(supple.host.run("unknown()", "@test-code"))

-- And now, one where we pass an error from host to sandbox and back
lprint_fail(supple.host.run("local f = ... f()", "@test-code", function() unknown() end))

-- And now, one where we pass an error from sandbox to host to sandbox and back
lprint_fail(supple.host.run("local f = ... f(function() unknown() end)", "@test-code", function(ff) ff() end))

-- Next , a reasonable traceback via named functions on each end...

local errsrc = [[
      function raises()
	 does_not_exist()
      end
      
      function passes()
	 raises()
      end

      function chains(f)
	 f(passes)
      end

      local callme = ...

      callme(chains)
]]

function loopback(f)
   f(loopback)
end

lprint_fail(supple.host.run(errsrc, "@error-code", loopback))

-- Now we try the sandboxing limits

local long_run = [[
      local s = ""
      for i = 1, 100000 do
	 s = s .. "FISHFISHFISHFISHFISH"
      end
      return #s
]]

supple.host.set_limits { count = 100 }
lprint_fail(supple.host.run(long_run, "@long-code"))
supple.host.set_limits { memory = 1000 }
lprint_fail(supple.host.run(long_run, "@big-code"))


-- next we demonstrate that ipairs works on proxied tables

local summing = [[
      local t = ...
      local tot = 0
      for i, v in ipairs(t) do
	 tot = tot + v
      end
      return tot
]]

lprint_ok(supple.host.run(summing, "@summing", { 10, 14, 3 }))

-- next we demonstrate that next works on proxied tables

local isempty = [[
      local t = ...
      return next(t) == nil
]]

lprint_ok(supple.host.run(isempty, "@isempty", {}))
lprint_ok(supple.host.run(isempty, "@isempty", {"bar"}))

-- And now that pairs works on proxied tables

local keys = [[
      local t = ...
      local ret = {}
      for k, v in pairs(t) do
	 ret[#ret+1] = k
      end
      ret.ret = ret
      return ret
]]

lprint_ok(supple.host.run(keys, "@keys", { foo="bar", baz="meta" }))

-- Next demonstrate that using supple.host.loadstring works to allow
-- globals set in the parent to be seen/changed

local function loader(n)
   return supple.host.loadstring(([[return { print=print, name = %q }]]):format(n), "@" .. n)
end

local getname = [[
      local f = ...
      local mod = (f("Jeff"))()
      return mod.print and "PRINT" or mod.name
]]

-- Expect to see 'PRINT'
lprint_ok(supple.host.run(getname, "@getname", loader))

-- Attempt to do a test which shows the same issue as seen with gitano-web

local updates = {
   ["refs/heads/master"] = {
      oldsha = "0123456789abcdef0123456789abcdef01234567",
      newsha = "76543210fedcba9876543210fedcba9876543210",
   }
}

local update_code = [[
      local repo, updates = ...
      local hadmaster = false
      local foundrefs = {}
      if updates["refs/heads/master"] then
	 hadmaster = true
      end
      for ref, data in pairs(updates) do
	 foundrefs[ref] = { data.oldsha, data.newsha }
      end
      return hadmaster, foundrefs
]]

for i = 1, 1000 do
   supple.track.start()
   local ok, res = pcall(supple.host.run, update_code, "@update-hook", "REPO", updates)
   if not ok then print(res) print(supple.track.stop()) end
   lprint_ok(ok)
   lprint_ok(res)
   supple.track.stop()
   if passed ~= tests then
      os.exit(1)
   end
end

print(("[%d/%d OK]"):format(passed, tests))

-- Dump the final trace, just for edification
--print(supple.track.stop())
