Merge pull request #773 from Elv13/add_testing_framework

tests: Add a new GUI testing framework
This commit is contained in:
Emmanuel Lepage Vallée 2016-03-31 04:50:32 -04:00
commit b1a93e4540
19 changed files with 1107 additions and 12 deletions

View File

@ -299,6 +299,11 @@ set(AWESOME_THEMES_PATH ${AWESOME_DATA_PATH}/themes)
if(GENERATE_DOC) if(GENERATE_DOC)
# Generate some images and examples # Generate some images and examples
include(docs/generate_examples.cmake) include(docs/generate_examples.cmake)
# Use `include`, rather than `add_subdirectory`, to keep the variables
# The file is a valid CMakeLists.txt and can be executed directly if only
# the image artefacts are needed.
include(tests/examples/CMakeLists.txt)
endif() endif()
# {{{ Configure files # {{{ Configure files

View File

@ -11,6 +11,7 @@ local capi = { awesome = awesome }
local cairo = require("lgi").cairo local cairo = require("lgi").cairo
local color = nil local color = nil
local gdebug = require("gears.debug") local gdebug = require("gears.debug")
local hierarchy = require("wibox.hierarchy")
-- Keep this in sync with build-utils/lgi-check.sh! -- Keep this in sync with build-utils/lgi-check.sh!
local ver_major, ver_minor, ver_patch = string.match(require('lgi.version'), '(%d)%.(%d)%.(%d)') local ver_major, ver_minor, ver_patch = string.match(require('lgi.version'), '(%d)%.(%d)%.(%d)')
@ -205,6 +206,53 @@ function surface.apply_shape_bounding(draw, shape, ...)
draw.shape_bounding = img._native draw.shape_bounding = img._native
end end
local function no_op() end
local function run_in_hierarchy(self, cr, width, height)
local function redraw(h)
h:draw({dpi=96}, cr)
end
local h = hierarchy.new({dpi=96}, self, width, height, redraw, no_op, {})
redraw(h)
return h
end
--- Create an SVG file with this widget content.
-- This is dynamic, so the SVG will be updated along with the widget content.
-- because of this, the painting may happen hover multiple event loop cycles.
-- @tparam widget widget A widget
-- @tparam string path The output file path
-- @tparam number width The surface width
-- @tparam number height The surface height
-- @return The cairo surface
-- @return The hierarchy
function surface.widget_to_svg(widget, path, width, height)
local img = cairo.SvgSurface.create(path, width, height)
local cr = cairo.Context(img)
return img, run_in_hierarchy(widget, cr, width, height)
end
--- Create a cairo surface with this widget content.
-- This is dynamic, so the SVG will be updated along with the widget content.
-- because of this, the painting may happen hover multiple event loop cycles.
-- @tparam widget widget A widget
-- @tparam number width The surface width
-- @tparam number height The surface height
-- @param[opt=cairo.Format.ARGB32] format The surface format
-- @return The cairo surface
-- @return The hierarchy
function surface.widget_to_surface(widget, width, height, format)
local img = cairo.ImageSurface(format or cairo.Format.ARGB32, width, height)
local cr = cairo.Context(img)
return img, run_in_hierarchy(widget, cr, width, height)
end
return setmetatable(surface, surface.mt) return setmetatable(surface, surface.mt)
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80 -- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -1,4 +1,6 @@
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- A container capable of changing the background color, foreground color
-- widget shape.
-- @author Uli Schlachter -- @author Uli Schlachter
-- @copyright 2010 Uli Schlachter -- @copyright 2010 Uli Schlachter
-- @release @AWESOME_VERSION@ -- @release @AWESOME_VERSION@
@ -17,7 +19,7 @@ local unpack = unpack or table.unpack -- luacheck: globals unpack (compatibility
local background = { mt = {} } local background = { mt = {} }
--- Draw this widget -- Draw this widget
function background:draw(context, cr, width, height) function background:draw(context, cr, width, height)
if not self.widget or not self.widget.visible then if not self.widget or not self.widget.visible then
return return
@ -67,7 +69,7 @@ function background:after_draw_children(_, cr)
end end
end end
--- Prepare drawing the children of this widget -- Prepare drawing the children of this widget
function background:before_draw_children(_, cr) function background:before_draw_children(_, cr)
if self.foreground then if self.foreground then
cr:set_source(self.foreground) cr:set_source(self.foreground)
@ -80,14 +82,14 @@ function background:before_draw_children(_, cr)
end end
end end
--- Layout this widget -- Layout this widget
function background:layout(_, width, height) function background:layout(_, width, height)
if self.widget then if self.widget then
return { base.place_widget_at(self.widget, 0, 0, width, height) } return { base.place_widget_at(self.widget, 0, 0, width, height) }
end end
end end
--- Fit this widget into the given area -- Fit this widget into the given area
function background:fit(context, width, height) function background:fit(context, width, height)
if not self.widget then if not self.widget then
return 0, 0 return 0, 0
@ -97,6 +99,8 @@ function background:fit(context, width, height)
end end
--- Set the widget that is drawn on top of the background --- Set the widget that is drawn on top of the background
-- @tparam widget widget The widget to be disaplayed inside of the background
-- area
function background:set_widget(widget) function background:set_widget(widget)
if widget then if widget then
base.check_widget(widget) base.check_widget(widget)
@ -105,20 +109,22 @@ function background:set_widget(widget)
self:emit_signal("widget::layout_changed") self:emit_signal("widget::layout_changed")
end end
--- Get the number of children element -- Get children element
-- @treturn table The children -- @treturn table The children
function background:get_children() function background:get_children()
return {self.widget} return {self.widget}
end end
--- Replace the layout children -- Replace the layout children
-- This layout only accept one children, all others will be ignored -- This layout only accept one children, all others will be ignored
-- @tparam table children A table composed of valid widgets -- @tparam table children A table composed of valid widgets
function background:set_children(children) function background:set_children(children)
self:set_widget(children[1]) self:set_widget(children[1])
end end
--- Set the background to use --- Set the background to use.
--@DOC_wibox_widget_background_bg_EXAMPLE@
-- @param bg A color string, pattern or gradient (see `gears.color`)
function background:set_bg(bg) function background:set_bg(bg)
if bg then if bg then
self.background = color(bg) self.background = color(bg)
@ -128,7 +134,9 @@ function background:set_bg(bg)
self:emit_signal("widget::redraw_needed") self:emit_signal("widget::redraw_needed")
end end
--- Set the foreground to use --- Set the foreground to use.
--@DOC_wibox_widget_background_fg_EXAMPLE@
-- @param fg A color string, pattern or gradient (see `gears.color`)
function background:set_fg(fg) function background:set_fg(fg)
if fg then if fg then
self.foreground = color(fg) self.foreground = color(fg)
@ -138,23 +146,29 @@ function background:set_fg(fg)
self:emit_signal("widget::redraw_needed") self:emit_signal("widget::redraw_needed")
end end
--- Set the background shape --- Set the background shape.
-- @param shape A function taking a context, width and height as arguments --
-- Any other arguments will be passed to the shape function -- Any other arguments will be passed to the shape function
--@DOC_wibox_widget_background_shape_EXAMPLE@
-- @param shape A function taking a context, width and height as arguments
function background:set_shape(shape, ...) function background:set_shape(shape, ...)
self._shape = shape self._shape = shape
self._shape_args = {...} self._shape_args = {...}
self:emit_signal("widget::redraw_needed") self:emit_signal("widget::redraw_needed")
end end
--- When a `shape` is set, also draw a border --- When a `shape` is set, also draw a border.
--
-- See `wibox.widget.background.set_shape` for an usage example.
-- @tparam number width The border width -- @tparam number width The border width
function background:set_shape_border_width(width) function background:set_shape_border_width(width)
self._shape_border_width = width self._shape_border_width = width
self:emit_signal("widget::redraw_needed") self:emit_signal("widget::redraw_needed")
end end
--- When a `shape` is set, also draw a border --- When a `shape` is set, also draw a border.
--
-- See `wibox.widget.background.set_shape` for an usage example.
-- @param[opt=self.foreground] fg The border color, pattern or gradient -- @param[opt=self.foreground] fg The border color, pattern or gradient
function background:set_shape_border_color(fg) function background:set_shape_border_color(fg)
self._shape_border_color = fg self._shape_border_color = fg
@ -162,6 +176,7 @@ function background:set_shape_border_color(fg)
end end
--- When a `shape` is set, make sure nothing is drawn outside of it. --- When a `shape` is set, make sure nothing is drawn outside of it.
--@DOC_wibox_widget_background_clip_EXAMPLE@
-- @tparam boolean value If the shape clip is enable -- @tparam boolean value If the shape clip is enable
function background:set_shape_clip(value) function background:set_shape_clip(value)
self._shape_clip = value self._shape_clip = value

View File

@ -182,6 +182,7 @@ function clear_caches(widget)
clear_caches(w) clear_caches(w)
end end
end end
-- }}} -- }}}
--- Figure out the geometry in device coordinate space. This gives only tight --- Figure out the geometry in device coordinate space. This gives only tight

