[feat, Kobo] Autoshutdown (#5335)

The methods used here will likely work on most embedded devices, which is why I put them in their own WakeupMgr interface/scheduler module, separate from Kobo.

See https://www.mobileread.com/forums/showthread.php?p=3886403#post3886403 for more context.

Fixes #3806.
This commit is contained in:
Frans de Jonge
2019-09-12 14:15:08 +02:00
committed by GitHub
parent 9163a85b3c
commit e257c4e45e
10 changed files with 503 additions and 164 deletions

View File

@@ -1,75 +1,120 @@
describe("AutoSuspend widget tests", function()
describe("AutoSuspend", function()
setup(function()
require("commonrequire")
package.unloadAll()
require("document/canvascontext"):init(require("device"))
end)
before_each(function()
local Device = require("device")
stub(Device, "isKobo")
Device.isKobo.returns(true)
Device.input.waitEvent = function() end
local UIManager = require("ui/uimanager")
stub(UIManager, "suspend")
UIManager._run_forever = true
G_reader_settings:saveSetting("auto_suspend_timeout_seconds", 10)
require("mock_time"):install()
describe("suspend", function()
before_each(function()
local Device = require("device")
stub(Device, "isKobo")
Device.isKobo.returns(true)
Device.input.waitEvent = function() end
local UIManager = require("ui/uimanager")
stub(UIManager, "suspend")
UIManager._run_forever = true
G_reader_settings:saveSetting("auto_suspend_timeout_seconds", 10)
require("mock_time"):install()
end)
after_each(function()
require("device").isKobo:revert()
require("ui/uimanager").suspend:revert()
G_reader_settings:delSetting("auto_suspend_timeout_seconds")
require("mock_time"):uninstall()
end)
it("should be able to execute suspend when timing out", function()
local mock_time = require("mock_time")
local widget_class = dofile("plugins/autosuspend.koplugin/main.lua")
local widget = widget_class:new() --luacheck: ignore
local UIManager = require("ui/uimanager")
mock_time:increase(5)
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(0)
mock_time:increase(6)
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(1)
mock_time:uninstall()
end)
it("should be able to deprecate last task", function()
local mock_time = require("mock_time")
local widget_class = dofile("plugins/autosuspend.koplugin/main.lua")
local widget = widget_class:new()
mock_time:increase(5)
local UIManager = require("ui/uimanager")
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(0)
widget:onInputEvent()
widget:onSuspend()
widget:onResume()
mock_time:increase(6)
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(0)
mock_time:increase(5)
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(1)
mock_time:uninstall()
end)
end)
after_each(function()
require("device").isKobo:revert()
require("ui/uimanager").suspend:revert()
G_reader_settings:delSetting("auto_suspend_timeout_seconds")
require("mock_time"):uninstall()
end)
describe("shutdown", function()
--- @todo duplicate with above, elegant way to DRY?
before_each(function()
local Device = require("device")
stub(Device, "isKobo")
Device.isKobo.returns(true)
stub(Device, "canPowerOff")
Device.canPowerOff.returns(true)
Device.input.waitEvent = function() end
local UIManager = require("ui/uimanager")
stub(UIManager, "poweroff_action")
UIManager._run_forever = true
G_reader_settings:saveSetting("autoshutdown_timeout_seconds", 10)
require("mock_time"):install()
end)
it("should be able to execute suspend when timing out", function()
local mock_time = require("mock_time")
local widget_class = dofile("plugins/autosuspend.koplugin/main.lua")
local widget = widget_class:new() --luacheck: ignore
local UIManager = require("ui/uimanager")
mock_time:increase(5)
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(0)
mock_time:increase(6)
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(1)
mock_time:uninstall()
end)
after_each(function()
require("device").isKobo:revert()
require("ui/uimanager").poweroff_action:revert()
G_reader_settings:delSetting("autoshutdown_timeout_seconds")
require("mock_time"):uninstall()
end)
it("should be able to initialize several times", function()
local mock_time = require("mock_time")
-- AutoSuspend plugin set the last_action_sec each time it is initialized.
local widget_class = dofile("plugins/autosuspend.koplugin/main.lua")
local widget1 = widget_class:new() --luacheck: ignore
-- So if one more initialization happens, it won't sleep after another 5 seconds.
mock_time:increase(5)
local widget2 = widget_class:new() --luacheck: ignore
local UIManager = require("ui/uimanager")
mock_time:increase(6)
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(1)
mock_time:uninstall()
end)
it("should be able to execute suspend when timing out", function()
local mock_time = require("mock_time")
local widget_class = dofile("plugins/autosuspend.koplugin/main.lua")
local widget = widget_class:new() --luacheck: ignore
local UIManager = require("ui/uimanager")
mock_time:increase(5)
UIManager:handleInput()
assert.stub(UIManager.poweroff_action).was.called(0)
mock_time:increase(6)
UIManager:handleInput()
assert.stub(UIManager.poweroff_action).was.called(1)
mock_time:uninstall()
end)
it("should be able to deprecate last task", function()
local mock_time = require("mock_time")
local widget_class = dofile("plugins/autosuspend.koplugin/main.lua")
local widget = widget_class:new()
mock_time:increase(5)
local UIManager = require("ui/uimanager")
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(0)
widget:onInputEvent()
widget:onSuspend()
widget:onResume()
mock_time:increase(6)
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(0)
mock_time:increase(5)
UIManager:handleInput()
assert.stub(UIManager.suspend).was.called(1)
mock_time:uninstall()
it("should be able to deprecate last task", function()
local mock_time = require("mock_time")
local widget_class = dofile("plugins/autosuspend.koplugin/main.lua")
local widget = widget_class:new()
mock_time:increase(5)
local UIManager = require("ui/uimanager")
UIManager:handleInput()
assert.stub(UIManager.poweroff_action).was.called(0)
widget:onInputEvent()
widget:onSuspend()
widget:onResume()
mock_time:increase(6)
UIManager:handleInput()
assert.stub(UIManager.poweroff_action).was.called(0)
mock_time:increase(5)
UIManager:handleInput()
assert.stub(UIManager.poweroff_action).was.called(1)
mock_time:uninstall()
end)
end)
end)

View File

@@ -22,9 +22,9 @@ describe("UIManager spec", function()
{ time = future2, action = noop },
}
UIManager:_checkTasks()
assert.are.same(#UIManager._task_queue, 2)
assert.are.same(UIManager._task_queue[1].time, future)
assert.are.same(UIManager._task_queue[2].time, future2)
assert.are.same(2, #UIManager._task_queue, 2)
assert.are.same(future, UIManager._task_queue[1].time)
assert.are.same(future2, UIManager._task_queue[2].time)
end)
it("should calcualte wait_until properly in checkTasks routine", function()
@@ -39,7 +39,7 @@ describe("UIManager spec", function()
{ time = {future[1] + 5, future[2]}, action = noop },
}
wait_until, now = UIManager:_checkTasks()
assert.are.same(wait_until, future)
assert.are.same(future, wait_until)
end)
it("should return nil wait_until properly in checkTasks routine", function()
@@ -51,7 +51,7 @@ describe("UIManager spec", function()
{ time = now, action = noop },
}
wait_until, now = UIManager:_checkTasks()
assert.are.same(wait_until, nil)
assert.are.same(nil, wait_until)
end)
it("should insert new task properly in empty task queue", function()
@@ -61,7 +61,7 @@ describe("UIManager spec", function()
assert.are.same(0, #UIManager._task_queue)
UIManager:scheduleIn(50, 'foo')
assert.are.same(1, #UIManager._task_queue)
assert.are.same(UIManager._task_queue[1].action, 'foo')
assert.are.same('foo', UIManager._task_queue[1].action)
end)
it("should insert new task properly in single task queue", function()
@@ -74,7 +74,7 @@ describe("UIManager spec", function()
assert.are.same(1, #UIManager._task_queue)
UIManager:scheduleIn(150, 'quz')
assert.are.same(2, #UIManager._task_queue)
assert.are.same(UIManager._task_queue[1].action, 'quz')
assert.are.same('quz', UIManager._task_queue[1].action)
UIManager:quit()
UIManager._task_queue = {
@@ -83,10 +83,10 @@ describe("UIManager spec", function()
assert.are.same(1, #UIManager._task_queue)
UIManager:scheduleIn(150, 'foo')
assert.are.same(2, #UIManager._task_queue)
assert.are.same(UIManager._task_queue[2].action, 'foo')
assert.are.same('foo', UIManager._task_queue[2].action)
UIManager:scheduleIn(155, 'bar')
assert.are.same(3, #UIManager._task_queue)
assert.are.same(UIManager._task_queue[3].action, 'bar')
assert.are.same('bar', UIManager._task_queue[3].action)
end)
it("should insert new task in ascendant order", function()
@@ -151,7 +151,7 @@ describe("UIManager spec", function()
{ time = now, action = task_to_remove },
}
UIManager:_checkTasks()
assert.are.same(run_count, 2)
assert.are.same(2, run_count)
end)
it("should clear _task_queue_dirty bit before looping", function()
@@ -181,8 +181,8 @@ describe("UIManager spec", function()
end
})
assert.equals(UIManager._window_stack[1].widget.x_prefix_test_number, 2)
assert.equals(UIManager._window_stack[2].widget.x_prefix_test_number, 1)
assert.equals(2, UIManager._window_stack[1].widget.x_prefix_test_number)
assert.equals(1, UIManager._window_stack[2].widget.x_prefix_test_number)
end)
it("should insert second modal widget on top of first modal widget", function()
UIManager:show({
@@ -193,9 +193,9 @@ describe("UIManager spec", function()
end
})
assert.equals(UIManager._window_stack[1].widget.x_prefix_test_number, 2)
assert.equals(UIManager._window_stack[2].widget.x_prefix_test_number, 1)
assert.equals(UIManager._window_stack[3].widget.x_prefix_test_number, 3)
assert.equals(2, UIManager._window_stack[1].widget.x_prefix_test_number)
assert.equals(1, UIManager._window_stack[2].widget.x_prefix_test_number)
assert.equals(3, UIManager._window_stack[3].widget.x_prefix_test_number)
end)
end)
@@ -306,9 +306,9 @@ describe("UIManager spec", function()
}
UIManager:sendEvent("foo")
assert.is.same(call_signals[1], 1)
assert.is.same(call_signals[2], 1)
assert.is.same(call_signals[3], 1)
assert.is.same(1, call_signals[1])
assert.is.same(1, call_signals[2])
assert.is.same(1, call_signals[3])
end)
it("should handle stack change when broadcasting events", function()
@@ -350,7 +350,7 @@ describe("UIManager spec", function()
},
}
UIManager:broadcastEvent("foo")
assert.is.same(#UIManager._window_stack, 0)
assert.is.same(0, #UIManager._window_stack)
end)
it("should handle stack change when closing widgets", function()

View File

@@ -0,0 +1,55 @@
describe("WakeupMgr", function()
local RTC
local WakeupMgr
local epoch1, epoch2, epoch3
setup(function()
require("commonrequire")
package.unloadAll()
RTC = require("ffi/rtc")
WakeupMgr = require("device/wakeupmgr")
-- We could theoretically test this by running the tests as root locally.
stub(WakeupMgr, "setWakeupAlarm")
WakeupMgr.validateWakeupAlarmByProximity = spy.new(function() return true end)
epoch1 = RTC:secondsFromNowToEpoch(1234)
epoch2 = RTC:secondsFromNowToEpoch(123)
epoch3 = RTC:secondsFromNowToEpoch(9999)
end)
it("should add a task", function()
WakeupMgr:addTask(1234, function() end)
assert.is_equal(epoch1, WakeupMgr._task_queue[1].epoch)
assert.stub(WakeupMgr.setWakeupAlarm).was.called(1)
end)
it("should add a task in order", function()
WakeupMgr:addTask(9999, function() end)
assert.is_equal(epoch1, WakeupMgr._task_queue[1].epoch)
assert.stub(WakeupMgr.setWakeupAlarm).was.called(1)
WakeupMgr:addTask(123, function() end)
assert.is_equal(epoch2, WakeupMgr._task_queue[1].epoch)
assert.stub(WakeupMgr.setWakeupAlarm).was.called(2)
end)
it("should execute top task", function()
assert.is_true(WakeupMgr:wakeupAction())
end)
it("should have removed executed task from stack", function()
assert.is_equal(epoch1, WakeupMgr._task_queue[1].epoch)
assert.is_equal(epoch3, WakeupMgr._task_queue[2].epoch)
end)
it("should have scheduled next task after execution", function()
assert.stub(WakeupMgr.setWakeupAlarm).was.called(3)
end)
it("should remove arbitrary task from stack", function()
WakeupMgr:removeTask(2)
assert.is_equal(epoch1, WakeupMgr._task_queue[1].epoch)
assert.is_equal(nil, WakeupMgr._task_queue[2])
end)
it("should execute last task", function()
assert.is_true(WakeupMgr:wakeupAction())
end)
it("should not have scheduled a wakeup without a task", function()
assert.stub(WakeupMgr.setWakeupAlarm).was.called(3)
end)
end)