Skip to content

Commit

Permalink
Add display mocking
Browse files Browse the repository at this point in the history
- Split apart some of existing X11 functions so they can be unit tested.
- Improvements of testing code.

Signed-off-by: Tin Švagelj <[email protected]>
  • Loading branch information
Caellian committed Dec 16, 2024
1 parent 1784a59 commit 992e67c
Show file tree
Hide file tree
Showing 10 changed files with 289 additions and 55 deletions.
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
23 changes: 22 additions & 1 deletion src/x11.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1138,7 +1138,28 @@ 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
Expand Down
8 changes: 7 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ 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_sources})
add_executable(test-conky test-common.cc events.cc ${test_sources})
target_include_directories(test-conky
PUBLIC
${CMAKE_SOURCE_DIR}/src
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 */
12 changes: 6 additions & 6 deletions tests/mock/mock.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
#include <utility>

namespace mock {
std::deque<std::unique_ptr<state_change>> _state_changes;
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(_state_changes, result);
std::swap(__internal::state_changes, result);
return result;
}
std::optional<std::unique_ptr<state_change>> next_state_change() {
if (_state_changes.empty()) { return std::nullopt; }
auto front = std::move(_state_changes.front());
_state_changes.pop_front();
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) {
_state_changes.push_back(std::move(change));
__internal::state_changes.push_back(std::move(change));
}
} // namespace mock
15 changes: 12 additions & 3 deletions tests/mock/mock.hh
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ struct state_change {
virtual std::string debug() = 0;
};

/// Implementation detail; shouldn't be used directly.
extern std::deque<std::unique_ptr<state_change>> _state_changes;
namespace __internal {
extern std::deque<std::unique_ptr<state_change>> state_changes;
}

/// Removes all `state_change`s from the queue (clearing it) and returns them.
std::deque<std::unique_ptr<state_change>> take_state_changes();
Expand All @@ -64,7 +65,7 @@ std::optional<T> next_state_change_t() {
if (!result.has_value()) { return std::nullopt; }
auto cast_result = dynamic_cast<T*>(result.value().get());
if (!cast_result) {
_state_changes.push_front(std::move(result.value()));
__internal::state_changes.push_front(std::move(result.value()));
return std::nullopt;
}
return *dynamic_cast<T*>(result.value().release());
Expand Down Expand Up @@ -94,4 +95,12 @@ std::optional<T> next_state_change_t() {
// garbage reinterpretation after FAIL doesn't get returned because FAIL stops
// the test. Should be UNREACHABLE, but I have trouble including it.

#define EXPECT_NO_MORE_CHANGES() \
[]() { \
auto length = mock::__internal::state_changes.size(); \
if (length > 0) { \
FAIL("expected no more state changes; found: " << length); \
} \
}();

#endif /* MOCK_HH */
1 change: 0 additions & 1 deletion tests/mock/x11-mock.hh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
Expand Down
Loading

0 comments on commit 992e67c

Please sign in to comment.