Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for mocking to tests & test X11 struts #2115

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 26 additions & 21 deletions src/conky.cc
Original file line number Diff line number Diff line change
Expand Up @@ -849,27 +849,7 @@ static int get_string_width_special(char *s, int special_index) {

static int text_size_updater(char *s, int special_index);

int last_font_height;
void update_text_area() {
conky::vec2i xy;

if (display_output() == nullptr || !display_output()->graphical()) { return; }

/* update text size if it isn't fixed */
#ifdef OWN_WINDOW
if (fixed_size == 0)
#endif
{
text_size = conky::vec2i(dpi_scale(minimum_width.get(*state)), 0);
last_font_height = font_height();
for_each_line(text_buffer, text_size_updater);

text_size = text_size.max(conky::vec2i(text_size.x() + 1, dpi_scale(minimum_height.get(*state))));
int mw = dpi_scale(maximum_width.get(*state));
if (mw > 0) text_size = text_size.min(conky::vec2i(mw, text_size.y()));
}

alignment align = text_alignment.get(*state);
void apply_window_alignment(conky::vec2i &xy, alignment align) {
/* get text position on workarea */
switch (vertical_alignment(align)) {
case axis_align::START:
Expand Down Expand Up @@ -897,6 +877,31 @@ void update_text_area() {
dpi_scale(gap_x.get(*state)));
break;
}
}

int last_font_height;
void update_text_area() {
conky::vec2i xy;

if (display_output() == nullptr || !display_output()->graphical()) { return; }

/* update text size if it isn't fixed */
#ifdef OWN_WINDOW
if (fixed_size == 0)
#endif
{
text_size = conky::vec2i(dpi_scale(minimum_width.get(*state)), 0);
last_font_height = font_height();
for_each_line(text_buffer, text_size_updater);

text_size = text_size.max(
conky::vec2i(text_size.x() + 1, dpi_scale(minimum_height.get(*state))));
int mw = dpi_scale(maximum_width.get(*state));
if (mw > 0) text_size = text_size.min(conky::vec2i(mw, text_size.y()));
}

alignment align = text_alignment.get(*state);
apply_window_alignment(xy, align);
#ifdef OWN_WINDOW
if (align == alignment::NONE) { // Let the WM manage the window
xy = window.geometry.pos();
Expand Down
2 changes: 1 addition & 1 deletion src/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ extern char window_created;

void destroy_window(void);
void create_gc(void);
void set_struts(int);
void set_struts(alignment);
Copy link
Collaborator Author

@Caellian Caellian Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I kept this line like this for a reason, it previously caused compilation to fail because alignment wasn't available to some file (wayland?) that uses it. Note to self to check before merge.


bool out_to_gui(lua::state &l);

Expand Down
25 changes: 23 additions & 2 deletions src/x11.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1138,15 +1138,36 @@ constexpr size_t operator*(x11_strut index) {
return static_cast<size_t>(index);
}

/* reserve window manager space */
/// Reserve window manager space
///
/// Both `_NET_WM_STRUT` and `_NET_WM_STRUT_PARTIAL` work in coordinates
/// relative to root window (or sometimes Xinerama/Screen).
///
/// Values tell the WM which regions of the screen are invalidated. So, a
/// `bottom` value of `30` means that the window reserves bottom 30px of the
/// screen.
///
/// Because struts aren't handled the best by all WMs when multiple screens are
/// used, horizontal struts (top, bottom) should be preferred because that
/// works well for most multi-screen (horizontal monitor stacking) setups.
/// See: https://gitlab.gnome.org/GNOME/mutter/-/issues/452
///
/// Different WMs handle this differently, some adhere to the spec, some don't.
/// Spec compliant (relative to root window edges):
/// - mutter, metacity, openbox, marco, xfwm
/// Non-compliant (relative to edges of xinerama/single monitor):
/// - compiz, kwin, i3, fluxbox
///
/// Article why KWin doesn't follow the spec:
/// https://blog.martin-graesslin.com/blog/2016/08/panels-on-shared-screen-edges/
void set_struts(alignment align) {
// Middle and none align don't have least significant bit set.
// Ensures either vertical or horizontal axis are start/end
if ((*align & 0b0101) == 0) return;

Atom strut = ATOM(_NET_WM_STRUT);
if (strut != None) {
long sizes[STRUT_COUNT] = {0};
long sizes[STRUT_COUNT] = {};

int display_width = workarea.width();
int display_height = workarea.height();
Expand Down
51 changes: 36 additions & 15 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,26 +1,47 @@
include(CTest)

include_directories(${CMAKE_SOURCE_DIR}/src)
include_directories(${CMAKE_BINARY_DIR})
include_directories(${conky_includes})
file(GLOB test_sources test-*.cc)
file(GLOB mock_sources mock/*.cc)

file(GLOB test_srcs test-*.cc)
macro(EXCLUDING_ANY excluded)
set(__condition "${ARGN}")
string(REGEX MATCH "^IF" __match "${__condition}")
if(__match STREQUAL "")
message(FATAL_ERROR "EXCLUDING_ANY call missing IF keyword")
endif()
unset(__match)
string(REGEX REPLACE "^IF" "" __condition "${__condition}")
if(${__condition})
list(FILTER test_sources EXCLUDE REGEX ".*${excluded}.*\.(cc|hh)")
list(FILTER mock_sources EXCLUDE REGEX ".*${excluded}.*\.(cc|hh)")
endif()
unset(__condition)
endmacro()

if(NOT OS_LINUX)
list(FILTER test_srcs EXCLUDE REGEX ".*linux.*\.cc?")
endif()

if(NOT OS_DARWIN)
list(FILTER test_srcs EXCLUDE REGEX ".*darwin.*\.cc?")
endif()
excluding_any("linux" IF NOT OS_LINUX)
excluding_any("darwin" IF NOT OS_DARWIN)
excluding_any("x11" IF (NOT BUILD_X11) OR OS_DARWIN)
excluding_any("wayland" IF NOT BUILD_WAYLAND)

# Mocking works because it's linked before conky_core, so the linker uses mock
# implementations instead of those that are linked later.
add_library(conky-mock OBJECT ${mock_sources})
target_include_directories(conky-mock
PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_BINARY_DIR}
${conky_includes}
)
add_library(Catch2 STATIC catch2/catch_amalgamated.cpp)

add_executable(test-conky test-common.cc ${test_srcs})
target_link_libraries(test-conky
PRIVATE Catch2
PUBLIC conky_core
add_executable(test-conky test-common.cc events.cc ${test_sources})
target_include_directories(test-conky
PUBLIC
${CMAKE_SOURCE_DIR}/src
${CMAKE_BINARY_DIR}
${conky_includes}
)
target_link_libraries(test-conky Catch2 conky-mock conky_core)
catch_discover_tests(test-conky)

if(CODE_COVERAGE)
Expand Down
20 changes: 20 additions & 0 deletions tests/events.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "catch2/catch.hpp"
#include "mock/display-mock.hh"
#include "mock/mock.hh"

class testRunListener : public Catch::EventListenerBase {
public:
using Catch::EventListenerBase::EventListenerBase;

void testRunStarting(Catch::TestRunInfo const&) {
mock::__internal::init_display_output_mock();
}
void testRunEnded(Catch::TestRunStats const&) {
mock::__internal::delete_display_output_mock();
}
void testCaseStarting(Catch::SectionInfo const&) {
mock::__internal::state_changes.clear();
}
};

CATCH_REGISTER_LISTENER(testRunListener)
21 changes: 21 additions & 0 deletions tests/mock/display-mock.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#include "display-mock.hh"
#include "display-output.hh"

namespace mock {
display_output_mock *output;

display_output_mock &get_mock_output() { return *output; }

namespace __internal {
void init_display_output_mock() {
output = new display_output_mock();
conky::active_display_outputs.push_back(output);
conky::current_display_outputs.push_back(output);
}
void delete_display_output_mock() {
delete output;
conky::current_display_outputs.clear();
conky::active_display_outputs.clear();
}
} // namespace __internal
} // namespace mock
116 changes: 116 additions & 0 deletions tests/mock/display-mock.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
*
* Conky, a system monitor, based on torsmo
*
* Please see COPYING for details
*
* Copyright (C) 2018-2021 François Revol et al.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#ifndef DISPLAY_MOCK_HH
#define DISPLAY_MOCK_HH

#include "colours.h"
#include "display-output.hh"

namespace mock {
/// These are called by Catch2 events, DO NOT use them directly
namespace __internal {
void init_display_output_mock();
void delete_display_output_mock();
} // namespace __internal

/*
* A base class for mock display output that emulates a GUI.
*/
class display_output_mock : public conky::display_output_base {
// Use `mock::get_mock_output`.
explicit display_output_mock() : conky::display_output_base("mock") {};
~display_output_mock() {};

public:
float dpi_scale = 1.0;

// check if available and enabled in settings
bool detect() { return true; }
// connect to DISPLAY and other stuff
bool initialize() { return true; }
bool shutdown() { return true; }

bool graphical() { return true; };
bool draw_line_inner_required() { return true; }

bool main_loop_wait(double t) { return false; }

void sigterm_cleanup() {}
void cleanup() {}

// drawing primitives
void set_foreground_color(Colour c) {}

int calc_text_width(const char *s) { return 0; }

void begin_draw_text() {}
void end_draw_text() {}
void draw_string(const char *s, int w) {}
void line_inner_done() {}

// GUI interface
void draw_string_at(int /*x*/, int /*y*/, const char * /*s*/, int /*w*/) {}
// X11 lookalikes
void set_line_style(int /*w*/, bool /*solid*/) {}
void set_dashes(char * /*s*/) {}
void draw_line(int /*x1*/, int /*y1*/, int /*x2*/, int /*y2*/) {}
void draw_rect(int /*x*/, int /*y*/, int /*w*/, int /*h*/) {}
void fill_rect(int /*x*/, int /*y*/, int /*w*/, int /*h*/) {}
void draw_arc(int /*x*/, int /*y*/, int /*w*/, int /*h*/, int /*a1*/,
int /*a2*/) {}
void move_win(int /*x*/, int /*y*/) {}
float get_dpi_scale() { return dpi_scale; };

void begin_draw_stuff() {}
void end_draw_stuff() {}
void clear_text(int /*exposures*/) {}

// font stuff
int font_height(unsigned int) { return 0; }
int font_ascent(unsigned int) { return 0; }
int font_descent(unsigned int) { return 0; }
void setup_fonts(void) {}
void set_font(unsigned int) {}
void free_fonts(bool /*utf8*/) {}
void load_fonts(bool /*utf8*/) {}

// tty interface
int getx() { return 0; }
int gety() { return 0; }
void gotox(int /*x*/) {}
void gotoy(int /*y*/) {}
void gotoxy(int /*x*/, int /*y*/) {}

void flush() {}

protected:
bool active() { return true; }

friend void __internal::init_display_output_mock();
friend void __internal::delete_display_output_mock();
};

display_output_mock &get_mock_output();
} // namespace mock

#endif /* DISPLAY_MOCK_HH */
22 changes: 22 additions & 0 deletions tests/mock/mock.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "mock.hh"
#include <optional>
#include <utility>

namespace mock {
std::deque<std::unique_ptr<state_change>> __internal::state_changes;

std::deque<std::unique_ptr<state_change>> take_state_changes() {
std::deque<std::unique_ptr<mock::state_change>> result;
std::swap(__internal::state_changes, result);
return result;
}
std::optional<std::unique_ptr<state_change>> next_state_change() {
if (__internal::state_changes.empty()) { return std::nullopt; }
auto front = std::move(__internal::state_changes.front());
__internal::state_changes.pop_front();
return front;
}
void push_state_change(std::unique_ptr<state_change> change) {
__internal::state_changes.push_back(std::move(change));
}
} // namespace mock
Loading
Loading