diff --git a/.ccls b/.ccls index b4ce26b..5f64bcf 100644 --- a/.ccls +++ b/.ccls @@ -2,6 +2,7 @@ clang -Iinc -Ipro +-Itst -D_GNU_SOURCE -DVERSION="DUMMY" diff --git a/.gitignore b/.gitignore index 757b155..4330579 100644 --- a/.gitignore +++ b/.gitignore @@ -3,13 +3,7 @@ pro/*.h pro/*.c -way-displays -example-client -test - -tags - -.copy - -cfg.dev.yaml +/way-displays +/example-client +/tst-* diff --git a/GNUmakefile b/GNUmakefile index 58d79f1..d8acb5e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -33,7 +33,7 @@ $(PRO_C): $(PRO_X) wayland-scanner private-code $(@:.c=.xml) $@ clean: - rm -f way-displays example_client $(SRC_O) $(EXAMPLE_O) $(PRO_O) $(PRO_H) $(PRO_C) tags .copy + rm -f way-displays example_client $(SRC_O) $(EXAMPLE_O) $(PRO_O) $(PRO_H) $(PRO_C) install: way-displays way-displays.1 cfg.yaml mkdir -p $(DESTDIR)$(PREFIX)/bin @@ -59,10 +59,14 @@ cppcheck: $(SRC_C) $(SRC_CXX) $(INC_H) $(EXAMPLE_C) cppcheck $(^) --enable=warning,unusedFunction,performance,portability $(CPPFLAGS) # make -k iwyu -iwyu: CC = $(IWYU) -Xiwyu --check_also="inc/*h" -iwyu: CXX = $(IWYU) -Xiwyu --check_also="inc/marshalling.h" -iwyu: clean $(SRC_O) -IWYU = /usr/bin/include-what-you-use -Xiwyu --no_fwd_decls -Xiwyu --no_comments -Xiwyu --verbose=2 +iwyu: + $(MAKE) -f tst/GNUmakefile tst-iwyu -.PHONY: all clean install uninstall man cppcheck iwyu +test: + $(MAKE) -f tst/GNUmakefile tst-all + +clean-test: + $(MAKE) -f tst/GNUmakefile tst-clean + +.PHONY: all clean install uninstall man cppcheck iwyu test clean-test tst-iwyu tst-all tst-clean diff --git a/inc/mode.h b/inc/mode.h index ce6ff7a..5e058ad 100644 --- a/inc/mode.h +++ b/inc/mode.h @@ -37,5 +37,7 @@ void mode_free(void *mode); void mode_res_refresh_free(void *mode); +struct Mode *mode_user_mode(struct SList *modes, struct SList *modes_failed, struct UserMode *user_mode); + #endif // MODE_H diff --git a/src/fds.c b/src/fds.c index 7e4f4cc..dda6ebd 100644 --- a/src/fds.c +++ b/src/fds.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include diff --git a/src/head.c b/src/head.c index c2684e7..03b643b 100644 --- a/src/head.c +++ b/src/head.c @@ -1,10 +1,8 @@ #include #include #include -#include #include #include -#include #include #include "head.h" @@ -36,31 +34,6 @@ bool head_matches_user_mode(const void *user_mode, const void *head) { return user_mode && head && head_matches_name_desc_partial(((struct UserMode*)user_mode)->name_desc, (struct Head*)head); } -struct Mode *user_mode(struct Head *head, struct UserMode *user_mode) { - if (!head || !head->name || !user_mode) - return NULL; - - struct SList *i, *j; - - // highest mode matching the user mode - struct SList *mrrs = modes_res_refresh(head->modes); - for (i = mrrs; i; i = i->nex) { - struct ModesResRefresh *mrr = i->val; - if (mrr && mrr_satisfies_user_mode(mrr, user_mode)) { - for (j = mrr->modes; j; j = j->nex) { - struct Mode *mode = j->val; - if (!slist_find_equal(head->modes_failed, NULL, mode)) { - slist_free_vals(&mrrs, mode_res_refresh_free); - return mode; - } - } - } - } - slist_free_vals(&mrrs, mode_res_refresh_free); - - return NULL; -} - struct Mode *preferred_mode(struct Head *head) { if (!head) return NULL; @@ -185,7 +158,7 @@ bool head_matches_name_desc_partial(const void *a, const void *b) { return false; return ( - (head->name && strcasecmp(name_desc, head->name) == 0) || + (head->name && strcasestr(head->name, name_desc)) || (head->description && strcasestr(head->description, name_desc)) ); } @@ -240,7 +213,7 @@ struct Mode *head_find_mode(struct Head *head) { // maybe a user mode struct UserMode *um = slist_find_equal_val(cfg->user_modes, head_matches_user_mode, head); if (um) { - mode = user_mode(head, um); + mode = mode_user_mode(head->modes, head->modes_failed, um); if (!mode && !um->warned_no_mode) { um->warned_no_mode = true; info_user_mode_string(um, buf, sizeof(buf)); diff --git a/src/main.c b/src/main.c index 66f68ca..df1826f 100644 --- a/src/main.c +++ b/src/main.c @@ -5,6 +5,7 @@ #include #include #include +#include #include "cfg.h" #include "client.h" diff --git a/src/mode.c b/src/mode.c index 5f68445..ff720e5 100644 --- a/src/mode.c +++ b/src/mode.c @@ -99,6 +99,31 @@ struct SList *modes_res_refresh(struct SList *modes) { return mrrs; } +struct Mode *mode_user_mode(struct SList *modes, struct SList *modes_failed, struct UserMode *user_mode) { + if (!modes || !user_mode) + return NULL; + + struct SList *i, *j; + + // highest mode matching the user mode + struct SList *mrrs = modes_res_refresh(modes); + for (i = mrrs; i; i = i->nex) { + struct ModesResRefresh *mrr = i->val; + if (mrr && mrr_satisfies_user_mode(mrr, user_mode)) { + for (j = mrr->modes; j; j = j->nex) { + struct Mode *mode = j->val; + if (!slist_find_equal(modes_failed, NULL, mode)) { + slist_free_vals(&mrrs, mode_res_refresh_free); + return mode; + } + } + } + } + slist_free_vals(&mrrs, mode_res_refresh_free); + + return NULL; +} + void mode_free(void *data) { struct Mode *mode = data; diff --git a/src/server.c b/src/server.c index a086bdf..9887c56 100644 --- a/src/server.c +++ b/src/server.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include diff --git a/tst/GNUmakefile b/tst/GNUmakefile new file mode 100644 index 0000000..572b027 --- /dev/null +++ b/tst/GNUmakefile @@ -0,0 +1,33 @@ +include GNUmakefile + +CPPFLAGS += -Itst + +PKGS_TST = cmocka +CFLAGS += $(foreach p,$(PKGS_TST),$(shell pkg-config --cflags $(p))) +CXXFLAGS += $(foreach p,$(PKGS_TST),$(shell pkg-config --cflags $(p))) +LDLIBS += $(foreach p,$(PKGS_TST),$(shell pkg-config --libs $(p))) + +TST_H = $(wildcard tst/*.h) +TST_C = $(wildcard tst/*.c) +TST_CXX = $(wildcard tst/*.cpp) +TST_O = $(TST_C:.c=.o) $(TST_CXX:.cpp=.o) +TST_E = $(TST_O:tst/%.o=%) + +$(TST_O): $(TST_H) $(SRC_O) config.mk GNUmakefile tst/GNUmakefile + +tst-head: tst/tst-head.o $(filter-out src/main.o,$(SRC_O)) $(PRO_O) + $(CXX) -o $(@) $(^) $(LDFLAGS) $(LDLIBS) -Wl,--wrap=mode_dpi,--wrap=mode_user_mode,--wrap=log_info,--wrap=log_warn + +tst-layout: tst/tst-layout.o + $(CXX) -o $(@) $(^) $(LDFLAGS) $(LDLIBS) + +tst-all: $(TST_E) + @for e in $(^); do echo; echo $$e; ./$$e; done + +tst-clean: + rm -f $(TST_O) $(TST_E) + +tst-iwyu: CC = $(IWYU) -Xiwyu --check_also="inc/*h" +tst-iwyu: CXX = $(IWYU) -Xiwyu --check_also="inc/marshalling.h" +tst-iwyu: clean $(TST_O) +IWYU = /usr/bin/include-what-you-use -Xiwyu --no_fwd_decls -Xiwyu --no_comments -Xiwyu --verbose=2 diff --git a/tst/asserts.h b/tst/asserts.h new file mode 100644 index 0000000..74bcfec --- /dev/null +++ b/tst/asserts.h @@ -0,0 +1,19 @@ +#ifndef ASSERTS_H +#define ASSERTS_H + +#include + +#include "wayland-util.h" + +static void _assert_wl_fixed_t_equal_double(wl_fixed_t a, double b, + const char * const file, const int line) { + + if (a != wl_fixed_from_double(b)) { + cmocka_print_error("%g != %g\n", wl_fixed_to_double(a), b); + _fail(file, line); + } +} +#define assert_wl_fixed_t_equal_double(a, b) _assert_wl_fixed_t_equal_double(a, b, __FILE__, __LINE__) + +#endif // ASSERTS_H + diff --git a/tst/tst-head.c b/tst/tst-head.c new file mode 100644 index 0000000..791ef50 --- /dev/null +++ b/tst/tst-head.c @@ -0,0 +1,230 @@ +#include "tst.h" +#include "asserts.h" + +#include +#include +#include +#include +#include + +#include "cfg.h" +#include "head.h" +#include "list.h" +#include "mode.h" +#include "server.h" + +int before_all(void **state) { + return 0; +} + +int after_all(void **state) { + return 0; +} + +int before_each(void **state) { + cfg = cfg_default(); + return 0; +} + +int after_each(void **state) { + cfg_destroy(); + return 0; +} + + +double __wrap_mode_dpi(struct Mode *mode) { + check_expected(mode); + return mock(); +} + +struct Mode *__wrap_mode_user_mode(struct SList *modes, struct SList *modes_failed, struct UserMode *user_mode) { + check_expected(modes); + check_expected(modes_failed); + check_expected(user_mode); + return (struct Mode *)mock(); +} + +void __wrap_log_info(const char *__restrict __format, ...) { + check_expected(__format); +} + +void __wrap_log_warn(const char *__restrict __format, ...) { + check_expected(__format); +} + + +void head_auto_scale__default(void **state) { + struct Head head = { 0 }; + + // no head + assert_wl_fixed_t_equal_double(head_auto_scale(NULL), 1); + + // no desired mode + assert_wl_fixed_t_equal_double(head_auto_scale(&head), 1); +} + +void head_auto_scale__mode(void **state) { + struct Mode mode = { 0 }; + struct Head head = { .desired.mode = &mode }; + + // dpi 0 defaults to 96 + expect_value(__wrap_mode_dpi, mode, &mode); + will_return(__wrap_mode_dpi, 0); + assert_wl_fixed_t_equal_double(head_auto_scale(&head), 1); + + // even 144 + expect_value(__wrap_mode_dpi, mode, &mode); + will_return(__wrap_mode_dpi, 144); + assert_wl_fixed_t_equal_double(head_auto_scale(&head), 144.0 / 96); + + // rounded down to 156 + expect_value(__wrap_mode_dpi, mode, &mode); + will_return(__wrap_mode_dpi, 161); + assert_wl_fixed_t_equal_double(head_auto_scale(&head), 156.0 / 96); + + // rounded up to 168 + expect_value(__wrap_mode_dpi, mode, &mode); + will_return(__wrap_mode_dpi, 162); + assert_wl_fixed_t_equal_double(head_auto_scale(&head), 168.0 / 96); +} + +void head_scaled_dimensions__default(void **state) { + struct Head head = { .scaled.width = 1, .scaled.height = 1, }; + + // no head + head_scaled_dimensions(NULL); + + // no mode + head_scaled_dimensions(&head); + assert_int_equal(head.scaled.width, 1); + assert_int_equal(head.scaled.height, 1); + + // no scale + struct Mode mode = { .width = 200, .height = 100, }; + head.desired.mode = &mode; + + head_scaled_dimensions(&head); + assert_int_equal(head.scaled.width, 1); + assert_int_equal(head.scaled.height, 1); +} + +void head_scaled_dimensions__calculated(void **state) { + struct Mode mode = { .width = 200, .height = 100, }; + struct Head head = { .desired.mode = &mode, }; + + // double, not rotated + head.desired.scale = wl_fixed_from_double(0.5); + head.transform = WL_OUTPUT_TRANSFORM_NORMAL; + + head_scaled_dimensions(&head); + assert_int_equal(head.scaled.width, 400); + assert_int_equal(head.scaled.height, 200); + + // one third, rotated + head.desired.scale = wl_fixed_from_double(3); + head.transform = WL_OUTPUT_TRANSFORM_90; + + head_scaled_dimensions(&head); + assert_int_equal(head.scaled.width, 33); + assert_int_equal(head.scaled.height, 67); +} + +void head_find_mode__none(void **state) { + struct Head head = { 0 }; + struct Mode mode = { 0 }; + + // no head + assert_null(head_find_mode(NULL)); + + // all modes failed + slist_append(&head.modes, &mode); + slist_append(&head.modes_failed, &mode); + assert_null(head_find_mode(&head)); +} + +void head_find_mode__user_available(void **state) { + struct Head head = { 0 }; + struct Mode mode = { 0 }; + slist_append(&head.modes, &mode); + + // user preferred head + struct UserMode *user_mode = (struct UserMode*)calloc(1, sizeof(struct UserMode)); + user_mode->name_desc = strdup("HEAD"); + slist_append(&cfg->user_modes, user_mode); + head.name = strdup("HEAD"); + + // mode matched to user + struct Mode expected = { 0 }; + expect_value(__wrap_mode_user_mode, modes, head.modes); + expect_value(__wrap_mode_user_mode, modes_failed, head.modes_failed); + expect_value(__wrap_mode_user_mode, user_mode, user_mode); + will_return(__wrap_mode_user_mode, &expected); + + assert_ptr_equal(head_find_mode(&head), &expected); +} + +void head_find_mode__user_failed(void **state) { + struct Head head = { 0 }; + struct Mode mode = { 0 }; + slist_append(&head.modes, &mode); + + // user preferred head + struct UserMode *user_mode = (struct UserMode*)calloc(1, sizeof(struct UserMode)); + user_mode->name_desc = strdup("HEAD"); + slist_append(&cfg->user_modes, user_mode); + head.name = strdup("HEAD"); + + // mode not matched to user + expect_value(__wrap_mode_user_mode, modes, head.modes); + expect_value(__wrap_mode_user_mode, modes_failed, head.modes_failed); + expect_value(__wrap_mode_user_mode, user_mode, user_mode); + will_return(__wrap_mode_user_mode, NULL); + + // one and only notices: falling back to preferred then max + expect_any(__wrap_log_warn, __format); + expect_any(__wrap_log_info, __format); + + // user failed, fall back to max + assert_ptr_equal(head_find_mode(&head), &mode); + + // try a second time + expect_value(__wrap_mode_user_mode, modes, head.modes); + expect_value(__wrap_mode_user_mode, modes_failed, head.modes_failed); + expect_value(__wrap_mode_user_mode, user_mode, user_mode); + will_return(__wrap_mode_user_mode, NULL); + + // no notices this time + assert_ptr_equal(head_find_mode(&head), &mode); +} + +void head_find_mode__max(void **state) { + struct Head head = { 0 }; + struct Mode mode = { 0 }; + + slist_append(&head.modes, &mode); + + // one and only notice + expect_any(__wrap_log_info, __format); + assert_ptr_equal(head_find_mode(&head), &mode); + + // no notice + assert_ptr_equal(head_find_mode(&head), &mode); +} + +int main(void) { + const struct CMUnitTest tests[] = { + TEST(head_auto_scale__default), + TEST(head_auto_scale__mode), + + TEST(head_scaled_dimensions__default), + TEST(head_scaled_dimensions__calculated), + + TEST(head_find_mode__none), + TEST(head_find_mode__user_available), + TEST(head_find_mode__user_failed), + TEST(head_find_mode__max), + }; + + return RUN(tests); +} + diff --git a/tst/tst-layout.c b/tst/tst-layout.c new file mode 100644 index 0000000..5681fbb --- /dev/null +++ b/tst/tst-layout.c @@ -0,0 +1,32 @@ +#include "tst.h" + +#include + +int before_all(void **state) { + return 0; +} + +int after_all(void **state) { + return 0; +} + +int before_each(void **state) { + return 0; +} + +int after_each(void **state) { + return 0; +} + +void blargh(void **state) { + assert_int_equal(1, 1); +} + +int main(void) { + const struct CMUnitTest tests[] = { + TEST(blargh), + }; + + return RUN(tests); +} + diff --git a/tst/tst.h b/tst/tst.h new file mode 100644 index 0000000..fa11216 --- /dev/null +++ b/tst/tst.h @@ -0,0 +1,17 @@ +#ifndef TST_H +#define TST_H + +#include +#include +#include +#include + +#include + +// +// test definition +// +#define TEST(t) cmocka_unit_test_setup_teardown(t, before_each, after_each) +#define RUN(t) cmocka_run_group_tests(t, before_all, after_all) + +#endif // TST_H