mirror of https://github.com/koreader/koreader
BackgroundRunner (#3008)
* Use getCapacityHW() to ensure latest battery capacity can be retrieved * BackgroundRunner * Start background_runner_spec.lua * AutofrontLight plugin now uses BackgroundRunner pluginpull/3034/head
parent
ba96506483
commit
c9a997f42c
@ -0,0 +1,90 @@
|
|||||||
|
local logger = require("logger")
|
||||||
|
|
||||||
|
local CommandRunner = {
|
||||||
|
pio = nil,
|
||||||
|
job = nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
function CommandRunner:createEnvironmentFromTable(t)
|
||||||
|
if t == nil then return "" end
|
||||||
|
|
||||||
|
local r = ""
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
r = r .. k .. "=" .. v .. " "
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.len(r) > 0 then r = "export " .. r .. ";" end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
function CommandRunner:createEnvironment()
|
||||||
|
if type(self.job.environment) == "table" then
|
||||||
|
return self:createEnvironmentFromTable(self.job.environment)
|
||||||
|
end
|
||||||
|
if type(self.job.environment) == "function" then
|
||||||
|
local status, result = pcall(self.job.environment)
|
||||||
|
if status then
|
||||||
|
return self:createEnvironmentFromTable(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return ""
|
||||||
|
end
|
||||||
|
|
||||||
|
function CommandRunner:start(job)
|
||||||
|
assert(self ~= nil)
|
||||||
|
assert(self.pio == nil)
|
||||||
|
assert(self.job == nil)
|
||||||
|
self.job = job
|
||||||
|
self.job.start_sec = os.time()
|
||||||
|
assert(type(self.job.executable) == "string")
|
||||||
|
local command = self:createEnvironment() .. " " ..
|
||||||
|
"sh plugins/backgroundrunner.koplugin/luawrapper.sh " ..
|
||||||
|
"\"" .. self.job.executable .. "\""
|
||||||
|
logger.dbg("CommandRunner: Will execute command " .. command)
|
||||||
|
self.pio = io.popen(command)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Polls the status of self.pio.
|
||||||
|
-- @return a table contains the result from luawrapper.sh. Returns nil if the
|
||||||
|
-- command has not been finished.
|
||||||
|
function CommandRunner:poll()
|
||||||
|
assert(self ~= nil)
|
||||||
|
assert(self.pio ~= nil)
|
||||||
|
assert(self.job ~= nil)
|
||||||
|
local line = self.pio:read()
|
||||||
|
if line == "" then
|
||||||
|
return nil
|
||||||
|
else
|
||||||
|
if line == nil then
|
||||||
|
-- The binary crashes without output. This should not happen.
|
||||||
|
self.job.result = 223
|
||||||
|
else
|
||||||
|
line = line .. self.pio:read("*a")
|
||||||
|
logger.dbg("CommandRunner: Receive output " .. line)
|
||||||
|
local status, result = pcall(loadstring(line))
|
||||||
|
if status and result ~= nil then
|
||||||
|
for k, v in pairs(result) do
|
||||||
|
self.job[k] = v
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- The output from binary is invalid.
|
||||||
|
self.job.result = 222
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.pio:close()
|
||||||
|
self.pio = nil
|
||||||
|
self.job.end_sec = os.time()
|
||||||
|
local job = self.job
|
||||||
|
self.job = nil
|
||||||
|
return job
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Whether this is a running job.
|
||||||
|
-- @treturn boolean
|
||||||
|
function CommandRunner:pending()
|
||||||
|
assert(self ~= nil)
|
||||||
|
return self.pio ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return CommandRunner
|
@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Converts the return of "sh wrapper.sh $@" into Lua format.
|
||||||
|
|
||||||
|
CURRENT_DIR=$(dirname "$0")
|
||||||
|
sh "$CURRENT_DIR/wrapper.sh" "$@" >/dev/null 2>&1 &
|
||||||
|
JOB_ID=$!
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if ps -p $JOB_ID >/dev/null 2>&1; then
|
||||||
|
# Unblock f:read().
|
||||||
|
echo
|
||||||
|
else
|
||||||
|
wait $JOB_ID
|
||||||
|
EXIT_CODE=$?
|
||||||
|
if [ "$EXIT_CODE" -eq "255" ]; then
|
||||||
|
TIMEOUT="true"
|
||||||
|
else
|
||||||
|
TIMEOUT="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$EXIT_CODE" -eq "127" ]; then
|
||||||
|
BADCOMMAND="true"
|
||||||
|
else
|
||||||
|
BADCOMMAND="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "return { \
|
||||||
|
result = $EXIT_CODE, \
|
||||||
|
timeout = $TIMEOUT, \
|
||||||
|
bad_command = $BADCOMMAND, \
|
||||||
|
}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
@ -0,0 +1,234 @@
|
|||||||
|
local CommandRunner = require("commandrunner")
|
||||||
|
local PluginShare = require("pluginshare")
|
||||||
|
local UIManager = require("ui/uimanager")
|
||||||
|
local WidgetContainer = require("ui/widget/container/widgetcontainer")
|
||||||
|
local logger = require("logger")
|
||||||
|
|
||||||
|
-- BackgroundRunner is an experimental feature to execute non-critical jobs in
|
||||||
|
-- background. A job is defined as a table in PluginShare.backgroundJobs table.
|
||||||
|
-- It contains at least following items:
|
||||||
|
-- when: number, string or function
|
||||||
|
-- number: the delay in seconds
|
||||||
|
-- string: "best-effort" - the job will be started when there is no other jobs
|
||||||
|
-- to be executed.
|
||||||
|
-- "idle" - the job will be started when the device is idle.
|
||||||
|
-- function: if the return value of the function is true, the job will be
|
||||||
|
-- executed immediately.
|
||||||
|
--
|
||||||
|
-- repeated: boolean or function or nil or number
|
||||||
|
-- boolean: true to repeated the job once it finished.
|
||||||
|
-- function: if the return value of the function is true, repeated the job
|
||||||
|
-- once it finished. If the function throws an error, it equals to
|
||||||
|
-- return false.
|
||||||
|
-- nil: same as false.
|
||||||
|
-- number: times to repeat.
|
||||||
|
--
|
||||||
|
-- executable: string or function
|
||||||
|
-- string: the command line to be executed. The command or binary will be
|
||||||
|
-- executed in the lowest priority. Command or binary will be killed
|
||||||
|
-- if it executes for over 1 hour.
|
||||||
|
-- function: the action to be executed. The execution cannot be killed, but it
|
||||||
|
-- will be considered as timeout if it executes for more than 1
|
||||||
|
-- second.
|
||||||
|
-- If the executable times out, the job will be blocked, i.e. the repeated
|
||||||
|
-- field will be ignored.
|
||||||
|
--
|
||||||
|
-- environment: table or function or nil
|
||||||
|
-- table: the key-value pairs of all environments set for string executable.
|
||||||
|
-- function: the function to return a table of environments.
|
||||||
|
-- nil: ignore.
|
||||||
|
--
|
||||||
|
-- callback: function or nil
|
||||||
|
-- function: the action to be executed when executable has been finished.
|
||||||
|
-- Errors thrown from this function will be ignored.
|
||||||
|
-- nil: ignore.
|
||||||
|
--
|
||||||
|
-- If a job does not contain enough information, it will be ignored.
|
||||||
|
--
|
||||||
|
-- Once the job is finished, several items will be added to the table:
|
||||||
|
-- result: number, the return value of the command. In general, 0 means
|
||||||
|
-- succeeded.
|
||||||
|
-- For function executable, 1 if the function throws an error.
|
||||||
|
-- For string executable, several predefined values indicate the
|
||||||
|
-- internal errors. E.g. 223: the binary crashes. 222: the output is
|
||||||
|
-- invalid. 127: the command is invalid. 255: the command timed out.
|
||||||
|
-- Typically, consumers can use following states instead of hardcodeing
|
||||||
|
-- the error codes.
|
||||||
|
-- exception: error, the error returned from function executable. Not available
|
||||||
|
-- for string executable.
|
||||||
|
-- timeout: boolean, whether the command times out.
|
||||||
|
-- bad_command: boolean, whether the command is not found. Not available for
|
||||||
|
-- function executable.
|
||||||
|
-- blocked: boolean, whether the job is blocked.
|
||||||
|
-- start_sec: number, the os.time() when the job was started.
|
||||||
|
-- end_sec: number, the os.time() when the job was stopped.
|
||||||
|
-- insert_sec: number, the os.time() when the job was inserted into queue.
|
||||||
|
|
||||||
|
PluginShare.backgroundJobs = PluginShare.backgroundJobs or {}
|
||||||
|
|
||||||
|
local BackgroundRunner = {
|
||||||
|
jobs = PluginShare.backgroundJobs,
|
||||||
|
}
|
||||||
|
|
||||||
|
--- Copies required fields from |job|.
|
||||||
|
-- @return a new table with required fields of a valid job.
|
||||||
|
function BackgroundRunner:_clone(job)
|
||||||
|
assert(job ~= nil)
|
||||||
|
local result = {}
|
||||||
|
result.when = job.when
|
||||||
|
result.repeated = job.repeated
|
||||||
|
result.executable = job.executable
|
||||||
|
result.callback = job.callback
|
||||||
|
result.environment = job.environment
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
function BackgroundRunner:_shouldRepeat(job)
|
||||||
|
if type(job.repeated) == "nil" then return false end
|
||||||
|
if type(job.repeated) == "boolean" then return job.repeated end
|
||||||
|
if type(job.repeated) == "function" then
|
||||||
|
local status, result = pcall(job.repeated)
|
||||||
|
if status then
|
||||||
|
return result
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if type(job.repeated) == "number" then
|
||||||
|
job.repeated = job.repeated - 1
|
||||||
|
return job.repeated > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function BackgroundRunner:_finishJob(job)
|
||||||
|
assert(self ~= nil)
|
||||||
|
if type(job.executable) == "function" then
|
||||||
|
job.timeout = ((job.end_sec - job.start_sec) > 1)
|
||||||
|
end
|
||||||
|
job.blocked = job.timeout
|
||||||
|
if not job.blocked and self:_shouldRepeat(job) then
|
||||||
|
self:_insert(self:_clone(job))
|
||||||
|
end
|
||||||
|
if type(job.callback) == "function" then
|
||||||
|
pcall(job.callback)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Executes |job|.
|
||||||
|
-- @treturn boolean true if job is valid.
|
||||||
|
function BackgroundRunner:_executeJob(job)
|
||||||
|
assert(not CommandRunner:pending())
|
||||||
|
if job == nil then return false end
|
||||||
|
if job.executable == nil then return false end
|
||||||
|
|
||||||
|
if type(job.executable) == "string" then
|
||||||
|
CommandRunner:start(job)
|
||||||
|
return true
|
||||||
|
elseif type(job.executable) == "function" then
|
||||||
|
job.start_sec = os.time()
|
||||||
|
local status, err = pcall(job.executable)
|
||||||
|
if status then
|
||||||
|
job.result = 0
|
||||||
|
else
|
||||||
|
job.result = 1
|
||||||
|
job.exception = err
|
||||||
|
end
|
||||||
|
job.end_sec = os.time()
|
||||||
|
self:_finishJob(job)
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Polls the status of the pending CommandRunner.
|
||||||
|
function BackgroundRunner:_poll()
|
||||||
|
assert(self ~= nil)
|
||||||
|
assert(CommandRunner:pending())
|
||||||
|
local result = CommandRunner:poll()
|
||||||
|
if result == nil then return end
|
||||||
|
|
||||||
|
self:_finishJob(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BackgroundRunner:_execute()
|
||||||
|
logger.dbg("BackgroundRunner: _execute() @ ", os.time())
|
||||||
|
assert(self ~= nil)
|
||||||
|
if CommandRunner:pending() then
|
||||||
|
self:_poll()
|
||||||
|
else
|
||||||
|
local round = 0
|
||||||
|
while #self.jobs > 0 do
|
||||||
|
local job = table.remove(self.jobs, 1)
|
||||||
|
if job.insert_sec == nil then
|
||||||
|
-- Jobs are first inserted to jobs table from external users. So
|
||||||
|
-- they may not have insert_sec field.
|
||||||
|
job.insert_sec = os.time()
|
||||||
|
end
|
||||||
|
local should_execute = false
|
||||||
|
local should_ignore = false
|
||||||
|
if type(job.when) == "function" then
|
||||||
|
local status, result = pcall(job.when)
|
||||||
|
if status then
|
||||||
|
should_execute = result
|
||||||
|
else
|
||||||
|
should_ignore = true
|
||||||
|
end
|
||||||
|
elseif type(job.when) == "number" then
|
||||||
|
if job.when >= 0 then
|
||||||
|
should_execute = ((os.time() - job.insert_sec) >= job.when)
|
||||||
|
else
|
||||||
|
should_ignore = true
|
||||||
|
end
|
||||||
|
elseif type(job.when) == "string" then
|
||||||
|
-- TODO(Hzj_jie): Implement "idle" mode
|
||||||
|
if job.when == "best-effort" then
|
||||||
|
should_execute = (round > 0)
|
||||||
|
elseif job.when == "idle" then
|
||||||
|
should_execute = (round > 1)
|
||||||
|
else
|
||||||
|
should_ignore = true
|
||||||
|
end
|
||||||
|
else
|
||||||
|
should_ignore = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if should_execute then
|
||||||
|
assert(not should_ignore)
|
||||||
|
self:_executeJob(job)
|
||||||
|
break
|
||||||
|
elseif not should_ignore then
|
||||||
|
table.insert(self.jobs, job)
|
||||||
|
end
|
||||||
|
|
||||||
|
round = round + 1
|
||||||
|
if round > 2 then break end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if PluginShare.stopBackgroundRunner == nil then
|
||||||
|
self:_schedule()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function BackgroundRunner:_schedule()
|
||||||
|
assert(self ~= nil)
|
||||||
|
UIManager:scheduleIn(2, function() self:_execute() end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function BackgroundRunner:_insert(job)
|
||||||
|
assert(self ~= nil)
|
||||||
|
job.insert_sec = os.time()
|
||||||
|
table.insert(self.jobs, job)
|
||||||
|
end
|
||||||
|
|
||||||
|
BackgroundRunner:_schedule()
|
||||||
|
|
||||||
|
local BackgroundRunnerWidget = WidgetContainer:new{
|
||||||
|
name = "backgroundrunner",
|
||||||
|
runner = BackgroundRunner,
|
||||||
|
}
|
||||||
|
|
||||||
|
return BackgroundRunnerWidget
|
@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Starts the arguments as a bash command in background with low priorty. The
|
||||||
|
# command will be killed if it executes for over 1 hour. If the command failed
|
||||||
|
# to start, this script returns 127. If the command is timed out, this script
|
||||||
|
# returns 255. Otherwise the return value of the command will be returned.
|
||||||
|
|
||||||
|
echo "TIMEOUT in environment: $TIMEOUT"
|
||||||
|
|
||||||
|
if [ -z "$TIMEOUT" ]; then
|
||||||
|
TIMEOUT=3600
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Timeout has been set to $TIMEOUT seconds"
|
||||||
|
|
||||||
|
echo "Will start command $*"
|
||||||
|
|
||||||
|
echo "$@" | nice -n 19 sh &
|
||||||
|
JOB_ID=$!
|
||||||
|
echo "Job id: $JOB_ID"
|
||||||
|
|
||||||
|
for i in $(seq 1 1 $TIMEOUT); do
|
||||||
|
if ps -p $JOB_ID >/dev/null 2>&1; then
|
||||||
|
# Job is still running.
|
||||||
|
sleep 1
|
||||||
|
ROUND=$(printf "%s" "$i" | tail -c 1)
|
||||||
|
if [ "$ROUND" -eq "0" ]; then
|
||||||
|
echo "Job $JOB_ID is still running ... waited for $i seconds."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
wait $JOB_ID
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Command $* has timed out"
|
||||||
|
|
||||||
|
kill -9 $JOB_ID
|
||||||
|
exit 255
|
@ -0,0 +1,292 @@
|
|||||||
|
describe("BackgroundRunner widget tests", function()
|
||||||
|
local Device, PluginShare, MockTime, UIManager
|
||||||
|
|
||||||
|
setup(function()
|
||||||
|
require("commonrequire")
|
||||||
|
package.unloadAll()
|
||||||
|
-- Device needs to be loaded before UIManager.
|
||||||
|
Device = require("device")
|
||||||
|
Device.input.waitEvent = function() end
|
||||||
|
PluginShare = require("pluginshare")
|
||||||
|
MockTime = require("mock_time")
|
||||||
|
MockTime:install()
|
||||||
|
UIManager = require("ui/uimanager")
|
||||||
|
UIManager._run_forever = true
|
||||||
|
requireBackgroundRunner()
|
||||||
|
end)
|
||||||
|
|
||||||
|
teardown(function()
|
||||||
|
MockTime:uninstall()
|
||||||
|
package.unloadAll()
|
||||||
|
stopBackgroundRunner()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should start job", function()
|
||||||
|
local executed = false
|
||||||
|
table.insert(PluginShare.backgroundJobs, {
|
||||||
|
when = 10,
|
||||||
|
executable = function()
|
||||||
|
executed = true
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
MockTime:increase(9)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.is_false(executed)
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.is_true(executed)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should repeat job", function()
|
||||||
|
local executed = 0
|
||||||
|
table.insert(PluginShare.backgroundJobs, {
|
||||||
|
when = 1,
|
||||||
|
repeated = function() return executed < 10 end,
|
||||||
|
executable = function()
|
||||||
|
executed = executed + 1
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
|
||||||
|
for i = 1, 10 do
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(i, executed)
|
||||||
|
end
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(10, executed)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should repeat job for predefined times", function()
|
||||||
|
local executed = 0
|
||||||
|
table.insert(PluginShare.backgroundJobs, {
|
||||||
|
when = 1,
|
||||||
|
repeated = 10,
|
||||||
|
executable = function()
|
||||||
|
executed = executed + 1
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
|
||||||
|
for i = 1, 10 do
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(i, executed)
|
||||||
|
end
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(10, executed)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should block long job", function()
|
||||||
|
local executed = 0
|
||||||
|
local job = {
|
||||||
|
when = 1,
|
||||||
|
repeated = true,
|
||||||
|
executable = function()
|
||||||
|
executed = executed + 1
|
||||||
|
MockTime:increase(2)
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
table.insert(PluginShare.backgroundJobs, job)
|
||||||
|
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(1, executed)
|
||||||
|
assert.is_true(job.timeout)
|
||||||
|
assert.is_true(job.blocked)
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(1, executed)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should execute binary", function()
|
||||||
|
local executed = false
|
||||||
|
local job = {
|
||||||
|
when = 1,
|
||||||
|
executable = "ls | grep this-should-not-be-a-file",
|
||||||
|
callback = function()
|
||||||
|
executed = true
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
table.insert(PluginShare.backgroundJobs, job)
|
||||||
|
|
||||||
|
while job.end_sec == nil do
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- grep should return 1 when there is no match.
|
||||||
|
assert.are.equal(1, job.result)
|
||||||
|
assert.is_false(job.timeout)
|
||||||
|
assert.is_false(job.bad_command)
|
||||||
|
assert.is_true(executed)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should forward string environment to the executable", function()
|
||||||
|
local job = {
|
||||||
|
when = 1,
|
||||||
|
repeated = false,
|
||||||
|
executable = "echo $ENV1 | grep $ENV2",
|
||||||
|
environment = {
|
||||||
|
ENV1 = "yes",
|
||||||
|
ENV2 = "yes",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.insert(PluginShare.backgroundJobs, job)
|
||||||
|
|
||||||
|
while job.end_sec == nil do
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- grep should return 0 when there is a match.
|
||||||
|
assert.are.equal(0, job.result)
|
||||||
|
assert.is_false(job.timeout)
|
||||||
|
assert.is_false(job.bad_command)
|
||||||
|
|
||||||
|
job.environment = {
|
||||||
|
ENV1 = "yes",
|
||||||
|
ENV2 = "no",
|
||||||
|
}
|
||||||
|
job.end_sec = nil
|
||||||
|
table.insert(PluginShare.backgroundJobs, job)
|
||||||
|
|
||||||
|
while job.end_sec == nil do
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- grep should return 1 when there is no match.
|
||||||
|
assert.are.equal(1, job.result)
|
||||||
|
assert.is_false(job.timeout)
|
||||||
|
assert.is_false(job.bad_command)
|
||||||
|
|
||||||
|
assert.are.not_equal(os.getenv("ENV1"), "yes")
|
||||||
|
assert.are.not_equal(os.getenv("ENV2"), "yes")
|
||||||
|
assert.are.not_equal(os.getenv("ENV2"), "no")
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should forward function environment to the executable", function()
|
||||||
|
local env2 = "yes"
|
||||||
|
local job = {
|
||||||
|
when = 1,
|
||||||
|
repeated = false,
|
||||||
|
executable = "echo $ENV1 | grep $ENV2",
|
||||||
|
environment = function()
|
||||||
|
return {
|
||||||
|
ENV1 = "yes",
|
||||||
|
ENV2 = env2,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
table.insert(PluginShare.backgroundJobs, job)
|
||||||
|
|
||||||
|
while job.end_sec == nil do
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- grep should return 0 when there is a match.
|
||||||
|
assert.are.equal(0, job.result)
|
||||||
|
assert.is_false(job.timeout)
|
||||||
|
assert.is_false(job.bad_command)
|
||||||
|
|
||||||
|
job.end_sec = nil
|
||||||
|
env2 = "no"
|
||||||
|
table.insert(PluginShare.backgroundJobs, job)
|
||||||
|
|
||||||
|
while job.end_sec == nil do
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- grep should return 1 when there is no match.
|
||||||
|
assert.are.equal(1, job.result)
|
||||||
|
assert.is_false(job.timeout)
|
||||||
|
assert.is_false(job.bad_command)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should block long binary job", function()
|
||||||
|
local executed = 0
|
||||||
|
local job = {
|
||||||
|
when = 1,
|
||||||
|
repeated = true,
|
||||||
|
executable = "sleep 1h",
|
||||||
|
environment = {
|
||||||
|
TIMEOUT = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.insert(PluginShare.backgroundJobs, job)
|
||||||
|
|
||||||
|
while job.end_sec == nil do
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
end
|
||||||
|
|
||||||
|
assert.are.equal(255, job.result)
|
||||||
|
assert.is_true(job.timeout)
|
||||||
|
assert.is_true(job.blocked)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should execute callback", function()
|
||||||
|
local executed = 0
|
||||||
|
table.insert(PluginShare.backgroundJobs, {
|
||||||
|
when = 1,
|
||||||
|
repeated = 10,
|
||||||
|
executable = function() end,
|
||||||
|
callback = function()
|
||||||
|
executed = executed + 1
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
|
||||||
|
for i = 1, 10 do
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(i, executed)
|
||||||
|
end
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(10, executed)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("should not execute two jobs sequentially", function()
|
||||||
|
local executed = 0
|
||||||
|
table.insert(PluginShare.backgroundJobs, {
|
||||||
|
when = 1,
|
||||||
|
executable = function()
|
||||||
|
executed = executed + 1
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
table.insert(PluginShare.backgroundJobs, {
|
||||||
|
when = 1,
|
||||||
|
executable = function()
|
||||||
|
executed = executed + 1
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(1, executed)
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(2, executed)
|
||||||
|
MockTime:increase(2)
|
||||||
|
UIManager:handleInput()
|
||||||
|
assert.are.equal(2, executed)
|
||||||
|
end)
|
||||||
|
end)
|
Loading…
Reference in New Issue