tests: Add a framework to make it easier to test multiple screens.
While testing using "the real deal" and with all the tests would be better, it would add a lot of complexity to the testing framework. This module generate multiple multi-screen scenarios and some obvious issues that they can cause. Over time, as more steps are added, it will provide "good enough" testing for multiple screens. Individual test suits can require() this utility to replicate their steps for each multi-screen scenarios.
This commit is contained in:
parent
a0b40bfd8f
commit
db73887b9f
|
@ -0,0 +1,512 @@
|
||||||
|
-- Helper module to replicate a set of steps across multiple screen scenarios
|
||||||
|
-- Ideas for improvements:
|
||||||
|
-- * Export an iterator so the suits can call the screen change step by step
|
||||||
|
-- themselves instead of the current all or nothing
|
||||||
|
-- * Offer multiple scenarios such as screen add, remove, resize/swap instead
|
||||||
|
-- of having to test all corner cases all the time.
|
||||||
|
local wibox = require("wibox")
|
||||||
|
local surface = require("gears.surface")
|
||||||
|
local wallpaper = require("gears.wallpaper")
|
||||||
|
local color = require("gears.color")
|
||||||
|
local shape = require("gears.shape")
|
||||||
|
|
||||||
|
local module = {}
|
||||||
|
|
||||||
|
-- Get the original canvas size before it is "lost"
|
||||||
|
local canvas_w, canvas_h = root.size()
|
||||||
|
|
||||||
|
local half_w, half_h = math.floor(canvas_w/2), math.floor(canvas_h/2)
|
||||||
|
local quarter_w = math.floor(canvas_w/4)
|
||||||
|
|
||||||
|
-- Create virtual screens.
|
||||||
|
-- The steps will be executed on each set of screens.
|
||||||
|
local dispositions = {
|
||||||
|
-- A simple classic, 2 identical screens side by side
|
||||||
|
{
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = half_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = half_w,
|
||||||
|
y = 0,
|
||||||
|
width = half_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Take the previous disposition and swap the screens
|
||||||
|
{
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = half_w,
|
||||||
|
y = 0,
|
||||||
|
width = half_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = half_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
keep = true,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Take the previous disposition and resize the leftmost one
|
||||||
|
{
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = quarter_w,
|
||||||
|
y = 0,
|
||||||
|
width = 3*quarter_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = quarter_w,
|
||||||
|
height = half_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
keep = true,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Take the previous disposition and remove the leftmost screen
|
||||||
|
{
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = quarter_w,
|
||||||
|
y = 0,
|
||||||
|
width = 3*quarter_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
|
keep = true,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Less used, but still quite common vertical setup
|
||||||
|
{
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = canvas_w,
|
||||||
|
height = half_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = half_h,
|
||||||
|
width = canvas_w,
|
||||||
|
height = half_h,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Another popular setup, 22" screens with a better 31" in the middle.
|
||||||
|
-- So, 2 smaller vertical screen with a larger central horizontal one.
|
||||||
|
{
|
||||||
|
-- Left
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = quarter_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
-- Right, this test non-continous screen index on purpose, as this setup
|
||||||
|
-- Often requires dual-GPU, it _will_ happen.
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = canvas_w-quarter_w,
|
||||||
|
y = 0,
|
||||||
|
width = quarter_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
-- Center
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = quarter_w,
|
||||||
|
y = 0,
|
||||||
|
width = half_w,
|
||||||
|
height = math.floor(canvas_h*(9/16)),
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Same as the previous one, but with the gap centered
|
||||||
|
{
|
||||||
|
-- Left
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = quarter_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
-- Right, this test non-continous screen index on purpose, as this setup
|
||||||
|
-- Often requires dual-GPU, it _will_ happen.
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = canvas_w-quarter_w,
|
||||||
|
y = 0,
|
||||||
|
width = quarter_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
-- Center
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = quarter_w,
|
||||||
|
y = math.ceil((canvas_h-(canvas_h*(9/16)))/2),
|
||||||
|
width = half_w,
|
||||||
|
height = math.floor(canvas_h*(9/16)),
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Keep the same screens as the last test, just move them
|
||||||
|
keep = true,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- AMD Eyefinity / (old) NVIDIA MOSAIC style config (symmetric grid)
|
||||||
|
{
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = half_w,
|
||||||
|
height = half_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = half_w,
|
||||||
|
y = 0,
|
||||||
|
width = half_w,
|
||||||
|
height = half_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = half_h,
|
||||||
|
width = half_w,
|
||||||
|
height = half_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = half_w,
|
||||||
|
y = half_h,
|
||||||
|
width = half_w,
|
||||||
|
height = half_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Corner case 1: Non-continuous screens
|
||||||
|
-- If there is nothing and client is drag&dropped into that area, some
|
||||||
|
-- geometry callbacks may break in nil index.
|
||||||
|
{
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = quarter_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = canvas_w-quarter_w,
|
||||||
|
y = 0,
|
||||||
|
width = quarter_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Corner case 2: Nothing at 0x0.
|
||||||
|
-- As some position may fallback to 0x0 this need to be tested often. It
|
||||||
|
-- also caused issues such as #154
|
||||||
|
{
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = half_w,
|
||||||
|
width = half_w,
|
||||||
|
height = half_w,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = half_w,
|
||||||
|
y = 0,
|
||||||
|
width = half_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Corner case 3: Many very small screens.
|
||||||
|
-- On the embedded side of the compuverse, it is possible
|
||||||
|
-- to buy 32x32 RGB OLED screens. They are usually used to display single
|
||||||
|
-- status icons, but why not use them as a desktop! This is a critical
|
||||||
|
-- market for AwesomeWM. Here's a 256px wide strip of tiny screens.
|
||||||
|
-- This may cause wibars to move to the wrong screen accidentally
|
||||||
|
{
|
||||||
|
function() return { x = 0 , y = 0, width = 32, height = 32, } end,
|
||||||
|
function() return { x = 32 , y = 0, width = 32, height = 32, } end,
|
||||||
|
function() return { x = 64 , y = 0, width = 32, height = 32, } end,
|
||||||
|
function() return { x = 96 , y = 0, width = 32, height = 32, } end,
|
||||||
|
function() return { x = 128, y = 0, width = 32, height = 32, } end,
|
||||||
|
function() return { x = 160, y = 0, width = 32, height = 32, } end,
|
||||||
|
function() return { x = 192, y = 0, width = 32, height = 32, } end,
|
||||||
|
function() return { x = 224, y = 0, width = 32, height = 32, } end,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Corner case 4: A screen taller than more than 1 other screen.
|
||||||
|
-- this may cause some issues with client resize and drag&drop move
|
||||||
|
{
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
width = half_w,
|
||||||
|
height = canvas_h,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = half_w,
|
||||||
|
y = 0,
|
||||||
|
width = half_w,
|
||||||
|
height = math.floor(canvas_h/3),
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = half_w,
|
||||||
|
y = math.floor(canvas_h/3),
|
||||||
|
width = half_w,
|
||||||
|
height = math.floor(canvas_h/3),
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
function()
|
||||||
|
return {
|
||||||
|
x = half_w,
|
||||||
|
y = 2*math.floor(canvas_h/3),
|
||||||
|
width = half_w,
|
||||||
|
height = math.floor(canvas_h/3),
|
||||||
|
}
|
||||||
|
end
|
||||||
|
},
|
||||||
|
|
||||||
|
-- The overlapping corner case isn't supported. There is valid use case,
|
||||||
|
-- such as a projector with a smaller resolution than the laptop screen
|
||||||
|
-- in non-scaling mirror mode, but it isn't worth the complexity it brings.
|
||||||
|
}
|
||||||
|
|
||||||
|
local function check_tag_indexes()
|
||||||
|
for s in screen do
|
||||||
|
for i, t in ipairs(s.tags) do
|
||||||
|
assert(t.index == i)
|
||||||
|
assert(t.screen == s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local colors = {
|
||||||
|
"#000030",
|
||||||
|
"#300000",
|
||||||
|
"#043000",
|
||||||
|
"#302E00",
|
||||||
|
"#002C30",
|
||||||
|
"#300030",
|
||||||
|
"#301C00",
|
||||||
|
"#140030",
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Paint it black
|
||||||
|
local function clear_screen()
|
||||||
|
for s in screen do
|
||||||
|
local sur = surface.widget_to_surface(
|
||||||
|
wibox.widget {
|
||||||
|
bg = "#000000",
|
||||||
|
widget = wibox.container.background
|
||||||
|
},
|
||||||
|
s.geometry.width,
|
||||||
|
s.geometry.height
|
||||||
|
)
|
||||||
|
wallpaper.fit(sur, s, "#000000")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make it easier to debug the tests by showing the screen geometry when the
|
||||||
|
-- tests are executed.
|
||||||
|
local function show_screens()
|
||||||
|
wallpaper.set(color("#000000")) -- Should this clear the wallpaper? It doesn't
|
||||||
|
|
||||||
|
-- Add a wallpaper on each screen
|
||||||
|
for i=1, screen.count() do
|
||||||
|
local s = screen[i]
|
||||||
|
|
||||||
|
local w = wibox.widget {
|
||||||
|
{
|
||||||
|
text = table.concat{
|
||||||
|
"Screen: ",i,"\n",
|
||||||
|
s.geometry.width,"x",s.geometry.height,
|
||||||
|
"+",s.geometry.x,",",s.geometry.y
|
||||||
|
},
|
||||||
|
valign = "center",
|
||||||
|
align = "center",
|
||||||
|
widget = wibox.widget.textbox,
|
||||||
|
},
|
||||||
|
bg = colors[i],
|
||||||
|
fg = "#ffffff",
|
||||||
|
shape_border_color = "#ff0000",
|
||||||
|
shape_border_width = 1,
|
||||||
|
shape = shape.rectangle,
|
||||||
|
widget = wibox.container.background
|
||||||
|
}
|
||||||
|
local sur = surface.widget_to_surface(
|
||||||
|
w,
|
||||||
|
s.geometry.width,
|
||||||
|
s.geometry.height
|
||||||
|
)
|
||||||
|
wallpaper.fit(sur, s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function add_steps(real_steps, new_steps)
|
||||||
|
for _, dispo in ipairs(dispositions) do
|
||||||
|
-- Cleanup
|
||||||
|
table.insert(real_steps, function()
|
||||||
|
if #client.get() == 0 then return true end
|
||||||
|
|
||||||
|
for _, c in ipairs(client.get()) do
|
||||||
|
c:kill()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
table.insert(real_steps, function()
|
||||||
|
clear_screen()
|
||||||
|
local keep = dispo.keep or false
|
||||||
|
local old = {}
|
||||||
|
local geos = {}
|
||||||
|
|
||||||
|
check_tag_indexes()
|
||||||
|
|
||||||
|
if keep then
|
||||||
|
for _, sf in ipairs(dispo) do
|
||||||
|
local geo = sf and sf() or nil
|
||||||
|
|
||||||
|
-- If the function return nothing, assume the screen need to
|
||||||
|
-- be destroyed.
|
||||||
|
table.insert(geos, geo or false)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Removed screens need to be explicit.
|
||||||
|
assert(#geos >= screen.count())
|
||||||
|
|
||||||
|
-- Keep a cache to avoid working with invalid data
|
||||||
|
local old_screens = {}
|
||||||
|
for s in screen do
|
||||||
|
table.insert(old_screens, s)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i, s in ipairs(old_screens) do
|
||||||
|
-- Remove the screen (if no new geometry is given
|
||||||
|
if not geos[i] then
|
||||||
|
s:fake_remove()
|
||||||
|
else
|
||||||
|
local cur_geo = s.geometry
|
||||||
|
for _, v in ipairs {"x", "y", "width", "height" } do
|
||||||
|
cur_geo[v] = geos[i][v] or cur_geo[v]
|
||||||
|
end
|
||||||
|
s:fake_resize(
|
||||||
|
cur_geo.x,
|
||||||
|
cur_geo.y,
|
||||||
|
cur_geo.width,
|
||||||
|
cur_geo.height
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add the remaining screens
|
||||||
|
for i=#old_screens + 1, #geos do
|
||||||
|
local geo = geos[i]
|
||||||
|
screen.fake_add(geo.x, geo.y, geo.width, geo.height)
|
||||||
|
end
|
||||||
|
|
||||||
|
check_tag_indexes()
|
||||||
|
else
|
||||||
|
-- Move all existing screens out of the way (to avoid temporary overlapping)
|
||||||
|
for s in screen do
|
||||||
|
s:fake_resize(canvas_w, canvas_h, 1, 1)
|
||||||
|
table.insert(old, s)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Add the new screens
|
||||||
|
for _, sf in ipairs(dispo) do
|
||||||
|
local geo = sf()
|
||||||
|
screen.fake_add(geo.x, geo.y, geo.width, geo.height)
|
||||||
|
table.insert(geos, geo)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove old screens
|
||||||
|
for _, s in ipairs(old) do
|
||||||
|
s:fake_remove()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
show_screens()
|
||||||
|
|
||||||
|
-- Check the result is correct
|
||||||
|
local expected_count = 0
|
||||||
|
for _,v in ipairs(geos) do
|
||||||
|
expected_count = expected_count + (v and 1 or 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert(expected_count == screen.count())
|
||||||
|
for k, geo in ipairs(geos) do
|
||||||
|
if geo then
|
||||||
|
local sgeo = screen[k].geometry
|
||||||
|
assert(geo.x == sgeo.x)
|
||||||
|
assert(geo.y == sgeo.y)
|
||||||
|
assert(geo.width == sgeo.width )
|
||||||
|
assert(geo.height == sgeo.height)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end)
|
||||||
|
|
||||||
|
for _, step in ipairs(new_steps) do
|
||||||
|
table.insert(real_steps, step)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable(module, {
|
||||||
|
__call = function(_,...) return add_steps(...) end
|
||||||
|
})
|
Loading…
Reference in New Issue