diff --git a/src/x11.cc b/src/x11.cc index db6c7a5ac..5cd456b25 100644 --- a/src/x11.cc +++ b/src/x11.cc @@ -1146,7 +1146,7 @@ void set_struts(alignment align) { 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(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e45c3fc70..014e42314 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -25,10 +25,8 @@ 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(Catch2 STATIC catch2/catch_amalgamated.cpp) - add_library(conky-mock OBJECT ${mock_sources}) -target_link_libraries(conky-mock Catch2) +add_library(Catch2 STATIC catch2/catch_amalgamated.cpp) add_executable(test-conky test-common.cc ${test_sources}) target_include_directories(test-conky diff --git a/tests/mock/x11-mock.hh b/tests/mock/x11-mock.hh index a366709c5..5b96a9098 100644 --- a/tests/mock/x11-mock.hh +++ b/tests/mock/x11-mock.hh @@ -5,9 +5,11 @@ #include #include #include +#include #include #include #include +#include #include "mock.hh" @@ -36,6 +38,9 @@ enum x11_property_type { Atom name_to_atom(const char *name); const std::string_view atom_to_name(Atom atom); +size_t format_size(std::size_t format); +void dump_x11_blob(const std::byte *data, std::size_t format, + std::size_t length); /// Mutation produced by creating new `Atom`s. struct x11_define_atom : public state_change { @@ -62,29 +67,29 @@ class x11_change_property : public state_change { Atom m_type; std::size_t m_format; set_property_mode m_mode; - const unsigned char *m_data; + std::vector m_data; std::size_t m_element_count; public: x11_change_property(Atom property, Atom type, std::size_t format, - set_property_mode mode, const unsigned char *data, + set_property_mode mode, const std::byte *data, std::size_t element_count) : m_property(property), m_type(type), m_format(format), m_mode(mode), - m_data(data), - m_element_count(element_count) {} + m_element_count(element_count), + m_data(std::vector(data, data + format_size(format) * element_count)) {} static std::string change_name() { return "x11_change_property"; } - Atom property() { return m_property; } - std::string_view property_name() { return atom_to_name(m_property); } - Atom type() { return m_type; } - std::string_view type_name() { return atom_to_name(m_type); } - std::size_t format() { return m_format; } - set_property_mode mode() { return m_mode; } - std::string_view mode_name() { + Atom property() const { return m_property; } + std::string_view property_name() const { return atom_to_name(m_property); } + Atom type() const { return m_type; } + std::string_view type_name() const { return atom_to_name(m_type); } + std::size_t format() const { return m_format; } + set_property_mode mode() const { return m_mode; } + std::string_view mode_name() const { switch (m_mode) { case mock::set_property_mode::REPLACE: return "replace"; @@ -96,8 +101,8 @@ class x11_change_property : public state_change { return "other"; } } - std::size_t element_count() { return m_element_count; } - const unsigned char *const data() { return m_data; } + std::size_t element_count() const { return m_element_count; } + const std::byte *data() const { return m_data.data(); } std::string debug() { return debug_format( @@ -107,26 +112,24 @@ class x11_change_property : public state_change { m_element_count); } }; - -template -struct always_false : std::false_type {}; } // namespace mock +#define REQUIRE_FORMAT_SIZE(format, T) REQUIRE(format == (sizeof(T) * 8)) + // These are only macros because including Catch2 from mocking causes spurious -// errors. +// errors. I whish they weren't because they're such a pain to write this way. // Originally a single templated function: // // template // const D &expect_x11_data( -// const unsigned char * const data, Atom type, std::size_t format, +// const std::byte* data, Atom type, std::size_t format, // std::size_t element_count // ) {...} // // It is a somewhat large blob, but most of it will be compiled away. The only // downside is that lambdas must return owned values. -#define REQUIRE_FORMAT_SIZE(format, T) REQUIRE(format == (sizeof(T) * 8)) #define EXPECT_X11_VALUE(data, type, format, element_count, T) \ []() { \ if constexpr (std::is_same_v && \ @@ -225,6 +228,21 @@ struct always_false : std::false_type {}; } \ }() +#define _COPY_C_ARRAY_TO_CAST(BaseT, TargetT, Length, source) \ + [&]() { \ + auto values = reinterpret_cast(source); \ + auto result = std::array{}; \ + for (size_t i = 0; i < Length; i++) { \ + if constexpr (std::numeric_limits::max() > \ + std::numeric_limits::max()) { \ + CHECK(values[i] >= std::numeric_limits::min()); \ + CHECK(values[i] <= std::numeric_limits::max()); \ + } \ + result[i] = static_cast(values[i]); \ + } \ + return result; \ + }() + #define EXPECT_X11_ARRAY(data, type, format, element_count, T, Count) \ [&]() { \ if constexpr (std::is_same_v && \ @@ -244,14 +262,14 @@ struct always_false : std::false_type {}; } \ REQUIRE_FORMAT_SIZE(format, std::uint32_t); \ REQUIRE(element_count == Count); \ - return *reinterpret_cast *>(data); \ + return _COPY_C_ARRAY_TO_CAST(long, std::uint32_t, Count, data); \ } else if constexpr (std::is_same_v) { \ if (type != mock::x11_property_type::CARDINAL) { \ FAIL("expected CARDINAL array; got: " << mock::atom_to_name(type)); \ } \ REQUIRE_FORMAT_SIZE(format, std::uint32_t); \ REQUIRE(element_count == Count); \ - return *reinterpret_cast *>(data); \ + return _COPY_C_ARRAY_TO_CAST(long, std::uint32_t, Count, data); \ } else if constexpr (std::is_same_v) { \ if (!(type == mock::x11_property_type::ATOM || \ type == mock::x11_property_type::BITMAP || \ @@ -266,68 +284,82 @@ struct always_false : std::false_type {}; } \ REQUIRE_FORMAT_SIZE(format, XID); \ REQUIRE(element_count == Count); \ - return *reinterpret_cast *>(data); \ + return _COPY_C_ARRAY_TO_CAST(long, T, Count, data); \ } else if constexpr (std::is_same_v) { \ if (type != mock::x11_property_type::INTEGER) { \ FAIL("expected INTEGER array; got: " << mock::atom_to_name(type)); \ } \ - REQUIRE_FORMAT_SIZE(format, std::uint32_t); \ + REQUIRE_FORMAT_SIZE(format, std::int32_t); \ REQUIRE(element_count == Count); \ - return *reinterpret_cast *>(data); \ + return _COPY_C_ARRAY_TO_CAST(long, std::int32_t, Count, data); \ } else { \ throw "unimplemented conversion"; \ } \ }() -#define EXPECT_X11_VEC(data, type, format, element_count, T) \ - [&]() { \ - REQUIRE(sizeof(T) == format); \ - if constexpr (std::is_same_v && \ - std::is_same_v) { \ - if (!(type == mock::x11_property_type::ATOM || \ - type == mock::x11_property_type::BITMAP || \ - type == mock::x11_property_type::CARDINAL || \ - type == mock::x11_property_type::PIXMAP || \ - type == mock::x11_property_type::COLORMAP || \ - type == mock::x11_property_type::CURSOR || \ - type == mock::x11_property_type::DRAWABLE || \ - type == mock::x11_property_type::FONT || \ - type == mock::x11_property_type::VISUALID || \ - type == mock::x11_property_type::WINDOW)) { \ - FAIL("expected unsigned long array; got: " \ - << mock::atom_to_name(type)); \ - } \ - REQUIRE_FORMAT_SIZE(format, std::uint32_t); \ - return std::vector(data), element_count>; \ - } else if constexpr (std::is_same_v) { \ - if (type != mock::x11_property_type::CARDINAL) { \ - FAIL("expected CARDINAL array; got: " << mock::atom_to_name(type)); \ - } \ - REQUIRE_FORMAT_SIZE(format, std::uint32_t); \ - return std::vector(data), element_count>; \ - } else if constexpr (std::is_same_v) { \ - if (!(type == mock::x11_property_type::ATOM || \ - type == mock::x11_property_type::BITMAP || \ - type == mock::x11_property_type::PIXMAP || \ - type == mock::x11_property_type::COLORMAP || \ - type == mock::x11_property_type::CURSOR || \ - type == mock::x11_property_type::DRAWABLE || \ - type == mock::x11_property_type::FONT || \ - type == mock::x11_property_type::VISUALID || \ - type == mock::x11_property_type::WINDOW)) { \ - FAIL("expected XID data; got: " << mock::atom_to_name(type)); \ - } \ - REQUIRE_FORMAT_SIZE(format, XID); \ - return std::vector(data), element_count>; \ - } else if constexpr (std::is_same_v) { \ - if (type != mock::x11_property_type::INTEGER) { \ - FAIL("expected INTEGER array; got: " << mock::atom_to_name(type)); \ - } \ - REQUIRE_FORMAT_SIZE(format, std::int32_t); \ - return std::vector(data), element_count>; \ - } else { \ - throw "unimplemented conversion"; \ - } \ +#define _COPY_C_ARRAY_TO_VEC(BaseT, TargetT, source, length) \ + [&]() { \ + auto values = reinterpret_cast(source); \ + auto result = std::vector(length); \ + for (const BaseT *it = values; it < values + length; it++) { \ + if constexpr (std::numeric_limits::max() > \ + std::numeric_limits::max()) { \ + CHECK(*it >= std::numeric_limits::min()); \ + CHECK(*it <= std::numeric_limits::max()); \ + } \ + result.push_back(*it); \ + } \ + return result; \ + }() + +#define EXPECT_X11_VEC(data, type, format, element_count, T) \ + [&]() { \ + if constexpr (std::is_same_v && \ + std::is_same_v) { \ + if (!(type == mock::x11_property_type::ATOM || \ + type == mock::x11_property_type::BITMAP || \ + type == mock::x11_property_type::CARDINAL || \ + type == mock::x11_property_type::PIXMAP || \ + type == mock::x11_property_type::COLORMAP || \ + type == mock::x11_property_type::CURSOR || \ + type == mock::x11_property_type::DRAWABLE || \ + type == mock::x11_property_type::FONT || \ + type == mock::x11_property_type::VISUALID || \ + type == mock::x11_property_type::WINDOW)) { \ + FAIL("expected unsigned long array; got: " \ + << mock::atom_to_name(type)); \ + } \ + REQUIRE_FORMAT_SIZE(format, std::uint32_t); \ + return _COPY_C_ARRAY_TO_VEC(long, std::uint32_t, data, element_count); \ + } else if constexpr (std::is_same_v) { \ + if (type != mock::x11_property_type::CARDINAL) { \ + FAIL("expected CARDINAL array; got: " << mock::atom_to_name(type)); \ + } \ + REQUIRE_FORMAT_SIZE(format, std::uint32_t); \ + return _COPY_C_ARRAY_TO_VEC(long, std::uint32_t, data, element_count); \ + } else if constexpr (std::is_same_v) { \ + if (!(type == mock::x11_property_type::ATOM || \ + type == mock::x11_property_type::BITMAP || \ + type == mock::x11_property_type::PIXMAP || \ + type == mock::x11_property_type::COLORMAP || \ + type == mock::x11_property_type::CURSOR || \ + type == mock::x11_property_type::DRAWABLE || \ + type == mock::x11_property_type::FONT || \ + type == mock::x11_property_type::VISUALID || \ + type == mock::x11_property_type::WINDOW)) { \ + FAIL("expected XID data; got: " << mock::atom_to_name(type)); \ + } \ + REQUIRE_FORMAT_SIZE(format, XID); \ + return _COPY_C_ARRAY_TO_VEC(long, XID, data, element_count); \ + } else if constexpr (std::is_same_v) { \ + if (type != mock::x11_property_type::INTEGER) { \ + FAIL("expected INTEGER array; got: " << mock::atom_to_name(type)); \ + } \ + REQUIRE_FORMAT_SIZE(format, std::int32_t); \ + return _COPY_C_ARRAY_TO_VEC(long, std::int32_t, data, element_count); \ + } else { \ + throw "unimplemented conversion"; \ + } \ }() #endif /* X11_MOCK_HH */ diff --git a/tests/mock/x11.cc b/tests/mock/x11.cc index c2880f2ec..053038164 100644 --- a/tests/mock/x11.cc +++ b/tests/mock/x11.cc @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -111,6 +112,33 @@ const std::string_view atom_to_name(Atom atom) { } return "UNKNOWN"; } + +size_t format_size(std::size_t format) { + if (format == 32) { + return sizeof(long); + } else if (format == 16) { + return sizeof(short); + } else if (format == 8) { + return sizeof(char); + } else { + throw "invalid format"; + } +} +void dump_x11_blob(const std::byte *data, std::size_t format, + std::size_t length) { + size_t entry_len = format_size(format); + for (size_t i = 0; i < length * entry_len; i++) { + if (((i + 1) % entry_len) == 1) { printf("%p: ", data + i); } + // Print bytes in order: + // printf("%02x ", data[i]); + // Reorder bytes: + printf("%02x ", (unsigned char)data[((i / entry_len - 1) * entry_len) + + (2 * entry_len - 1 - (i % entry_len))]); + if (i > 0 && ((i + 1) % entry_len) == 0) { puts(""); } + } + printf("Total bytes: %d\n", (int)(length * entry_len)); + puts(""); +} } // namespace mock extern "C" { @@ -130,9 +158,11 @@ Atom XInternAtom(Display *display, const char *atom_name, int only_if_exists) { int XChangeProperty(Display *display, Window w, Atom property, Atom type, int format, int mode, const unsigned char *data, int nelements) { + // printf("Setting %s property data:\n", mock::atom_to_name(property).data()); + // dump_x11_blob((const std::byte *)data, format, nelements); mock::push_state_change(std::make_unique( - property, type, format, static_cast(mode), data, - nelements)); + property, type, format, static_cast(mode), + (const std::byte *)data, nelements)); return Success; } } diff --git a/tests/test-x11-struts.cc b/tests/test-x11-struts.cc index 989dd1c30..747c17f6a 100644 --- a/tests/test-x11-struts.cc +++ b/tests/test-x11-struts.cc @@ -82,14 +82,14 @@ x11_strut_partial expect_strut_partial(mock::x11_change_property &change) { TEST_CASE("x11 set_struts sets correct struts") { // Temporarily initialize used globals workarea = absolute_rect{vec2i(0, 0), vec2i(600, 800)}; - window.geometry = rect{vec2i(0, 0), vec2i(200, 400)}; + window.geometry = rect{vec2i(0, 0), vec2i(200, 600)}; SECTION("for TOP_LEFT alignment") { set_struts(alignment::TOP_LEFT); mock::x11_change_property full = EXPECT_NEXT_CHANGE(mock::x11_change_property); REQUIRE(full.property_name() == "_NET_WM_STRUT"); - REQUIRE(full.type() == mock::CARDINAL); + REQUIRE(full.type() == mock::x11_property_type::CARDINAL); auto strut_bounds = expect_strut(full); // CHECK(strut_bounds.left == 0); @@ -100,12 +100,14 @@ TEST_CASE("x11 set_struts sets correct struts") { mock::x11_change_property partial = EXPECT_NEXT_CHANGE(mock::x11_change_property); REQUIRE(partial.property_name() == "_NET_WM_STRUT_PARTIAL"); - REQUIRE(partial.type_name() == "CARDINAL"); + REQUIRE(partial.type() == mock::x11_property_type::CARDINAL); auto strut_partial_bounds = expect_strut_partial(partial); // CHECK(strut_partial_bounds.left == 0); - // CHECK(strut_partial_bounds.right == workarea.width() - window.geometry.width()); + // CHECK(strut_partial_bounds.right == + // workarea.width() - window.geometry.width()); // CHECK(strut_partial_bounds.top == 0); - // CHECK(strut_partial_bounds.bottom == workarea.height() - window.geometry.height()); + // CHECK(strut_partial_bounds.bottom == + // workarea.height() - window.geometry.height()); } // Reset globals