-- Some memory leak checks involving clients as integration tests. local runner = require("_runner") local awful = require("awful") local wibox = require("wibox") local gtable = require("gears.table") -- This test has been proven to perform unreliably on GitHub Actions. -- The additional Lua process that creates the test client will sometimes -- linger for an unpredictable amount of time. -- Stalling the step to check for clean-up increases the chance for success, -- but does not fix the issue. -- See https://github.com/awesomeWM/awesome/pull/3292. if os.getenv("GITHUB_ACTIONS") then print("Skipping unreliable test 'test-leak-client'") runner.run_steps { function() return true end } return end -- Create a titlebar and return a table with references to its member widgets. local function create_titlebar(c) local parts = {} local buttons = { awful.button({ }, 1, function() client.focus = c c:raise() awful.mouse.client.move(c) end), awful.button({ }, 3, function() client.focus = c c:raise() awful.mouse.client.resize(c) end) } -- Widgets that are aligned to the left parts.icon = awful.titlebar.widget.iconwidget(c) local left_layout = wibox.layout.fixed.horizontal(parts.icon) left_layout.buttons = buttons -- The title goes in the middle parts.title = awful.titlebar.widget.titlewidget(c) parts.title:set_align("center") local middle_layout = wibox.layout.flex.horizontal(parts.title) middle_layout.buttons = buttons parts.floating_button = awful.titlebar.widget.floatingbutton(c) parts.maximized_button = awful.titlebar.widget.maximizedbutton(c) parts.sticky_button = awful.titlebar.widget.stickybutton(c) parts.ontop_button = awful.titlebar.widget.ontopbutton(c) parts.close_button = awful.titlebar.widget.closebutton(c) awful.titlebar(c):set_widget(wibox.layout.align.horizontal( left_layout, middle_layout, wibox.layout.fixed.horizontal( parts.floating_button, parts.maximized_button, parts.sticky_button, parts.ontop_button, parts.close_button )), { position = "bottom"}) return parts end -- "Enable" titlebars (so that the titlebar can prevent garbage collection) client.connect_signal("request::manage", function (c) create_titlebar(c) end) -- We tell the garbage collector when to work, disable it collectgarbage("stop") -- We try to get a client representing an already-closed client. -- The first iteration starts xterm, the second keeps a weak reference to it and -- closes it and the last one checks that the client object is GC'able. local objs = nil local second_call = false local state local steps = { function(count) if count == 1 then awful.spawn("xterm") elseif not objs then local c = client.get()[1] if c then objs = setmetatable({ c }, { __mode = "v" }) c:kill() end elseif not second_call then -- Wait for one iteration so that gears.timer handles other delayed -- calls (= the tasklist updates) second_call = true elseif #client.get() == 0 then assert(#objs == 1) -- Test that we have a client and that it's invalid (tostring() -- causes an "invalid object" error) local success, msg = pcall(function() tostring(objs[1]) end) assert(not success, msg) assert(msg:find("invalid object"), msg) -- Check that it is garbage-collectable collectgarbage("collect") assert(#objs == 0, "still clients left after garbage collect") return true end end, -- Test that titlebar widgets are GC'able even when the corresponding client -- still exists. function(count) if count == 1 then awful.spawn("xterm") state = 1 elseif state == 1 then local c = client.get()[1] if c then -- Create the titlebar and store references to its widgets in a -- weak table. objs = setmetatable({}, { __mode = "v" }) gtable.crush(objs, create_titlebar(c)) state = 2 end elseif state == 2 then local c = client.get()[1] -- Recreate the titlebar; at this point old titlebar widgets should -- become unreferenced, except for some delayed calls. create_titlebar(c) -- Wait for one iteration so that delayed calls would complete and -- release remaining references to widgets. state = 3 elseif state == 3 then -- Check that old titlebar widgets have been destroyed. for _ = 1, 10 do collectgarbage("collect") end local num_objs = 0 for k in pairs(objs) do if num_objs == 0 then print("Error: titlebar widgets remaining after GC:") end num_objs = num_objs + 1 print(k) end assert(num_objs == 0) client.get()[1]:kill() state = 4 elseif state == 4 then if #client.get() == 0 then return true end end end, } runner.run_steps(steps) -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80