View File

@ -0,0 +1,251 @@
# This module is designed to allow the Awesome documentation examples to be
# tested.
#
# It shims enough of the Awesome C API to allow code to be executed without an
# actual X server or running Awesome process. These tests are not genuine
# integration tests, but they are the next best thing.
#
# # As secondary goals, this module also generates images of the test result where
# relevant. Those images are used by the documentation and help the developers
# track user interface regressions and glitches. Finally, it also helps to find
# broken code.
cmake_minimum_required(VERSION 3.0.0)
# Get and update the LUA_PATH so the scripts can be executed without Awesome.
execute_process(COMMAND lua -e print\(package.path\) OUTPUT_VARIABLE "LUA_PATH_")
# Add the main awesome lua libraries.
set(LUA_PATH2_ "\
${CMAKE_SOURCE_DIR}/lib/?.lua;\
${CMAKE_SOURCE_DIR}/lib/?/init.lua;\
${CMAKE_SOURCE_DIR}/lib/?;"
)
# Add the C API shims.
set(LUA_PATH3_ "\
${CMAKE_SOURCE_DIR}/tests/examples/shims/?.lua;\
${CMAKE_SOURCE_DIR}/tests/examples/shims/?/init.lua;\
${CMAKE_SOURCE_DIR}/tests/examples/shims/?;"
)
# Done in 3 variables to avoid CMake from implicitly converting into a list.
set(ENV{LUA_PATH} "${LUA_PATH3_}${LUA_PATH2_}${LUA_PATH_}")
# The documentation images directory
set(IMAGE_DIR "${CMAKE_BINARY_DIR}/doc/images")
file(MAKE_DIRECTORY "${IMAGE_DIR}")
# Escape potentially multiline strings to be part of the API doc.
# * add "--" in front of each lines
# * add a custom prefix in front of each lines
# * drop empty lines
# * convert " " lines into empty lines
# * drop lines ending with "--DOC_SOMETHING", they are handled elsewhere
function(escape_string variable content escaped_content line_prefix)
# If DOC_HIDE_ALL is present, do nothing
if(variable MATCHES "--DOC_HIDE_ALL")
return()
endif()
string(REGEX REPLACE "\n" ";" var_lines "${variable}")
set(tmp_output ${content})
foreach (LINE ${var_lines})
if(NOT LINE MATCHES "^.+--DOC_[A-Z]+$")
set(tmp_output ${tmp_output}\n--${line_prefix}${LINE})
endif()
endforeach()
set(${escaped_content} ${tmp_output} PARENT_SCOPE)
endfunction()
# Extract lines with the --DOC_HEADER marker
function(extract_header variable pre_output post_output)
string(REGEX REPLACE "\n" ";" var_lines "${variable}")
set(IS_POST 0)
foreach (LINE ${var_lines})
# This function doesn't escape the lines, so make sure they are
# already comments.
if(LINE MATCHES "^--.*--DOC_HEADER")
# Remove the header tag
string(REGEX REPLACE "[ ]*--DOC_HEADER" "" LINE "${LINE}")
# ldoc is picky about what happen after the first --@, so split
# the output between all that come before and all that come after.
if (NOT IS_POST AND LINE MATCHES "^--[ ]*@")
set(IS_POST 1)
endif()
if (IS_POST)
set(tmp_post_output ${tmp_post_output}\n${line_prefix}${LINE})
else()
set(tmp_pre_output ${tmp_pre_output}\n${line_prefix}${LINE})
endif()
endif()
endforeach()
set(${post_output} "${tmp_post_output}\n--" PARENT_SCOPE)
set(${pre_output} "${tmp_pre_output}\n--" PARENT_SCOPE)
endfunction()
# Read a code file and convert it to ldoc usage example.
# * add "--" in front of each lines
# * drop empty lines
# * convert " " lines into empty lines
# * drop lines ending with "--DOC_HIDE"
function(escape_code path escaped_content pre_header post_header)
file(READ ${path} path)
escape_string("${path}\n" "" escaped_code "")
extract_header("${path}" example_pre_header example_post_header)
set(${escaped_content} ${escaped_code} PARENT_SCOPE)
set(${pre_header} ${example_pre_header} PARENT_SCOPE)
set(${post_header} ${example_post_header} PARENT_SCOPE)
endfunction()
# Execute a lua file.
function(run_test test_path namespace template escaped_content)
# A template is required to know how to handle the output.
if (template STREQUAL " ")
message(FATAL_ERROR "No template found for " ${test_path} ", bye")
endif()
# Get the file name without the extension
get_filename_component(${test_path} TEST_FILE_NAME NAME)
set(IMAGE_PATH "${IMAGE_DIR}/${namespace}_${TEST_FILE_NAME}")
# Execute the script, leave the image extension decision to the test
# SVG is preferred, but PNG is better suited for some tests, like bitmap
# patterns.
execute_process(
COMMAND lua ${template} ${test_path} ${IMAGE_PATH} ${SOURCE_DIR}/.luacov
OUTPUT_VARIABLE TEST_OUTPUT
ERROR_VARIABLE TEST_ERROR
)
# If there is something on stderr, exit
if (NOT TEST_ERROR STREQUAL "")
message("${TEST_OUTPUT}")
message("${TEST_ERROR}")
message(FATAL_ERROR ${test_path} " A test failed, bye")
endif()
# Read the code and turn it into an usage example.
escape_code(${test_path} TEST_CODE TEST_PRE_HEADER TEST_POST_HEADER)
# Build the documentation.
set(TEST_DOC_CONTENT "${TEST_PRE_HEADER}")
# If the image has been created, then add it.
if(EXISTS "${IMAGE_PATH}.svg")
escape_string(
"![Usage example](../images/${namespace}_${TEST_FILE_NAME}.svg)\n"
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
)
elseif(EXISTS "${IMAGE_PATH}.png")
escape_string(
"![Usage example](../images/${namespace}_${TEST_FILE_NAME}.png)\n"
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
)
endif()
# If there is an output, assume it is relevant and add it to the
# documentation under the image.
if(NOT ${TEST_OUTPUT} STREQUAL "")
set(TEST_DOC_CONTENT
"${TEST_DOC_CONTENT}\n--\n--**Usage example output**:\n--"
)
# Markdown require an empty line before and after + 4 spaces.
escape_string(
"\n${TEST_OUTPUT}"
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT " "
)
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}\n--")
endif()
# If there is some @* content, append it.
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}${TEST_POST_HEADER}")
# Only add it if there is something to display.
if(NOT ${TEST_CODE} STREQUAL "\n--")
escape_string(
" @usage"
"${TEST_DOC_CONTENT}" TEST_DOC_CONTENT ""
)
set(TEST_DOC_CONTENT "${TEST_DOC_CONTENT}${TEST_CODE}")
endif()
# Export the outout to the parent scope
set(${escaped_content} "${TEST_DOC_CONTENT}" PARENT_SCOPE)
endfunction()
# Recursive helper function to avoid adding CMakeLists.txt and add_subdirectory
# in every sub-directories.
function(digg path namespace template)
# Check if there is a template for this directory, else use the
# last known one.
if(EXISTS ${path}/template.lua)
message(STATUS "Testing code based on ${namespace}")
set(template ${path}/template.lua)
endif()
# Get the directory content
file(GLOB ex_files RELATIVE "${path}"
"${path}/*")
foreach(ex_file_name ${ex_files})
if(IS_DIRECTORY ${path}/${ex_file_name}
AND (NOT ${ex_file_name} STREQUAL "shims"))
digg("${path}/${ex_file_name}" "${namespace}_${ex_file_name}" ${template})
elseif(${ex_file_name} MATCHES ".lua"
AND NOT ${ex_file_name} MATCHES "template.lua")
# Get the file name without the extension
string(REGEX REPLACE "\\.lua" "" TEST_FILE_NAME ${ex_file_name})
run_test("${path}/${ex_file_name}" "${namespace}" ${template} ESCAPED_CODE_EXAMPLE)
# Set the test name
set(TEST_NAME DOC${namespace}_${TEST_FILE_NAME}_EXAMPLE)
# Anything called @DOC_`namespace`_EXAMPLE@
# in the Lua or C sources will be replaced by the content if that
# variable during the pre-processing
set(ENV{${TEST_NAME}} "${ESCAPED_CODE_EXAMPLE}" CACHE INTERNAL FORCE)
# Update the test list
set(ENV{EXAMPLE_LIST} "$ENV{EXAMPLE_LIST};${TEST_NAME}")
endif()
endforeach()
endfunction()
# Start at the top level then recursively explore the sub-directories to locate
# the test. In parallel, build a namespace for the global variables. Those
# variables will be inserted into the lua source code itself once the examples
# are validated.
digg("${SOURCE_DIR}/tests/examples" "" " ")
# This is ugly, but CMake variable scope system totally ignore 50 years of
# computer science evolution and only support function local variables.
# PARENT_SCOPE is useless in recursive methods and the CMake pre-processor
# can't access ENV variables. So the only (insane) way is to set tons of ENV
# variables, keep track of them in yet another one and set them in the global
# scope once in the "top level" CMakeLists section (it cannot be done from
# functions).
foreach(vari $ENV{EXAMPLE_LIST})
# While at it, replace \" created by CMake by ', &quot; wont work in <code>
string(REGEX REPLACE "\\\"" "'" ${vari} $ENV{${vari}})
endforeach()
message(STATUS "All test passed!")

