diff --git a/tests/xtimer_mocked_periph_timer/Makefile b/tests/xtimer_mocked_periph_timer/Makefile new file mode 100644 index 000000000000..13eb2b5952f1 --- /dev/null +++ b/tests/xtimer_mocked_periph_timer/Makefile @@ -0,0 +1,19 @@ +include ../Makefile.tests_common + +TEST_ON_CI_WHITELIST += all + +USEMODULE += embunit + +# Replace enabling xtimer through the build system to manually enabling it +# It allows replacing periph_timer implementation for xtimer by including c +# files +## USEMODULE += xtimer + +# Dependencies +INCLUDES += -I$(RIOTBASE)/sys/xtimer +USEMODULE += div +# Ignore periph_timer dependency it will be mocked + +DISABLE_MODULE += auto_init + +include $(RIOTBASE)/Makefile.include diff --git a/tests/xtimer_mocked_periph_timer/main.c b/tests/xtimer_mocked_periph_timer/main.c new file mode 100644 index 000000000000..fa515eb5662d --- /dev/null +++ b/tests/xtimer_mocked_periph_timer/main.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2018 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief xtimer mocked periph_timer test application + * + * Test xtimer using a mocked implementation of periph_timer + * + * @author Gaëtan Harter + * @} + */ +#include + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#include "embUnit.h" + +/* + * Mock 'xtimer' to use our periph_timer mock implementation + * The '.c' files must be included to have access to private functions and + * mock with the macros + */ +#define timer_init timer_init_mock +#define timer_read timer_read_mock +#define timer_set_absolute timer_set_absolute_mock +#include "xtimer.h" +#include "xtimer.c" +#include "xtimer_core.c" + +/* Allow configuring the mock through here */ +#include "periph_timer_mock.h" + + +static void test_timer_cb(void *arg) +{ + printf("test_timer_cb(%p)\n", arg); +} + + + +/* This is a magic that should tell that it is the first call in + * `_xtimer_set` + */ +#define TIMER_SET_TICK_COUNT (1) + +/* + * A magic value that is meant to make it work with the current + * implementation + */ +#define TIMER_SET_XTIMER_SET_OFFSET (XTIMER_BACKOFF) +#define TIMER_SET_DESCHEDULE_TIME_TICK (TIMER_SET_XTIMER_SET_OFFSET +1) + +/* Simulate descheduling the task for XTIMER_BACKOFF after `_xtimer_now` in + * `_xtimer_set_absolute` + * + * Simulating the de-scheduling with this method assumes we are not in masked + * interrupts, which we currently are. + * A proper test should just unlock a thread but this is for next step. + */ +static int timer_set_deschedule_triggered = 0; +static void _simulate_deschedule_in_timer_set(uint32_t now) +{ + if (now == TIMER_SET_TICK_COUNT && !timer_set_deschedule_triggered) { + /* This is countered by being in an interrupt context */ + DEBUG("De-schedule the thread at %" PRIu32 " for %u\n", + now, TIMER_SET_DESCHEDULE_TIME_TICK); + timer_set_deschedule_triggered = 1; + timer_mock_set(now + TIMER_SET_DESCHEDULE_TIME_TICK); + } + +} +static void test_show_xtimer_set_call_target_in_the_past(void) +{ + xtimer_init(); + timer_mock_set_post_read_cb(_simulate_deschedule_in_timer_set); + + xtimer_t timer_low_offset = {.callback = test_timer_cb}; + + /* TODO I only handle Freq 1MHz for now in this test */ + TEST_ASSERT_MESSAGE(XTIMER_HZ == 1000000, + "Only 1Mhz handled here for this first test"); + + + xtimer_set(&timer_low_offset, TIMER_SET_XTIMER_SET_OFFSET); + + /* The test should not be in the `long_list_head` as it is close by */ + int was_in_list = _remove_timer_from_list(&long_list_head, &timer_low_offset); + TEST_ASSERT_MESSAGE(was_in_list == 0, + "The timer has been placed in the long_list_head for a small sleep time"); +} + + + +/* This is a magic that should tell that it is the first call in + * `_xtimer_set_absolute` + */ +#define SET_ABSOLUTE_TICK_COUNT (2) + +/* + * A magic value that is meant to make it work with the current + * implementation + */ +#define SET_ABSOLUTE_XTIMER_SET_OFFSET (XTIMER_BACKOFF) +#define SET_ABSOLUTE_DESCHEDULE_TIME_TICK (SET_ABSOLUTE_XTIMER_SET_OFFSET) + +/* Simulate descheduling the task for XTIMER_BACKOFF after `_xtimer_now` in + * `_xtimer_set_absolute` + * + * Simulating the de-scheduling with this method assumes we are not in masked + * interrupts, which we currently are. + * A proper test should just unlock a thread but this is for next step. + */ +static int set_absolute_deschedule_triggered = 0; +static int set_absolute_full_loop = 0; +static void _simulate_deschedule_in_set_absolute(uint32_t now) +{ + if (now == SET_ABSOLUTE_TICK_COUNT && !set_absolute_deschedule_triggered) { + /* This is countered by being in an interrupt context */ + DEBUG("De-schedule the thread at %" PRIu32 " for %u\n", + now, SET_ABSOLUTE_DESCHEDULE_TIME_TICK); + set_absolute_deschedule_triggered = 1; + timer_mock_set(now + SET_ABSOLUTE_DESCHEDULE_TIME_TICK); + } + + if (now == 200) { + puts("Stuck in xtimer_spin_until for a full loop, jump out"); + set_absolute_full_loop = 1; + /* Step a lot in the future */ + timer_mock_set((((uint64_t)1) << XTIMER_WIDTH) - 2); + } +} + + + +/* This test adds delay after the _xtimer_now() in `_xtimer_set_absolute` as if + * it was de-scheduled enough time to make `target` in the past. + * + * This makes `xtimer_spin_until` be called with a target already elapsed. + */ +static void test_show_xtimer_set_does_not_handle_overhead(void) +{ + xtimer_init(); + timer_mock_set_post_read_cb(_simulate_deschedule_in_set_absolute); + + xtimer_t timer_low_offset = {.callback = test_timer_cb}; + + /* TODO I only handle Freq 1MHz for now in this test */ + TEST_ASSERT_MESSAGE(XTIMER_HZ == 1000000, + "Only 1Mhz handled here for this first test"); + + + xtimer_set(&timer_low_offset, SET_ABSOLUTE_XTIMER_SET_OFFSET); + DEBUG("Outside of xtimer_set\n"); + TEST_ASSERT(!set_absolute_full_loop); +} + + + +static TestRef tests_xtimer_low_level(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_show_xtimer_set_call_target_in_the_past), + new_TestFixture(test_show_xtimer_set_does_not_handle_overhead), + + }; + EMB_UNIT_TESTCALLER(xtimer_low_level, NULL, NULL, fixtures); + + return (Test *)&xtimer_low_level; +} + +int main(void) +{ + printf("XTIMER_WIDTH: %u\n", XTIMER_WIDTH); + printf("XTIMER_HZ: %lu\n", XTIMER_HZ); + printf("XTIMER_BACKOFF: %u\n", XTIMER_BACKOFF); + + TESTS_START(); + TESTS_RUN(tests_xtimer_low_level()); + TESTS_END(); + return 0; +} diff --git a/tests/xtimer_mocked_periph_timer/periph_timer_mock.c b/tests/xtimer_mocked_periph_timer/periph_timer_mock.c new file mode 100644 index 000000000000..6fbc5df1a533 --- /dev/null +++ b/tests/xtimer_mocked_periph_timer/periph_timer_mock.c @@ -0,0 +1,107 @@ +#include + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#include "periph/timer.h" +#include "periph_timer_mock.h" + + +static struct { + tim_t dev; + unsigned long freq; + timer_cb_t cb; + void *cb_arg; + /* Handle only one channel for now */ + int channel; + unsigned int timeout_value; + + uint32_t current; + + void (*post_read_cb)(uint32_t current); +} mock_timer; + +static void _timer_mock_step_one(void); + + +void timer_mock_set_post_read_cb(void (*post_read_cb)(uint32_t current)) +{ + mock_timer.post_read_cb = post_read_cb; +} + +uint32_t timer_mock_step(uint32_t ticks) +{ + DEBUG("timer_mock_step(%" PRIu32 ") from %" PRIu32 "\n", + ticks, mock_timer.current); + for (uint32_t i = 0; i < ticks; i++) { + _timer_mock_step_one(); + } + return mock_timer.current; +} + +static void _timer_mock_step_one(void) +{ + /* TODO handle callbacks for timer */ + mock_timer.current = (mock_timer.current == mock_timer.timeout_value ? + 0 : mock_timer.current + 1); + if (mock_timer.current == mock_timer.timeout_value) { + printf("periph_timer_mock: TODO Callback not handled\n"); + } +} + +void timer_mock_set(uint32_t ticks) +{ + DEBUG("timer_mock_set(%" PRIu32 ")\n", ticks); + /* TODO This should be lower than `mock_timer.timeout_value` to be handled + * properly.*/ + mock_timer.current = ticks; +} + +static void _timer_cb_mock(void *arg, int channel) +{ + (void)arg; + (void)channel; +} + +/* + * Mock of the original functions + */ +int timer_init_mock(tim_t dev, unsigned long freq, timer_cb_t cb, void *arg) +{ + DEBUG("timer_init_mock(%u): %" PRIu32 ", %p, %p\n", dev, freq, cb, arg); + + + mock_timer.dev = dev; + mock_timer.freq = freq; + mock_timer.cb = cb; + mock_timer.cb_arg = arg; + + mock_timer.current = 0; + + mock_timer.post_read_cb = NULL; + + (void)_timer_cb_mock; + + return 0; +} + +unsigned int timer_read_mock(tim_t dev) +{ + DEBUG("timer_read_mock(%u)\n", dev); + uint32_t now = timer_mock_step(1); + + if (mock_timer.post_read_cb) { + mock_timer.post_read_cb(now); + } + + return now; +} + +int timer_set_absolute_mock(tim_t dev, int channel, unsigned int value) +{ + assert(channel == 0); /* Not handled more than one channel for the moment */ + DEBUG("timer_set_absolute_mock(%u): channel %i, value %u\n", dev, channel, value); + mock_timer.channel = channel; + mock_timer.timeout_value = value; + return 1; +} diff --git a/tests/xtimer_mocked_periph_timer/periph_timer_mock.h b/tests/xtimer_mocked_periph_timer/periph_timer_mock.h new file mode 100644 index 000000000000..98bddfd8691d --- /dev/null +++ b/tests/xtimer_mocked_periph_timer/periph_timer_mock.h @@ -0,0 +1,23 @@ +#ifndef PERIPH_TIMER_MOCK_H +#define PERIPH_TIMER_MOCK_H + +/* Put here any functions that could need to help communicate with the mock */ + +/* + * Step the timer counter by 'ticks' and return the current ticks count after. + */ +uint32_t timer_mock_step(uint32_t ticks); + +/* + * Set the internal timer without handling any callbacks + * Should fit the internal precision + */ +void timer_mock_set(uint32_t ticks); + +/* + * This simulates a function that would be called after the value of + * `timer_read` is calculated. It is executed after evaluating the return value. + */ +void timer_mock_set_post_read_cb(void (*post_read_cb)(uint32_t current)); + +#endif /* PERIPH_TIMER_MOCK_H */