mirror of
https://github.com/Doodle3D/doodle3d-firmware.git
synced 2024-12-22 19:13:49 +01:00
Add simple unit test framework and various other improvements/changes:
* Move logger.lua; * update quotation style in several files; * add proposed configuration key layout to conf_defaults.lua; * move dump function to utils.lua; * implement (very) basic unit testing environment; * fix bugs in various functions in utils.lua; * checkin of preliminary REST API test code.
This commit is contained in:
parent
3726063b99
commit
9a8ac55b8f
34
src/conf_defaults.lua
Normal file
34
src/conf_defaults.lua
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
--NOTE: pcall protects from invocation exceptions, which is what we need except
|
||||||
|
--during debugging. This flag replaces them with a normal call so we can inspect stack traces.
|
||||||
|
M.DEBUG_PCALLS = true
|
||||||
|
|
||||||
|
--REST responses will contain 'module' and 'function' keys describing what was requested
|
||||||
|
M.API_INCLUDE_ENDPOINT_INFO = false
|
||||||
|
|
||||||
|
M.DEFAULT_AP_SSID = "d3d-ap-%MAC_ADDR_TAIL%"
|
||||||
|
M.DEFAULT_AP_ADDRESS = "192.168.10.1"
|
||||||
|
M.DEFAULT_AP_NETMASK = "255.255.255.0"
|
||||||
|
|
||||||
|
|
||||||
|
--NOTE: proposed notation for baseline configuration (containing defaults as well as type and constraint information)
|
||||||
|
--the table name is the configuration key; min, max and regex are all optional; type is one of: {int, float, string, ...?}
|
||||||
|
M.temperature = {
|
||||||
|
default = 230,
|
||||||
|
type = 'int',
|
||||||
|
description = '...xyzzy',
|
||||||
|
min = 0,
|
||||||
|
max = 350
|
||||||
|
}
|
||||||
|
|
||||||
|
M.ssid = {
|
||||||
|
default = 'd3d-ap-%%MAC_TAIL%%',
|
||||||
|
type = 'int', --one of: {int, float, string, ...?}
|
||||||
|
min = 1,
|
||||||
|
max = 32,
|
||||||
|
regex = '[a-zA-Z0-9 -=+]+'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return M
|
@ -1,14 +0,0 @@
|
|||||||
local M = {}
|
|
||||||
|
|
||||||
--NOTE: pcall protects from invocation exceptions, which is what we need except
|
|
||||||
--during debugging. This flag replaces them with a normal call so we can inspect stack traces.
|
|
||||||
M.DEBUG_PCALLS = true
|
|
||||||
|
|
||||||
--REST responses will contain 'module' and 'function' keys describing what was requested
|
|
||||||
M.API_INCLUDE_ENDPOINT_INFO = false
|
|
||||||
|
|
||||||
M.DEFAULT_AP_SSID = "d3d-ap-%MAC_ADDR_TAIL%"
|
|
||||||
M.DEFAULT_AP_ADDRESS = "192.168.10.1"
|
|
||||||
M.DEFAULT_AP_NETMASK = "255.255.255.0"
|
|
||||||
|
|
||||||
return M
|
|
25
src/main.lua
25
src/main.lua
@ -1,11 +1,12 @@
|
|||||||
package.path = package.path .. ';/usr/share/lua/wifibox/?.lua'
|
package.path = package.path .. ';/usr/share/lua/wifibox/?.lua'
|
||||||
|
|
||||||
local l = require("logger")
|
local config = require('config')
|
||||||
local RequestClass = require("rest.request")
|
local u = require('util.utils')
|
||||||
local ResponseClass = require("rest.response")
|
local l = require('util.logger')
|
||||||
local wifi = require("network.wlanconfig")
|
local wifi = require('network.wlanconfig')
|
||||||
local netconf = require("network.netconfig")
|
local netconf = require('network.netconfig')
|
||||||
local config = require("config")
|
local RequestClass = require('rest.request')
|
||||||
|
local ResponseClass = require('rest.response')
|
||||||
|
|
||||||
local postData = nil
|
local postData = nil
|
||||||
|
|
||||||
@ -22,8 +23,8 @@ local function init()
|
|||||||
else l:info("Wifibox CGI handler started")
|
else l:info("Wifibox CGI handler started")
|
||||||
end
|
end
|
||||||
|
|
||||||
if (os.getenv("REQUEST_METHOD") == "POST") then
|
if (os.getenv('REQUEST_METHOD') == 'POST') then
|
||||||
local n = tonumber(os.getenv("CONTENT_LENGTH"))
|
local n = tonumber(os.getenv('CONTENT_LENGTH'))
|
||||||
postData = io.read(n)
|
postData = io.read(n)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -41,14 +42,14 @@ end
|
|||||||
local rq = RequestClass.new(postData, config.DEBUG_PCALLS)
|
local rq = RequestClass.new(postData, config.DEBUG_PCALLS)
|
||||||
|
|
||||||
l:info("received request of type " .. rq:getRequestMethod() .. " for " .. (rq:getRequestedApiModule() or "<unknown>")
|
l:info("received request of type " .. rq:getRequestMethod() .. " for " .. (rq:getRequestedApiModule() or "<unknown>")
|
||||||
.. "/" .. (rq:getRealApiFunctionName() or "<unknown>") .. " with arguments: " .. l:dump(rq:getAll()))
|
.. "/" .. (rq:getRealApiFunctionName() or "<unknown>") .. " with arguments: " .. u.dump(rq:getAll()))
|
||||||
if rq:getRequestMethod() ~= "CMDLINE" then
|
if rq:getRequestMethod() ~= 'CMDLINE' then
|
||||||
l:info("remote IP/port: " .. rq:getRemoteHost() .. "/" .. rq:getRemotePort())
|
l:info("remote IP/port: " .. rq:getRemoteHost() .. "/" .. rq:getRemotePort())
|
||||||
l:debug("user agent: " .. rq:getUserAgent())
|
l:debug("user agent: " .. rq:getUserAgent())
|
||||||
end
|
end
|
||||||
|
|
||||||
if (not config.DEBUG_PCALLS and rq:getRequestMethod() == "CMDLINE") then
|
if (not config.DEBUG_PCALLS and rq:getRequestMethod() == 'CMDLINE') then
|
||||||
if rq:get("autowifi") ~= nil then
|
if rq:get('autowifi') ~= nil then
|
||||||
setupAutoWifiMode()
|
setupAutoWifiMode()
|
||||||
else
|
else
|
||||||
l:info("Nothing to do...bye.\n")
|
l:info("Nothing to do...bye.\n")
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
local config = require("config")
|
local config = require("config")
|
||||||
local u = require("util.utils")
|
local u = require("util.utils")
|
||||||
local l = require("logger")
|
local l = require("util.logger")
|
||||||
local uci = require("uci").cursor()
|
local uci = require("uci").cursor()
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
local util = require("util.utils")
|
local util = require("util.utils")
|
||||||
local l = require("logger")
|
local l = require("util.logger")
|
||||||
local uci = require("uci").cursor()
|
local uci = require("uci").cursor()
|
||||||
local iwinfo = require("iwinfo")
|
local iwinfo = require("iwinfo")
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
local config = require("config")
|
local config = require("config")
|
||||||
local l = require("logger")
|
|
||||||
local u = require("util.utils")
|
local u = require("util.utils")
|
||||||
|
local l = require("util.logger")
|
||||||
local netconf = require("network.netconfig")
|
local netconf = require("network.netconfig")
|
||||||
local wifi = require("network.wlanconfig")
|
local wifi = require("network.wlanconfig")
|
||||||
local ResponseClass = require("rest.response")
|
local ResponseClass = require("rest.response")
|
||||||
@ -36,7 +36,7 @@ function M.available(request, response)
|
|||||||
netInfo["signal"] = se.signal
|
netInfo["signal"] = se.signal
|
||||||
netInfo["quality"] = se.quality
|
netInfo["quality"] = se.quality
|
||||||
netInfo["quality_max"] = se.quality_max
|
netInfo["quality_max"] = se.quality_max
|
||||||
if withRaw then netInfo["_raw"] = l:dump(se) end
|
if withRaw then netInfo["_raw"] = u.dump(se) end
|
||||||
|
|
||||||
table.insert(netInfoList, netInfo)
|
table.insert(netInfoList, netInfo)
|
||||||
end
|
end
|
||||||
@ -63,7 +63,7 @@ function M.known(request, response)
|
|||||||
netInfo["bssid"] = net.bssid or ""
|
netInfo["bssid"] = net.bssid or ""
|
||||||
netInfo["channel"] = net.channel or ""
|
netInfo["channel"] = net.channel or ""
|
||||||
netInfo["encryption"] = net.encryption
|
netInfo["encryption"] = net.encryption
|
||||||
if withRaw then netInfo["_raw"] = l:dump(net) end
|
if withRaw then netInfo["_raw"] = u.dump(net) end
|
||||||
table.insert(netInfoList, netInfo)
|
table.insert(netInfoList, netInfo)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -87,7 +87,7 @@ function M.state(request, response)
|
|||||||
response:addData("txpower", ds.txpower)
|
response:addData("txpower", ds.txpower)
|
||||||
response:addData("signal", ds.signal)
|
response:addData("signal", ds.signal)
|
||||||
response:addData("noise", ds.noise)
|
response:addData("noise", ds.noise)
|
||||||
if withRaw then response:addData("_raw", l:dump(ds)) end
|
if withRaw then response:addData("_raw", u.dump(ds)) end
|
||||||
end
|
end
|
||||||
|
|
||||||
--UNTESTED
|
--UNTESTED
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
local l = require("logger")
|
local l = require("util.logger")
|
||||||
local ResponseClass = require("rest.response")
|
local ResponseClass = require("rest.response")
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
63
src/test/ansicolors.lua
Normal file
63
src/test/ansicolors.lua
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
-- adapted from: http://lua-users.org/wiki/AnsiTerminalColors
|
||||||
|
local pairs = pairs
|
||||||
|
local tostring = tostring
|
||||||
|
local setmetatable = setmetatable
|
||||||
|
local schar = string.char
|
||||||
|
|
||||||
|
local colormt = {}
|
||||||
|
|
||||||
|
function colormt:__tostring()
|
||||||
|
return self.value
|
||||||
|
end
|
||||||
|
|
||||||
|
function colormt:__concat(other)
|
||||||
|
return tostring(self) .. tostring(other)
|
||||||
|
end
|
||||||
|
|
||||||
|
function colormt:__call(s)
|
||||||
|
return self .. s .. colormt.reset
|
||||||
|
end
|
||||||
|
|
||||||
|
colormt.__metatable = {}
|
||||||
|
|
||||||
|
local function makecolor(value)
|
||||||
|
return setmetatable({ value = schar(27) .. '[' .. tostring(value) .. 'm' }, colormt)
|
||||||
|
end
|
||||||
|
|
||||||
|
local colors = {
|
||||||
|
-- attributes
|
||||||
|
reset = 0,
|
||||||
|
clear = 0,
|
||||||
|
bright = 1,
|
||||||
|
dim = 2,
|
||||||
|
underscore = 4,
|
||||||
|
blink = 5,
|
||||||
|
reverse = 7,
|
||||||
|
hidden = 8,
|
||||||
|
|
||||||
|
-- foreground
|
||||||
|
black = 30,
|
||||||
|
red = 31,
|
||||||
|
green = 32,
|
||||||
|
yellow = 33,
|
||||||
|
blue = 34,
|
||||||
|
magenta = 35,
|
||||||
|
cyan = 36,
|
||||||
|
white = 37,
|
||||||
|
|
||||||
|
-- background
|
||||||
|
onblack = 40,
|
||||||
|
onred = 41,
|
||||||
|
ongreen = 42,
|
||||||
|
onyellow = 43,
|
||||||
|
onblue = 44,
|
||||||
|
onmagenta = 45,
|
||||||
|
oncyan = 46,
|
||||||
|
onwhite = 47,
|
||||||
|
}
|
||||||
|
|
||||||
|
for c, v in pairs(colors) do
|
||||||
|
colormt[c] = makecolor(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
return colormt
|
105
src/test/test_utils.lua
Normal file
105
src/test/test_utils.lua
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
local utils = require("util.utils")
|
||||||
|
|
||||||
|
local M = {
|
||||||
|
_is_test = true,
|
||||||
|
_skip = { 'dump', 'symlink', 'getUciSectionName', 'symlinkInRoot' },
|
||||||
|
_wifibox_only = { 'getUciSectionName', 'symlinkInRoot' }
|
||||||
|
}
|
||||||
|
|
||||||
|
local function compareTables(t1, t2)
|
||||||
|
if #t1 ~= #t2 then return false end
|
||||||
|
for i=1,#t1 do
|
||||||
|
if t1[i] ~= t2[i] then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Returns a string representation of the argument, with 'real' strings enclosed in single quotes
|
||||||
|
local function stringRepresentation(v)
|
||||||
|
return type(v) == 'string' and ("'"..v.."'") or tostring(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local filename = "/tmp/somefile12345.txt"
|
||||||
|
|
||||||
|
|
||||||
|
function M:_setup()
|
||||||
|
os.execute("rm -f " .. filename) -- make sure the file does not exist
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:_teardown()
|
||||||
|
os.execute("rm -f " .. filename) -- make sure the file gets removed again
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function M:test_splitString()
|
||||||
|
local input1, input2, input3 = ':a:b::', '/a/b//', '$a$b$$'
|
||||||
|
local expected = { '', 'a', 'b', '', '' }
|
||||||
|
|
||||||
|
local result1 = input1:split()
|
||||||
|
local result2 = input2:split('/')
|
||||||
|
local result3 = input3:split('$')
|
||||||
|
|
||||||
|
assert(#result1 == 5)
|
||||||
|
assert(compareTables(result1, expected))
|
||||||
|
assert(#result2 == 5)
|
||||||
|
assert(compareTables(result2, expected))
|
||||||
|
assert(#result3 == 5)
|
||||||
|
assert(compareTables(result3, expected))
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:test_toboolean()
|
||||||
|
local trues = { true, 1, 'true', 'True', 'T', '1' }
|
||||||
|
local falses = { nil, false, 0, 'false', 'False' , 'f', {} }
|
||||||
|
|
||||||
|
for _,v in pairs(trues) do assert(utils.toboolean(v), "expected true: " .. stringRepresentation(v)) end
|
||||||
|
for _,v in pairs(falses) do assert(not utils.toboolean(v), "expected false: " .. stringRepresentation(v)) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:test_dump()
|
||||||
|
--test handling of reference loops
|
||||||
|
assert(false, 'not implemented')
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:test_getUciSectionName()
|
||||||
|
assert(false, 'not implemented')
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:test_exists()
|
||||||
|
assert(utils.exists() == nil)
|
||||||
|
assert(utils.exists(nil) == nil)
|
||||||
|
|
||||||
|
assert(not utils.exists(filename))
|
||||||
|
os.execute("touch " .. filename)
|
||||||
|
assert(utils.exists(filename))
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:test_create()
|
||||||
|
local f, testContents = nil, 'test text'
|
||||||
|
|
||||||
|
assert(utils.create() == nil)
|
||||||
|
assert(utils.create(nil) == nil)
|
||||||
|
|
||||||
|
assert(not io.open(filename, 'r'))
|
||||||
|
utils.create(filename)
|
||||||
|
assert(io.open(filename, 'r'))
|
||||||
|
|
||||||
|
f = io.open(filename, 'w')
|
||||||
|
f:write(testContents)
|
||||||
|
f:close()
|
||||||
|
|
||||||
|
utils.create(filename)
|
||||||
|
f = io.open(filename, 'r')
|
||||||
|
local actualContents = f:read('*all')
|
||||||
|
assert(actualContents == testContents)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:test_symlink()
|
||||||
|
assert(false, 'not implemented')
|
||||||
|
end
|
||||||
|
|
||||||
|
function M:test_symlinkInRoot()
|
||||||
|
assert(false, 'not implemented')
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
87
src/test/testrunner.lua
Normal file
87
src/test/testrunner.lua
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package.path = package.path .. ';./test/?.lua'
|
||||||
|
local ansicolors = require('test.ansicolors')
|
||||||
|
|
||||||
|
local testFunctionPrefix = 'test_'
|
||||||
|
|
||||||
|
local function tableIndexOf(t, val)
|
||||||
|
for k,v in ipairs(t) do
|
||||||
|
if v == val then return k end
|
||||||
|
end
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
|
||||||
|
local function runningOnWifibox()
|
||||||
|
local f,e = io.open('/etc/openwrt_release', 'r')
|
||||||
|
return f ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local function runTestFile(filename, showStackTraces)
|
||||||
|
local r,tf = pcall(require, filename)
|
||||||
|
local stackTrace, errorMessage
|
||||||
|
|
||||||
|
local function errorHandler(msg)
|
||||||
|
stackTrace = debug.traceback()
|
||||||
|
errorMessage = msg
|
||||||
|
end
|
||||||
|
|
||||||
|
if not r then return nil,tf end
|
||||||
|
if not tf._is_test then return nil,"not a test file" end
|
||||||
|
|
||||||
|
local setupFunc, teardownFunc = tf._setup, tf._teardown
|
||||||
|
|
||||||
|
print("======= running test file '" .. filename .. "' =======")
|
||||||
|
|
||||||
|
for k,v in pairs(tf) do
|
||||||
|
--if type(v) == 'function' and k ~= '_setup' and k ~= '_teardown' then
|
||||||
|
if type(v) == 'function' and k:find(testFunctionPrefix) == 1 then
|
||||||
|
local baseName = k:sub(testFunctionPrefix:len() + 1)
|
||||||
|
local skip = (tableIndexOf(tf._skip, baseName) > -1)
|
||||||
|
local wifiboxOnly = (tableIndexOf(tf._wifibox_only, baseName) > -1)
|
||||||
|
|
||||||
|
if not skip and (not wifiboxOnly or runningOnWifibox()) then
|
||||||
|
pcall(setupFunc)
|
||||||
|
local testResult = xpcall(v, errorHandler)
|
||||||
|
pcall(teardownFunc)
|
||||||
|
|
||||||
|
if testResult then
|
||||||
|
print(ansicolors.green .. "[OK ] " .. ansicolors.reset .. k)
|
||||||
|
else
|
||||||
|
print(ansicolors.red .. "[ERR] " .. ansicolors.reset .. k)
|
||||||
|
if errorMessage then print(" " .. errorMessage) end
|
||||||
|
if showStackTraces and stackTrace then print(" " .. stackTrace) end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if skip then
|
||||||
|
print(ansicolors.bright .. ansicolors.black .. "[SKP] " .. ansicolors.reset .. k)
|
||||||
|
else
|
||||||
|
print(ansicolors.yellow .. "[WBO] " .. ansicolors.reset .. k)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("")
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
local function main()
|
||||||
|
if #arg < 1 then
|
||||||
|
print("Please specify at least one lua test file")
|
||||||
|
os.exit(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i=1,#arg do
|
||||||
|
local modName = 'test_'..arg[i]
|
||||||
|
local r,e = runTestFile(modName, true)
|
||||||
|
|
||||||
|
if not r then
|
||||||
|
io.stderr:write("test file '" .. modName .. "' could not be loaded or is not a test file ('" .. e .. "')\n")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
os.exit(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
main(arg)
|
43
src/test/www/restapi.css
Normal file
43
src/test/www/restapi.css
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div#content {
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.module {
|
||||||
|
border: 1px solid #eee;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
padding-left: 2em;
|
||||||
|
font-size: larger;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resp_success {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resp_status {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resp_fail {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resp_error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resp_unknown {
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.resp_msg {
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
22
src/test/www/restapi.html
Normal file
22
src/test/www/restapi.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
|
||||||
|
<title>Test page for the Doodle3D WiFiBox REST API</title>
|
||||||
|
<link type="text/css" rel="stylesheet" href="restapi.css" />
|
||||||
|
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
|
||||||
|
<script type="text/javascript" src="restapi.js"></script>
|
||||||
|
</head>
|
||||||
|
<body><div id="content">
|
||||||
|
<div id="module_test" class="module">
|
||||||
|
<span class="title">Test API</span>
|
||||||
|
</div>
|
||||||
|
<div id="module_network" class="module">
|
||||||
|
<span class="title">Network API</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="title">Run arbitrary requests</span>
|
||||||
|
</div>
|
||||||
|
</div></body>
|
||||||
|
</html>
|
40
src/test/www/restapi.js
Normal file
40
src/test/www/restapi.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
const API_ROOT = "/cgi-bin/d3dapi";
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
performRequest('test', '123', "GET", {});
|
||||||
|
performRequest('test', '', "POST", {});
|
||||||
|
performRequest('test', 'success', "GET", {});
|
||||||
|
performRequest('test', 'fail', "GET", {});
|
||||||
|
performRequest('test', 'error', "GET", {});
|
||||||
|
performRequest('test', 'echo', "GET", {});
|
||||||
|
performRequest('test', 'write', "POST", {});
|
||||||
|
performRequest('test', 'read', "GET", {});
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO
|
||||||
|
* - change function signature to: apiPath, data, expected
|
||||||
|
* - create request divs before ajax request so it's visible what 'threads' have been put out
|
||||||
|
* - function always tries both GET and POST and matches this with expected.methods
|
||||||
|
* - expected.methods can be 'GET', 'POST' or 'BOTH'
|
||||||
|
* - expected.status indicates what status was expected
|
||||||
|
* - expected.data.{...} indicates what fields should look like (regex/literal/...?)
|
||||||
|
* - anything not specified in expected.data is ignored
|
||||||
|
*/
|
||||||
|
function performRequest(mod, func, method, data) {
|
||||||
|
$.ajax({
|
||||||
|
type: method,
|
||||||
|
context: $("#module_" + mod),
|
||||||
|
url: API_ROOT + "/" + mod + "/" + func,
|
||||||
|
dataType: 'json',
|
||||||
|
data: data
|
||||||
|
}).done(function(response){
|
||||||
|
var status = response.status == 'success' ? '<span class="resp_status resp_success">+</span>'
|
||||||
|
: response.status == 'fail' ? '<span class="resp_status resp_fail">-</span>'
|
||||||
|
: response.status == 'error' ? '<span class="resp_status resp_error">!</span>'
|
||||||
|
: '<span class="resp_status resp_unknown">?</span>';
|
||||||
|
var modFunc = '<span class="resp_mod_func">' + mod + '/' + func + '</span><br/>';
|
||||||
|
this.append('<div id="resp_' + mod + '_' + func + '">' + status + modFunc
|
||||||
|
+ '<span class="resp_msg">' + response.msg + '</span></div>');
|
||||||
|
});
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
|
local utils = require('util.utils')
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local logLevel, logVerbose, logStream
|
local logLevel, logVerbose, logStream
|
||||||
|
|
||||||
M.LEVEL = {"debug", "info", "warn", "error", "fatal"}
|
M.LEVEL = {'debug', 'info', 'warn', 'error', 'fatal'}
|
||||||
|
|
||||||
--M.LEVEL already has idx=>name entries, now create name=>idx entries
|
-- M.LEVEL already has idx=>name entries, now create name=>idx entries
|
||||||
for i,v in ipairs(M.LEVEL) do
|
for i,v in ipairs(M.LEVEL) do
|
||||||
M.LEVEL[v] = i
|
M.LEVEL[v] = i
|
||||||
end
|
end
|
||||||
@ -15,38 +17,25 @@ function M:init(level, verbose)
|
|||||||
logStream = stream or io.stdout
|
logStream = stream or io.stdout
|
||||||
end
|
end
|
||||||
|
|
||||||
--pass nil as stream to reset to stdout
|
-- pass nil as stream to reset to stdout
|
||||||
function M:setStream(stream)
|
function M:setStream(stream)
|
||||||
logStream = stream or io.stdout
|
logStream = stream or io.stdout
|
||||||
end
|
end
|
||||||
|
|
||||||
local function log(level, msg, verbose)
|
local function log(level, msg, verbose)
|
||||||
if level >= logLevel then
|
if level >= logLevel then
|
||||||
local now = os.date("%m-%d %H:%M:%S")
|
local now = os.date('%m-%d %H:%M:%S')
|
||||||
local i = debug.getinfo(3) --the stack frame just above the logger call
|
local i = debug.getinfo(3) --the stack frame just above the logger call
|
||||||
local v = verbose
|
local v = verbose
|
||||||
if v == nil then v = logVerbose end
|
if v == nil then v = logVerbose end
|
||||||
local name = i.name or "(nil)"
|
local name = i.name or "(nil)"
|
||||||
local vVal = "nil"
|
local vVal = 'nil'
|
||||||
local m = (type(msg) == "string") and msg or M:dump(msg)
|
local m = (type(msg) == 'string') and msg or utils.dump(msg)
|
||||||
if v then logStream:write(now .. " (" .. M.LEVEL[level] .. ") " .. m .. " [" .. name .. "@" .. i.short_src .. ":" .. i.linedefined .. "]\n")
|
if v then logStream:write(now .. " (" .. M.LEVEL[level] .. ") " .. m .. " [" .. name .. "@" .. i.short_src .. ":" .. i.linedefined .. "]\n")
|
||||||
else logStream:write(now .. " (" .. M.LEVEL[level] .. ") " .. m .. "\n") end
|
else logStream:write(now .. " (" .. M.LEVEL[level] .. ") " .. m .. "\n") end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M:dump(o)
|
|
||||||
if type(o) == 'table' then
|
|
||||||
local s = '{ '
|
|
||||||
for k,v in pairs(o) do
|
|
||||||
if type(k) ~= 'number' then k = '"'..k..'"' end
|
|
||||||
s = s .. '['..k..'] = ' .. M:dump(v) .. ','
|
|
||||||
end
|
|
||||||
return s .. '} '
|
|
||||||
else
|
|
||||||
return tostring(o)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M:debug(msg, verbose) log(M.LEVEL.debug, msg, verbose); return true end
|
function M:debug(msg, verbose) log(M.LEVEL.debug, msg, verbose); return true end
|
||||||
function M:info(msg, verbose) log(M.LEVEL.info, msg, verbose); return true end
|
function M:info(msg, verbose) log(M.LEVEL.info, msg, verbose); return true end
|
||||||
function M:warn(msg, verbose) log(M.LEVEL.warn, msg, verbose); return true end
|
function M:warn(msg, verbose) log(M.LEVEL.warn, msg, verbose); return true end
|
@ -1,9 +1,9 @@
|
|||||||
local uci = require("uci").cursor()
|
local uci = require('uci').cursor()
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
function string:split(div)
|
function string:split(div)
|
||||||
local div, pos, arr = div or ":", 0, {}
|
local div, pos, arr = div or ':', 0, {}
|
||||||
for st,sp in function() return self:find(div, pos, true) end do
|
for st,sp in function() return self:find(div, pos, true) end do
|
||||||
table.insert(arr, self:sub(pos, st - 1))
|
table.insert(arr, self:sub(pos, st - 1))
|
||||||
pos = sp + 1
|
pos = sp + 1
|
||||||
@ -15,26 +15,63 @@ end
|
|||||||
function M.toboolean(s)
|
function M.toboolean(s)
|
||||||
if not s then return false end
|
if not s then return false end
|
||||||
|
|
||||||
local b = s:lower()
|
local b = type(s) == 'string' and s:lower() or s
|
||||||
return (b == "1" or b == "t" or b == "true") and true or false
|
local textTrue = (b == '1' or b == 't' or b == 'true')
|
||||||
|
local boolTrue = (type(b) == 'boolean' and b == true)
|
||||||
|
local numTrue = (type(b) == 'number' and b > 0)
|
||||||
|
return textTrue or boolTrue or numTrue
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.dump(o)
|
||||||
|
if type(o) == 'table' then
|
||||||
|
local s = '{ '
|
||||||
|
for k,v in pairs(o) do
|
||||||
|
if type(k) ~= 'number' then k = '"'..k..'"' end
|
||||||
|
s = s .. '['..k..'] = ' .. M.dump(v) .. ','
|
||||||
|
end
|
||||||
|
return s .. '} '
|
||||||
|
else
|
||||||
|
return tostring(o)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.getUciSectionName(config, type)
|
function M.getUciSectionName(config, type)
|
||||||
local sname = nil
|
local sname = nil
|
||||||
uci:foreach(config, type, function(s) sname = s[".name"] end)
|
uci:foreach(config, type, function(s) sname = s['.name'] end)
|
||||||
return sname
|
return sname
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.exists(file)
|
function M.exists(file)
|
||||||
local r = io.open(file) --ignore returned message
|
if not file or type(file) ~= 'string' or file:len() == 0 then
|
||||||
if r ~= nil then io.close(r) end
|
return nil, "file must be a non-empty string"
|
||||||
|
end
|
||||||
|
|
||||||
|
local r = io.open(file, 'r') -- ignore returned message
|
||||||
|
if r then r:close() end
|
||||||
return r ~= nil
|
return r ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--creates and returns true if not exists, returns false it does, nil+msg on error
|
||||||
|
function M.create(file)
|
||||||
|
local r,m = M.exists(file)
|
||||||
|
|
||||||
|
if r == nil then
|
||||||
|
return r,m
|
||||||
|
elseif r == true then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
r,m = io.open(file, 'a') -- append mode is probably safer in case the file does exist after all
|
||||||
|
if not r then return r,m end
|
||||||
|
|
||||||
|
r:close()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
--FIXME: somehow protect this function from running arbitrary commands
|
--FIXME: somehow protect this function from running arbitrary commands
|
||||||
function M.symlink(from, to)
|
function M.symlink(from, to)
|
||||||
if from == nil or from == "" or to == nil or to == "" then return -1 end
|
if from == nil or from == '' or to == nil or to == '' then return -1 end
|
||||||
local x = "ln -s " .. from .. " " .. to
|
local x = 'ln -s ' .. from .. ' ' .. to
|
||||||
return os.execute(x)
|
return os.execute(x)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user