View File

@ -0,0 +1,154 @@
local file_path, image_path, luacovpath = ...
local cairo = require("lgi").cairo
local pango = require("lgi").Pango
local pangocairo = require("lgi").PangoCairo
-- Set the global shims
-- luacheck: globals awesome root tag screen client mouse
awesome = require( "awesome" )
root = require( "root" )
tag = require( "tag" )
screen = require( "screen" )
client = require( "client" )
mouse = require( "mouse" )
-- Force luacheck to be silent about setting those as unused globals
assert(awesome and root and tag and screen and client and mouse)
-- If luacov is available, use it. Else, do nothing.
pcall(function()
require("luacov.runner")(luacovpath)
end)
local color = require( "gears.color" )
local shape = require( "gears.shape" )
local beautiful = require( "beautiful" )
-- Run the test
local args = loadfile(file_path)() or {}
-- Draw the result
local img = cairo.SvgSurface.create(image_path..".svg", screen._get_extents() )
local cr = cairo.Context(img)
local pango_crx = pangocairo.font_map_get_default():create_context()
local pl = pango.Layout.new(pango_crx)
-- Draw some text inside of the geometry
local function draw_label(geo, text)
cr:save()
cr:set_source(color(beautiful.fg_normal))
cr:translate(geo.x, geo.y)
pl.text = text
cr:show_layout(pl)
cr:restore()
end
-- Draw a mouse cursor at [x,y]
local function draw_mouse(x, y)
cr:move_to(x, y)
cr:rel_line_to( 0, 10)
cr:rel_line_to( 3, -2)
cr:rel_line_to( 3, 4)
cr:rel_line_to( 2, 0)
cr:rel_line_to(-3, -4)
cr:rel_line_to( 4, 0)
cr:close_path()
cr:fill()
end
-- Print an outline for the screens
for _, s in ipairs(screen) do
cr:save()
-- Draw the screen outline
cr:set_source(color("#00000044"))
cr:set_line_width(1.5)
cr:set_dash({10,4},1)
cr:rectangle(s.geometry.x+0.75,s.geometry.y+0.75,s.geometry.width-1.5,s.geometry.height-1.5)
cr:stroke()
-- Draw the workarea outline
cr:set_source(color("#00000033"))
cr:rectangle(s.workarea.x,s.workarea.y,s.workarea.width,s.workarea.height)
cr:stroke()
-- Draw the padding outline
--TODO
cr:restore()
end
cr:set_line_width(beautiful.border_width)
cr:set_source(color(beautiful.border_color))
-- Loop each clients geometry history and paint it
for _, c in ipairs(client.get()) do
local pgeo = nil
for _, geo in ipairs(c._old_geo) do
if not geo._hide then
cr:save()
cr:translate(geo.x, geo.y)
shape.rounded_rect(cr, geo.width, geo.height, args.radius or 5)
cr:stroke_preserve()
cr:set_source(color(c.color or geo._color or beautiful.bg_normal))
cr:fill()
cr:restore()
if geo._label then
draw_label(geo, geo._label)
end
end
-- Draw lines between the old and new corners
if pgeo and not args.hide_lines then
cr:save()
cr:set_source_rgba(0,0,0,.1)
-- Top left
cr:move_to(pgeo.x, pgeo.y)
cr:line_to(geo.x, geo.y)
cr:stroke()
-- Top right
cr:move_to(pgeo.x+pgeo.width, pgeo.y)
cr:line_to(geo.x+pgeo.width, geo.y)
-- Bottom left
cr:move_to(pgeo.x, pgeo.y+pgeo.height)
cr:line_to(geo.x, geo.y+geo.height)
cr:stroke()
-- Bottom right
cr:move_to(pgeo.x+pgeo.width, pgeo.y+pgeo.height)
cr:line_to(geo.x+pgeo.width, geo.y+geo.height)
cr:stroke()
cr:restore()
end
pgeo = geo
end
end
cr:set_source_rgba(1,0,0,1)
cr:set_dash({1,1},1)
-- Paint the mouse cursor position history
for _, h in ipairs(mouse.old_histories) do
local pos = nil
for _, coords in ipairs(h) do
draw_mouse(coords.x, coords.y)
cr:fill()
if pos then
cr:move_to(pos.x, pos.y)
cr:line_to(coords.x, coords.y)
cr:stroke()
end
pos = coords
end
end
img:finish()

