Skip to content

Commit

Permalink
Test strut bounds
Browse files Browse the repository at this point in the history
Signed-off-by: Tin Švagelj <[email protected]>
  • Loading branch information
Caellian committed Dec 9, 2024
1 parent 089f9ce commit ad5a655
Show file tree
Hide file tree
Showing 5 changed files with 328 additions and 30 deletions.
4 changes: 3 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ 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})
add_library(Catch2 STATIC catch2/catch_amalgamated.cpp)

add_library(conky-mock OBJECT ${mock_sources})
target_link_libraries(conky-mock Catch2)

add_executable(test-conky test-common.cc ${test_sources})
target_include_directories(test-conky
PUBLIC
Expand Down
6 changes: 4 additions & 2 deletions tests/mock/mock.hh
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ std::string debug_format(const std::string& format, Args... args) {
/// This is intended for cases where some library function is internally invoked
/// but would fail if conditions only present at runtime aren't met.
struct state_change {
public:
virtual ~state_change() = default;

static std::string change_name() { return "state_change"; }

/// Returns a string representation of this state change with information
/// necessary to differentiate it from other variants of the same type.
virtual std::string debug() = 0;
Expand Down Expand Up @@ -70,7 +72,7 @@ std::optional<T> next_state_change_t() {
} // namespace mock

/// A variant of `mock::next_state_change_t` that integrates into Catch2.
/// It's a macro because using `FAIL` outside of a test doesn't work.
/// It's a macro because using `FAIL` outside of a test doesn't compile.
#define EXPECT_NEXT_CHANGE(T) \
[]() { \
static_assert(std::is_base_of_v<mock::state_change, T>, \
Expand Down
273 changes: 256 additions & 17 deletions tests/mock/x11-mock.hh
Original file line number Diff line number Diff line change
@@ -1,42 +1,281 @@
#ifndef X11_MOCK_HH
#define X11_MOCK_HH

#include <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <cstdint>
#include <string>
#include <string_view>

#include "mock.hh"

namespace mock {

enum x11_property_type {
ARC = XA_ARC,
ATOM = XA_ATOM,
BITMAP = XA_BITMAP,
CARDINAL = XA_CARDINAL,
COLORMAP = XA_COLORMAP,
CURSOR = XA_CURSOR,
DRAWABLE = XA_DRAWABLE,
FONT = XA_FONT,
INTEGER = XA_INTEGER,
PIXMAP = XA_PIXMAP,
POINT = XA_POINT,
RGB_COLOR_MAP = XA_RGB_COLOR_MAP,
RECTANGLE = XA_RECTANGLE,
STRING = XA_STRING,
VISUALID = XA_VISUALID,
WINDOW = XA_WINDOW,
WM_HINTS = XA_WM_HINTS,
WM_SIZE_HINTS = XA_WM_SIZE_HINTS,
};

Atom name_to_atom(const char *name);
const std::string_view atom_to_name(Atom atom);

/// Mutation produced by creating new `Atom`s.
struct x11_define_atom : public state_change {
std::string name;

x11_define_atom(std::string name) : name(name) {}

static std::string change_name() { return "x11_define_atom"; }

std::string debug() {
return debug_format("x11_define_atom { name: \"%s\" }", name.c_str());
}
};

struct x11_change_property : public state_change {
std::string property;
std::string type;
int format;
int mode;
const unsigned char *data;
size_t element_count;

x11_change_property(std::string property, std::string type, int format,
int mode, const unsigned char *data, size_t element_count)
: property(property),
type(type),
format(format),
mode(mode),
data(data) {}
enum class set_property_mode {
REPLACE = PropModeReplace,
PREPEND = PropModePrepend,
APPEND = PropModeAppend,
};

/// Mutation produced by calls to XChangeProperty.
class x11_change_property : public state_change {
Atom m_property;
Atom m_type;
std::size_t m_format;
set_property_mode m_mode;
const unsigned char *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,
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) {}

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() {
switch (m_mode) {
case mock::set_property_mode::REPLACE:
return "replace";
case mock::set_property_mode::PREPEND:
return "prepend";
case mock::set_property_mode::APPEND:
return "append";
default:
return "other";
}
}
std::size_t element_count() { return m_element_count; }
const unsigned char *const data() { return m_data; }

std::string debug() {
return debug_format(
"x11_change_property { property: \"%s\", type: \"%s\", format: %d, "
"mode: %d, data: [...], element_count: %d }",
property.c_str(), type.c_str(), format, mode, element_count);
"mode: %s (%d), data: [...], element_count: %d }",
property_name(), type_name(), m_format, mode_name(), m_mode,
m_element_count);
}
};

template <class T>
struct always_false : std::false_type {};
} // namespace mock

// These are only macros because including Catch2 from mocking causes spurious
// errors.

// Originally a single templated function:
//
// template <typename D, const std::size_t Count>
// const D &expect_x11_data(
// const unsigned char * const 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 EXPECT_X11_VALUE(data, type, format, element_count, T) \
[]() { \
if constexpr (std::is_same_v<XID, std::uint32_t> && \
std::is_same_v<T, std::uint32_t>) { \
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 data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(format == 32); \
REQUIRE(element_count == 1); \
return *reinterpret_cast<const std::uint32_t *>(data); \
} else if constexpr (std::is_same_v<T, std::uint32_t>) { \
if (type != mock::x11_property_type::CARDINAL) { \
FAIL("expected CARDINAL data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(format == 32); \
REQUIRE(element_count == 1); \
return *reinterpret_cast<const std::uint32_t *>(data); \
} else if constexpr (std::is_same_v<T, XID>) { \
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 == 32); \
REQUIRE(element_count == 1); \
return *reinterpret_cast<const XID *>(data); \
} else if constexpr (std::is_same_v<T, std::int32_t>) { \
if (type != mock::x11_property_type::INTEGER) { \
FAIL("expected INTEGER data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(format == 32); \
REQUIRE(element_count == 1); \
return *reinterpret_cast<const std::int32_t *>(data); \
} else if constexpr (std::is_same_v<T, XRectangle>) { \
if (type != mock::x11_property_type::RECTANGLE) { \
FAIL("expected RECTANGLE data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(format == 16); \
REQUIRE(element_count == 1); \
return *reinterpret_cast<const XRectangle *>(data); \
} else if constexpr (std::is_same_v<T, XArc>) { \
if (type != mock::x11_property_type::ARC) { \
FAIL("expected ARC data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(format == 16); \
REQUIRE(element_count == 1); \
return *reinterpret_cast<const XArc *>(data); \
} else if constexpr (std::is_same_v<T, XColor>) { \
if (type != mock::x11_property_type::RGB_COLOR_MAP) { \
FAIL( \
"expected RGB_COLOR_MAP data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(format == 16); \
REQUIRE(element_count == 1); \
return *reinterpret_cast<const XColor *>(data); \
} else if constexpr (std::is_same_v<T, std::string_view> || \
std::is_same_v<T, std::string>) { \
if (type != mock::x11_property_type::STRING) { \
FAIL("expected STRING data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(format == 8); \
return T(reinterpret_cast<const char *>(data), element_count); \
} else if constexpr (std::is_same_v<T, XWMHints>) { \
if (type != mock::x11_property_type::WM_HINTS) { \
FAIL("expected WM_HINTS data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(element_count == 1); \
return *reinterpret_cast<const XWMHints *>(data); \
} else if constexpr (std::is_same_v<T, XSizeHints>) { \
if (type != mock::x11_property_type::WM_SIZE_HINTS) { \
FAIL( \
"expected WM_SIZE_HINTS data; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(element_count == 1); \
return *reinterpret_cast<const XSizeHints *>(data); \
} else { \
throw "unimplemented conversion" \
} \
}()

#define EXPECT_X11_ARRAY(data, type, format, element_count, T, Count) \
[&]() { \
if constexpr (std::is_same_v<XID, std::uint32_t> && \
std::is_same_v<T, std::uint32_t>) { \
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 == 32); \
REQUIRE(element_count == Count); \
return *reinterpret_cast<const std::array<T, Count> *>(data); \
} else if constexpr (std::is_same_v<T, std::uint32_t>) { \
if (type != mock::x11_property_type::CARDINAL) { \
FAIL("expected CARDINAL array; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(format == 32); \
REQUIRE(element_count == Count); \
return *reinterpret_cast<const std::array<T, Count> *>(data); \
} else if constexpr (std::is_same_v<T, XID>) { \
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 == 32); \
REQUIRE(element_count == Count); \
return *reinterpret_cast<const std::array<T, Count> *>(data); \
} else if constexpr (std::is_same_v<T, std::int32_t>) { \
if (type != mock::x11_property_type::INTEGER) { \
FAIL("expected INTEGER array; got: " << mock::atom_to_name(type)); \
} \
REQUIRE(format == 32); \
REQUIRE(element_count == Count); \
return *reinterpret_cast<const std::array<T, Count> *>(data); \
} else { \
throw "unimplemented conversion"; \
} \
}()

// TODO: EXPECT_X11_VEC

#endif /* X11_MOCK_HH */
15 changes: 9 additions & 6 deletions tests/mock/x11.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <array>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
Expand Down Expand Up @@ -89,6 +90,7 @@ static auto MOCK_ATOMS = std::vector{
"_NET_WM_STRUT_PARTIAL",
};

namespace mock {
Atom name_to_atom(const char *name) {
for (size_t i = 0; i < PREDEFINED_ATOMS.size(); i++) {
if (std::strcmp(name, PREDEFINED_ATOMS[i]) == 0) { return i; }
Expand All @@ -100,20 +102,21 @@ Atom name_to_atom(const char *name) {
}
return 0;
}
std::string atom_to_name(Atom atom) {
const std::string_view atom_to_name(Atom atom) {
if (atom > XA_LAST_PREDEFINED &&
atom - XA_LAST_PREDEFINED < MOCK_ATOMS.size()) {
return std::string(MOCK_ATOMS[atom - XA_LAST_PREDEFINED]);
return std::string_view(MOCK_ATOMS[atom - XA_LAST_PREDEFINED]);
} else if (atom <= XA_LAST_PREDEFINED) {
return std::string(PREDEFINED_ATOMS[atom]);
return std::string_view(PREDEFINED_ATOMS[atom]);
}
return "UNKNOWN";
}
} // namespace mock

extern "C" {
Atom XInternAtom(Display *display, const char *atom_name, int only_if_exists) {
if (only_if_exists) { return name_to_atom(atom_name); }
const auto value = name_to_atom(atom_name);
if (only_if_exists) { return mock::name_to_atom(atom_name); }
const auto value = mock::name_to_atom(atom_name);
if (value != 0) {
return value;
} else {
Expand All @@ -128,7 +131,7 @@ int XChangeProperty(Display *display, Window w, Atom property, Atom type,
int format, int mode, const unsigned char *data,
int nelements) {
mock::push_state_change(std::make_unique<mock::x11_change_property>(
atom_to_name(property), atom_to_name(type), format, mode, data,
property, type, format, static_cast<mock::set_property_mode>(mode), data,
nelements));
return Success;
}
Expand Down
Loading

0 comments on commit ad5a655

Please sign in to comment.