View File

@ -0,0 +1,60 @@
local gears_obj = require("gears.object")
-- Emulate the C API classes. They differ from C API objects as connect_signal
-- doesn't take an object as first argument and they support fallback properties
-- handlers.
local function _shim_fake_class()
local obj = gears_obj()
local meta = {
__index = function()end,
__new_index = function()end,
}
obj._connect_signal = obj.connect_signal
function obj.connect_signal(name, func)
return obj._connect_signal(obj, name, func)
end
obj._add_signal = obj.add_signal
function obj.add_signal(name)
return obj._add_signal(obj, name)
end
function obj.set_index_miss_handler(handler)
meta.__index = handler
end
function obj.set_newindex_miss_handler(handler)
meta.__new_index = handler
end
function obj.emit_signal(name, c, ...)
local conns = obj._signals[name] or {strong={}}
for func in pairs(conns.strong) do
func(c, ...)
end
end
return obj, meta
end
local awesome = _shim_fake_class()
awesome._shim_fake_class = _shim_fake_class
-- Avoid c.screen = acreen.focused() to be called, all tests will fail
awesome.startup = true
function awesome.register_xproperty()
end
awesome.add_signal("refresh")
awesome.add_signal("wallpaper_changed")
awesome.add_signal("spawn::canceled")
awesome.add_signal("spawn::timeout")
return awesome
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,24 @@
local lgi = require("lgi")
local Pango = lgi.Pango
-- Default theme for the documentation examples
local module = {
fg_normal = "#000000" ,
bg_normal = "#6181FF7D",
bg_highlight = "#AA00FF7D",
border_color = "#6181FF" ,
border_width = 1.5 ,
-- Fake resources handling
xresources = require("beautiful.xresources")
}
local f = Pango.FontDescription.from_string("sans 8")
function module.get_font()
return f
end
return module
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,8 @@
return {
apply_dpi = function(size, _)
return size
end,
get_dpi = function() return 96 end,
}
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,171 @@
local gears_obj = require("gears.object")
local clients = {}
local client = awesome._shim_fake_class()
local function add_signals(c)
c:add_signal("property::width")
c:add_signal("property::height")
c:add_signal("property::x")
c:add_signal("property::y")
c:add_signal("property::screen")
c:add_signal("property::geometry")
c:add_signal("request::geometry")
c:add_signal("swapped")
c:add_signal("raised")
c:add_signal("property::_label") --Used internally
end
-- Keep an history of the geometry for validation and images
local function push_geometry(c)
table.insert(c._old_geo, c:geometry())
end
-- Undo the last move, but keep it in history
-- local function pop_geometry(c)
-- CURRENTLY UNUSED
-- end
-- Create fake clients to move around
function client.gen_fake(args)
local ret = gears_obj()
ret.type = "normal"
ret.valid = true
ret.size_hints = {}
ret.border_width = 1
-- Apply all properties
for k,v in pairs(args or {}) do
ret[k] = v
end
-- Tests should always set a geometry, but just in case
for _, v in ipairs{"x","y","width","height"} do
ret[v] = ret[v] or 1
end
add_signals(ret)
-- Emulate capi.client.geometry
function ret:geometry(new)
if new then
for k,v in pairs(new) do
ret[k] = v
ret:emit_signal("property::"..k, v)
end
ret:emit_signal("property::geometry", ret:geometry())
push_geometry(ret)
end
return {
x = ret.x,
y = ret.y,
width = ret.width,
height = ret.height,
label = ret._label,
}
end
function ret:isvisible()
return true
end
-- Used for screenshots
function ret:set_label(text)
ret._old_geo[#ret._old_geo]._label = text
end
-- Used for screenshots, hide the current client position
function ret:_hide()
ret._old_geo[#ret._old_geo]._hide = true
end
function ret:get_xproperty()
return nil
end
function ret:tags(new) --FIXME
if new then
ret._tags = new
end
if ret._tags then
return ret._tags
end
for _, t in ipairs(root._tags) do
if t.screen == ret.screen then
return {t}
end
end
return {}
end
-- Record the geometry
ret._old_geo = {}
push_geometry(ret)
-- Set the attributes
ret.screen = args.screen or screen[1]
-- Add to the client list
table.insert(clients, ret)
client.focus = ret
client.emit_signal("manage", ret)
assert(not args.screen or (args.screen == ret.screen))
return ret
end
function client.get(s)
if not s then return clients end
local s2 = screen[s]
local ret = {}
for _,c in ipairs(clients) do
if c.screen == s2 then
table.insert(ret, c)
end
end
return ret
end
client:_add_signal("manage")
client:_add_signal("unmanage")
client:_add_signal("property::urgent")
client:_add_signal("untagged")
client:_add_signal("tagged")
client:_add_signal("property::shape_client_bounding")
client:_add_signal("property::shape_client_clip")
client:_add_signal("property::width")
client:_add_signal("property::height")
client:_add_signal("property::x")
client:_add_signal("property::y")
client:_add_signal("property::geometry")
client:_add_signal("focus")
client:_add_signal("new")
client:_add_signal("property::size_hints_honor")
client:_add_signal("property::struts")
client:_add_signal("property::minimized")
client:_add_signal("property::maximized_horizontal")
client:_add_signal("property::maximized_vertical")
client:_add_signal("property::sticky")
client:_add_signal("property::fullscreen")
client:_add_signal("property::border_width")
client:_add_signal("property::hidden")
client:_add_signal("property::screen")
client:_add_signal("raised")
client:_add_signal("lowered")
client:_add_signal("list")
return client
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,27 @@
local screen = require("screen")
local coords = {x=100,y=100}
local mouse = {
screen = screen[1],
old_histories = {},
history = {},
}
function mouse.coords(args)
if args then
coords.x, coords.y = args.x, args.y
table.insert(mouse.history, {x=coords.x, y=coords.y})
end
return coords
end
function mouse.push_history()
table.insert(mouse.old_histories, mouse.history)
mouse.history = {}
end
return mouse
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,9 @@
local root = {_tags={}}
function root:tags()
return root._tags
end
return root
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,101 @@
local gears_obj = require("gears.object")
local screen = awesome._shim_fake_class()
screen.count = 1
local function create_screen(args)
local s = gears_obj()
s:add_signal("property::workarea")
s:add_signal("padding")
-- Copy the geo in case the args are mutated
local geo = {
x = args.x ,
y = args.y ,
width = args.width ,
height = args.height,
}
function s._resize(args2)
geo.x = args2.x or geo.x
geo.y = args2.y or geo.y
geo.width = args2.width or geo.width
geo.height = args2.height or geo.height
end
local wa = args.workarea_sides or 10
return setmetatable(s,{ __index = function(_, key)
if key == "geometry" then
return {
x = geo.x or 0,
y = geo.y or 0,
width = geo.width ,
height = geo.height,
}
elseif key == "workarea" then
return {
x = (geo.x or 0) + wa ,
y = (geo.y or 0) + wa ,
width = geo.width - 2*wa,
height = geo.height - 2*wa,
}
end
end,
})
end
local screens = {}
function screen._add_screen(args)
local s = create_screen(args)
table.insert(screens, s)
s.index = #screens
screen[#screen+1] = s
screen[s] = s
end
function screen._get_extents()
local xmax, ymax
for _, v in ipairs(screen) do
if not xmax or v.geometry.x+v.geometry.width > xmax.geometry.x+xmax.geometry.width then
xmax = v
end
if not ymax or v.geometry.y+v.geometry.height > ymax.geometry.y+ymax.geometry.height then
ymax = v
end
end
return xmax.geometry.x+xmax.geometry.width, ymax.geometry.y+ymax.geometry.height
end
function screen._clear()
for i=1, #screen do
screen[screen[i]] = nil
screen[i] = nil
end
screens = {}
end
function screen._setup_grid(w, h, rows, args)
args = args or {}
screen._clear()
for i, row in ipairs(rows) do
for j=1, row do
args.x = (j-1)*w + (j-1)*10
args.y = (i-1)*h + (i-1)*10
args.width = w
args.height = h
screen._add_screen(args)
end
end
end
screen._add_screen {width=320, height=240}
return screen
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,53 @@
local gears_obj = require("gears.object")
local tag, meta = awesome._shim_fake_class()
local function new_tag(_, args)
local ret = gears_obj()
ret:add_signal("property::layout")
ret:add_signal("property::name")
ret:add_signal("property::geometry")
ret:add_signal("property::screen")
ret:add_signal("property::mwfact")
ret:add_signal("property::ncol")
ret:add_signal("property::nmaster")
ret:add_signal("property::index")
ret:add_signal("property::useless_gap")
ret:add_signal("property::_wa_tracker")
ret.name = args.name or "test"
ret.activated = true
ret.selected = true
function ret:clients(_) --TODO handle new
local list = {}
for _, c in ipairs(client.get()) do
if c.screen == (ret.screen or screen[1]) then
table.insert(list, c)
end
end
return list
end
table.insert(root._tags, ret)
return setmetatable(ret, {
__index = function(...) return meta.__index(...) end,
__new_index = function(...) return meta.__new_index(...) end
})
end
tag:_add_signal("request::select")
tag:_add_signal("property::selected")
tag:_add_signal("property::activated")
tag:_add_signal("tagged")
tag:_add_signal("untagged")
return setmetatable(tag, {
__call = new_tag,
})
-- vim: filetype=lua:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:textwidth=80

View File

@ -0,0 +1,42 @@
local file_path, image_path, luacovpath = ...
-- Set the global shims
-- luacheck: globals awesome client tag
awesome = require( "awesome" )
client = require( "client" )
tag = require( "tag" )
-- Force luacheck to be silent about setting those as unused globals
assert(awesome and client and tag)
local wibox = require( "wibox" )
local surface = require( "gears.surface" )
-- If luacov is available, use it. Else, do nothing.
pcall(function()
require("luacov.runner")(luacovpath)
end)
-- This is the main widget the tests will use as top level
local container = wibox.layout.fixed.vertical()
-- Let the test request a size and file format
local w, h, image_type = loadfile(file_path)(container)
image_type = image_type or "svg"
-- Emulate the event loop for 10 iterations
for _ = 1, 10 do
awesome:emit_signal("refresh")
end
-- Get the example fallback size (the tests can return a size if the want)
local f_w, f_h = container:fit({dpi=96}, 9999, 9999)
-- There is an overhead that cause testbox "...", add 10 until someone
-- figures out the real equation
f_w, f_h = f_w+10, f_h+10
-- Save to the output file
local img = surface["widget_to_"..image_type](container, image_path.."."..image_type, w or f_w, h or f_h)
img:finish()

View File

@ -0,0 +1,27 @@
local parent = ... --DOC_HIDE
local wibox = require("wibox") --DOC_HIDE
local text_widget = {
text = "Hello world!",
widget = wibox.widget.textbox
}
parent : setup {
{
text_widget,
bg = '#ff0000',
widget = wibox.widget.background
},
{
text_widget,
bg = '#00ff00',
widget = wibox.widget.background
},
{
text_widget,
bg = '#0000ff',
widget = wibox.widget.background
},
spacing = 10,
layout = wibox.layout.fixed.vertical
}

View File

@ -0,0 +1,32 @@
local parent = ... --DOC_HIDE
local wibox = require("wibox") --DOC_HIDE
local gears = {shape = require("gears.shape")} --DOC_HIDE
local beautiful = require("beautiful") --DOC_HIDE
parent : setup {
{
-- Some content may be outside of the shape
{
text = "Hello\nworld!",
widget = wibox.widget.textbox
},
shape = gears.shape.circle,
bg = beautiful.bg_normal,
shape_border_color = beautiful.border_color,
widget = wibox.widget.background
},
{
-- To solve this, clip the content
{
text = "Hello\nworld!",
widget = wibox.widget.textbox
},
shape_clip = true,
shape = gears.shape.circle,
bg = beautiful.bg_normal,
shape_border_color = beautiful.border_color,
widget = wibox.widget.background
},
spacing = 10,
layout = wibox.layout.fixed.vertical
}

View File

@ -0,0 +1,27 @@
local parent = ... --DOC_HIDE
local wibox = require("wibox") --DOC_HIDE
local text_widget = {
text = "Hello world!",
widget = wibox.widget.textbox
}
parent : setup {
{
text_widget,
fg = '#ff0000',
widget = wibox.widget.background
},
{
text_widget,
fg = '#00ff00',
widget = wibox.widget.background
},
{
text_widget,
fg = '#0000ff',
widget = wibox.widget.background
},
spacing = 10,
layout = wibox.layout.fixed.vertical
}

View File

@ -0,0 +1,40 @@
local parent = ... --DOC_HIDE
local wibox = require("wibox") --DOC_HIDE
local gears = {shape = require("gears.shape")} --DOC_HIDE
local beautiful = require("beautiful") --DOC_HIDE
parent : setup {
{
-- Adding a shape without margin may result in cropped output
{
text = "Hello world!",
widget = wibox.widget.textbox
},
shape = gears.shape.hexagon,
bg = beautiful.bg_normal,
shape_border_color = beautiful.border_color,
shape_border_width = beautiful.border_width,
widget = wibox.widget.background
},
{
-- To solve this, use a margin
{
{
text = "Hello world!",
widget = wibox.widget.textbox
},
left = 10,
right = 10,
top = 3,
bottom = 3,
widget = wibox.layout.margin
},
shape = gears.shape.hexagon,
bg = beautiful.bg_normal,
shape_border_color = beautiful.border_color,
shape_border_width = beautiful.border_width,
widget = wibox.widget.background
},
spacing = 10,
layout = wibox.layout.fixed.vertical
}