

12 changed files with 679 additions and 0 deletions
@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de> |
||||
* 2017 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 sys_evtimer |
||||
* @{ |
||||
* |
||||
* @file |
||||
* @brief event timer implementation |
||||
* |
||||
* @author Kaspar Schleiser <kaspar@schleiser.de> |
||||
* @author Martine Lenders <m.lenders@fu-berlin.de> |
||||
* |
||||
* @} |
||||
*/ |
||||
|
||||
#include "div.h" |
||||
#include "irq.h" |
||||
#include "xtimer.h" |
||||
|
||||
#include "evtimer.h" |
||||
|
||||
#define ENABLE_DEBUG (0) |
||||
#include "debug.h" |
||||
|
||||
/* XXX this function is intentionally non-static, since the optimizer can't
|
||||
* handle the pointer hack in this function */ |
||||
void evtimer_add_event_to_list(evtimer_t *evtimer, evtimer_event_t *event) |
||||
{ |
||||
uint32_t delta_sum = 0; |
||||
|
||||
/* we want list->next to point to the first list element. thus we take the
|
||||
* *address* of evtimer->events, then cast it from (evtimer_event_t **) to |
||||
* (evtimer_event_t*). After that, list->next actually equals |
||||
* evtimer->events. */ |
||||
evtimer_event_t *list = (evtimer_event_t *)&evtimer->events; |
||||
|
||||
while (list->next) { |
||||
evtimer_event_t *list_entry = list->next; |
||||
if ((list_entry->offset + delta_sum) > event->offset) { |
||||
break; |
||||
} |
||||
delta_sum += list_entry->offset; |
||||
list = list->next; |
||||
} |
||||
|
||||
event->next = list->next; |
||||
if (list->next) { |
||||
evtimer_event_t *next_entry = list->next; |
||||
next_entry->offset += delta_sum; |
||||
next_entry->offset -= event->offset; |
||||
} |
||||
event->offset -= delta_sum; |
||||
|
||||
list->next = event; |
||||
} |
||||
|
||||
static void _del_event_from_list(evtimer_t *evtimer, evtimer_event_t *event) |
||||
{ |
||||
evtimer_event_t *list = (evtimer_event_t *) &evtimer->events; |
||||
|
||||
while (list->next) { |
||||
evtimer_event_t *list_entry = list->next; |
||||
if (list_entry == event) { |
||||
list->next = event->next; |
||||
if (list->next) { |
||||
list_entry = list->next; |
||||
list_entry->offset += event->offset; |
||||
} |
||||
break; |
||||
} |
||||
list = list->next; |
||||
} |
||||
} |
||||
|
||||
static void _set_timer(xtimer_t *timer, uint32_t offset) |
||||
{ |
||||
uint64_t offset_in_us = (uint64_t)offset * 1000; |
||||
|
||||
DEBUG("evtimer: now=%" PRIu32 " setting xtimer to %" PRIu32 ":%" PRIu32 "\n", |
||||
xtimer_now_usec(), (uint32_t)(offset_in_us >> 32), |
||||
(uint32_t)(offset_in_us)); |
||||
_xtimer_set64(timer, offset_in_us, offset_in_us >> 32); |
||||
} |
||||
|
||||
static void _update_timer(evtimer_t *evtimer) |
||||
{ |
||||
if (evtimer->events) { |
||||
evtimer_event_t *event = evtimer->events; |
||||
_set_timer(&evtimer->timer, event->offset); |
||||
} |
||||
else { |
||||
xtimer_remove(&evtimer->timer); |
||||
} |
||||
} |
||||
|
||||
static uint32_t _get_offset(xtimer_t *timer) |
||||
{ |
||||
uint64_t now = xtimer_now_usec64(); |
||||
uint64_t target = ((uint64_t)timer->long_target) << 32 | timer->target; |
||||
|
||||
if (target <= now) { |
||||
return 0; |
||||
} |
||||
else { |
||||
target -= now; |
||||
/* add half of 125 so integer division rounds to nearest */ |
||||
return div_u64_by_125((target >> 3) + 62); |
||||
} |
||||
} |
||||
|
||||
static void _update_head_offset(evtimer_t *evtimer) |
||||
{ |
||||
if (evtimer->events) { |
||||
evtimer_event_t *event = evtimer->events; |
||||
event->offset = _get_offset(&evtimer->timer); |
||||
DEBUG("evtimer: _update_head_offset(): new head offset %" PRIu32 "\n", event->offset); |
||||
} |
||||
} |
||||
|
||||
void evtimer_add(evtimer_t *evtimer, evtimer_event_t *event) |
||||
{ |
||||
unsigned state = irq_disable(); |
||||
|
||||
DEBUG("evtimer_add(): adding event with offset %" PRIu32 "\n", event->offset); |
||||
|
||||
_update_head_offset(evtimer); |
||||
evtimer_add_event_to_list(evtimer, event); |
||||
if (evtimer->events == event) { |
||||
_set_timer(&evtimer->timer, event->offset); |
||||
} |
||||
irq_restore(state); |
||||
if (sched_context_switch_request) { |
||||
thread_yield_higher(); |
||||
} |
||||
} |
||||
|
||||
void evtimer_del(evtimer_t *evtimer, evtimer_event_t *event) |
||||
{ |
||||
unsigned state = irq_disable(); |
||||
|
||||
DEBUG("evtimer_del(): removing event with offset %" PRIu32 "\n", event->offset); |
||||
|
||||
_update_head_offset(evtimer); |
||||
_del_event_from_list(evtimer, event); |
||||
_update_timer(evtimer); |
||||
irq_restore(state); |
||||
} |
||||
|
||||
static evtimer_event_t *_get_next(evtimer_t *evtimer) |
||||
{ |
||||
evtimer_event_t *event = evtimer->events; |
||||
|
||||
if (event && (event->offset == 0)) { |
||||
evtimer->events = event->next; |
||||
return event; |
||||
} |
||||
else { |
||||
return NULL; |
||||
} |
||||
} |
||||
|
||||
static void _evtimer_handler(void *arg) |
||||
{ |
||||
DEBUG("_evtimer_handler()\n"); |
||||
|
||||
evtimer_t *evtimer = (evtimer_t *)arg; |
||||
|
||||
/* this function gets called directly by xtimer if the set xtimer expired.
|
||||
* Thus the offset of the first event is down to zero. */ |
||||
evtimer_event_t *event = evtimer->events; |
||||
event->offset = 0; |
||||
|
||||
/* iterate the event list */ |
||||
while ((event = _get_next(evtimer))) { |
||||
evtimer->callback(event); |
||||
} |
||||
|
||||
_update_timer(evtimer); |
||||
} |
||||
|
||||
void evtimer_init(evtimer_t *evtimer, evtimer_callback_t handler) |
||||
{ |
||||
evtimer->callback = handler; |
||||
evtimer->timer.callback = _evtimer_handler; |
||||
evtimer->timer.arg = (void *)evtimer; |
||||
evtimer->events = NULL; |
||||
} |
||||
|
||||
void evtimer_print(const evtimer_t *evtimer) |
||||
{ |
||||
evtimer_event_t *list = evtimer->events; |
||||
|
||||
while (list->next) { |
||||
evtimer_event_t *list_entry = list->next; |
||||
printf("ev offset=%u\n", (unsigned)list_entry->offset); |
||||
list = list->next; |
||||
} |
||||
} |
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de> |
||||
* 2017 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. |
||||
*/ |
||||
|
||||
/**
|
||||
* @defgroup sys_evtimer Millisecond interval event timers |
||||
* @ingroup sys |
||||
* @brief Provides timers for events up to @$2^{32}@$ milliseconds in the |
||||
* future |
||||
* |
||||
* @note Experimental and likely to replaced with unified timer API |
||||
* |
||||
* RIOT's main timer subsystem is @ref sys_xtimer "xtimer", but for many |
||||
* applications @ref sys_xtimer "xtimer's" 64-bit absolute time values are |
||||
* wasteful or clumsy to use. |
||||
* |
||||
* Compared to @ref sys_xtimer "xtimer", evtimer offers: |
||||
* |
||||
* - only relative 32-bit millisecond timer values |
||||
* Events can be scheduled with a relative offset of up to ~49.7 days in the |
||||
* future. |
||||
* **For time-critical stuff, use @ref sys_xtimer "xtimer"!** |
||||
* - more flexible, "intrusive" timer type @ref evtimer_event_t only contains |
||||
* the necessary fields, which can be extended as needed, and handlers define |
||||
* actions taken on timer triggers. Check out @ref evtimer_msg_event_t as |
||||
* example. |
||||
* - uses @ref sys_xtimer "xtimer" as backend |
||||
* |
||||
* @{ |
||||
* |
||||
* @file |
||||
* @brief evtimer API definitions |
||||
* |
||||
* @author Kaspar Schleiser <kaspar@schleiser.de> |
||||
* @author Martine Lenders <m.lenders@fu-berlin.de> |
||||
*/ |
||||
|
||||
#ifndef EVTIMER_H |
||||
#define EVTIMER_H |
||||
|
||||
#include <stdint.h> |
||||
|
||||
#include "xtimer.h" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief Generic event |
||||
*/ |
||||
typedef struct evtimer_event { |
||||
struct evtimer_event *next; /**< the next event in the queue */ |
||||
uint32_t offset; /**< offset in milliseconds from previous event */ |
||||
} evtimer_event_t; |
||||
|
||||
/**
|
||||
* @brief Event timer callback type |
||||
*/ |
||||
typedef void(*evtimer_callback_t)(evtimer_event_t* event); |
||||
|
||||
/**
|
||||
* @brief Event timer |
||||
*/ |
||||
typedef struct { |
||||
xtimer_t timer; /**< Timer */ |
||||
evtimer_callback_t callback; /**< Handler function for this evtimer's
|
||||
event type */ |
||||
evtimer_event_t *events; /**< Event queue */ |
||||
} evtimer_t; |
||||
|
||||
/**
|
||||
* @brief Initializes an event timer |
||||
* |
||||
* @param[in] evtimer An event timer |
||||
* @param[in] handler An event handler function |
||||
*/ |
||||
void evtimer_init(evtimer_t *evtimer, evtimer_callback_t handler); |
||||
|
||||
/**
|
||||
* @brief Adds event to an event timer |
||||
* |
||||
* @param[in] evtimer An event timer |
||||
* @param[in] event An event |
||||
*/ |
||||
void evtimer_add(evtimer_t *evtimer, evtimer_event_t *event); |
||||
|
||||
/**
|
||||
* @brief Removes an event from an event timer |
||||
* |
||||
* @param[in] evtimer An event timer |
||||
* @param[in] event An event |
||||
*/ |
||||
void evtimer_del(evtimer_t *evtimer, evtimer_event_t *event); |
||||
|
||||
/**
|
||||
* @brief Print overview of current state of an event timer |
||||
* |
||||
* @param[in] evtimer An event timer |
||||
*/ |
||||
void evtimer_print(const evtimer_t *evtimer); |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif /* EVTIMER_H */ |
||||
/** @} */ |
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (C) 2016-17 Kaspar Schleiser <kaspar@schleiser.de> |
||||
* 2017 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. |
||||
*/ |
||||
|
||||
/**
|
||||
* @addtogroup sys_evtimer |
||||
* @{ |
||||
* |
||||
* @file |
||||
* @brief IPC-based evtimer definitions |
||||
* |
||||
* @author Kaspar Schleiser <kaspar@schleiser.de> |
||||
* @author Martine Lenders <m.lenders@fu-berlin.de> |
||||
*/ |
||||
#ifndef EVTIMER_MSG_H |
||||
#define EVTIMER_MSG_H |
||||
|
||||
#include "msg.h" |
||||
#include "evtimer.h" |
||||
|
||||
#ifdef __cplusplus |
||||
extern "C" { |
||||
#endif |
||||
|
||||
/**
|
||||
* @brief IPC-message event timer |
||||
* @extends evtimer_t |
||||
*/ |
||||
typedef evtimer_t evtimer_msg_t; |
||||
|
||||
/**
|
||||
* @brief IPC-message event |
||||
* @extends evtimer_event_t |
||||
*/ |
||||
typedef struct { |
||||
evtimer_event_t event; /**< base class */ |
||||
msg_t msg; /**< the IPC message to generate on event */ |
||||
} evtimer_msg_event_t; |
||||
|
||||
/**
|
||||
* @brief Adds event to an event timer that handles events via IPC |
||||
* |
||||
* @param[in] evtimer An event timer |
||||
* @param[in] event An event |
||||
* @param[in] target_pid The PID of the thread that should receive the IPC |
||||
* message |
||||
*/ |
||||
static inline void evtimer_add_msg(evtimer_msg_t *evtimer, |
||||
evtimer_msg_event_t *event, |
||||
kernel_pid_t target_pid) |
||||
{ |
||||
/* use sender_pid field to get target_pid into callback function */ |
||||
event->msg.sender_pid = target_pid; |
||||
evtimer_add(evtimer, &event->event); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Event handler for IPC messages |
||||
* |
||||
* @param[in] event The event to handle |
||||
*/ |
||||
static inline void _evtimer_msg_handler(evtimer_event_t *event) |
||||
{ |
||||
evtimer_msg_event_t *mevent = (evtimer_msg_event_t *)event; |
||||
msg_send_int(&mevent->msg, mevent->msg.sender_pid); |
||||
} |
||||
|
||||
/**
|
||||
* @brief Initializes event timer to handle events via IPC |
||||
* |
||||
* @param[in] evtimer An event timer |
||||
*/ |
||||
static inline void evtimer_init_msg(evtimer_t *evtimer) |
||||
{ |
||||
evtimer_init(evtimer, _evtimer_msg_handler); |
||||
} |
||||
|
||||
#ifdef __cplusplus |
||||
} |
||||
#endif |
||||
|
||||
#endif /* EVTIMER_MSG_H */ |
||||
/** @} */ |
@ -0,0 +1,13 @@
|
||||
APPLICATION = evtimer_msg
|
||||
include ../Makefile.tests_common |
||||
|
||||
BOARD_INSUFFICIENT_MEMORY := nucleo32-f031 nucleo32-f042
|
||||
|
||||
USEMODULE += evtimer
|
||||
|
||||
include $(RIOTBASE)/Makefile.include |
||||
|
||||
test: |
||||
# `testrunner` calls `make term` recursively, results in duplicated `TERMFLAGS`.
|
||||
# So clears `TERMFLAGS` before run.
|
||||
TERMFLAGS= tests/01-run.py
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 evtimer_msg test application |
||||
* |
||||
* @author Martine Lenders <m.lenders@fu-berlin.de> |
||||
* |
||||
* @} |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
|
||||
#include "evtimer_msg.h" |
||||
#include "thread.h" |
||||
#include "msg.h" |
||||
#include "xtimer.h" |
||||
|
||||
static char worker_stack[THREAD_STACKSIZE_MAIN]; |
||||
static evtimer_t evtimer; |
||||
static evtimer_msg_event_t events[] = { |
||||
{ .event = { .offset = 1000 }, .msg = { .content = { .ptr = "supposed to be 1000" } } }, |
||||
{ .event = { .offset = 1500 }, .msg = { .content = { .ptr = "supposed to be 1500" } } }, |
||||
{ .event = { .offset = 659 }, .msg = { .content = { .ptr = "supposed to be 659" } } }, |
||||
{ .event = { .offset = 3954 }, .msg = { .content = { .ptr = "supposed to be 3954" } } }, |
||||
}; |
||||
|
||||
#define NEVENTS ((unsigned)(sizeof(events) / sizeof(evtimer_msg_event_t))) |
||||
|
||||
/* This thread will print the drift to stdout once per second */ |
||||
void *worker_thread(void *arg) |
||||
{ |
||||
int count = 0; |
||||
(void) arg; |
||||
|
||||
while (1) { |
||||
char *ctx; |
||||
msg_t m; |
||||
uint32_t now; |
||||
|
||||
msg_receive(&m); |
||||
now = xtimer_now_usec() / US_PER_MS; |
||||
ctx = m.content.ptr; |
||||
printf("At %6" PRIu32 " ms received msg %i: \"%s\"\n", now, count++, ctx); |
||||
} |
||||
} |
||||
|
||||
int main(void) |
||||
{ |
||||
uint32_t now = xtimer_now_usec() / US_PER_MS; |
||||
|
||||
evtimer_init_msg(&evtimer); |
||||
|
||||
/* create worker thread */ |
||||
kernel_pid_t pid = thread_create(worker_stack, sizeof(worker_stack), |
||||
THREAD_PRIORITY_MAIN - 1, |
||||
THREAD_CREATE_STACKTEST, |
||||
worker_thread, NULL, "worker"); |
||||
printf("Testing generic evtimer (start time = %" PRIu32 " ms)\n", now); |
||||
for (unsigned i = 0; i < NEVENTS; i++) { |
||||
evtimer_add_msg(&evtimer, &events[i], pid); |
||||
} |
||||
printf("Are the reception times of all %u msgs close to the supposed values?\n", |
||||
NEVENTS); |
||||
puts("If yes, the tests were successful"); |
||||
} |
@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# Copyright (C) 2016 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. |
||||
|
||||
from __future__ import print_function |
||||
import os |
||||
import sys |
||||
import time |
||||
|
||||
sys.path.append(os.path.join(os.environ['RIOTBASE'], 'dist/tools/testrunner')) |
||||
import testrunner |
||||
|
||||
ACCEPTED_ERROR = 20 |
||||
|
||||
def testfunc(child): |
||||
child.expect(r"Testing generic evtimer \(start time = (\d+) ms\)") |
||||
timer_offset = int(child.match.group(1)) |
||||
child.expect(r"Are the reception times of all (\d+) msgs close to the supposed values?") |
||||
numof = int(child.match.group(1)) |
||||
|
||||
for i in range(numof): |
||||
child.expect(r'At \s*(\d+) ms received msg %i: "supposed to be (\d+)"' % i) |
||||
stop = int(time.time() * 1000) |
||||
# check if output is correct |
||||
exp = int(child.match.group(2)) + timer_offset |
||||
assert(int(child.match.group(1)) in range(exp - ACCEPTED_ERROR, exp + ACCEPTED_ERROR + 1)) |
||||
print(".", end="", flush=True) |
||||
print("") |
||||
print("All tests successful") |
||||
|
||||
if __name__ == "__main__": |
||||
sys.exit(testrunner.run(testfunc, echo=False)) |
@ -0,0 +1,13 @@
|
||||
APPLICATION = evtimer_msg
|
||||
include ../Makefile.tests_common |
||||
|
||||
BOARD_INSUFFICIENT_MEMORY := nucleo32-f031 nucleo32-f042
|
||||
|
||||
USEMODULE += evtimer
|
||||
|
||||
include $(RIOTBASE)/Makefile.include |
||||
|
||||
test: |
||||
# `testrunner` calls `make term` recursively, results in duplicated `TERMFLAGS`.
|
||||
# So clears `TERMFLAGS` before run.
|
||||
TERMFLAGS= tests/01-run.py
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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 evtimer_msg test application |
||||
* |
||||
* @author Martine Lenders <m.lenders@fu-berlin.de> |
||||
* |
||||
* @} |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
|
||||
#include "evtimer_msg.h" |
||||
#include "thread.h" |
||||
#include "msg.h" |
||||
|
||||
#define WORKER_MSG_QUEUE_SIZE (8) |
||||
|
||||
msg_t worker_msg_queue[WORKER_MSG_QUEUE_SIZE]; |
||||
static char worker_stack[THREAD_STACKSIZE_MAIN]; |
||||
static evtimer_t evtimer; |
||||
static evtimer_msg_event_t events[] = { |
||||
{ .event = { .offset = 0 }, .msg = { .content = { .ptr = "1" } } }, |
||||
{ .event = { .offset = 0 }, .msg = { .content = { .ptr = "2" } } }, |
||||
{ .event = { .offset = 0 }, .msg = { .content = { .ptr = "3" } } }, |
||||
{ .event = { .offset = 0 }, .msg = { .content = { .ptr = "4" } } }, |
||||
{ .event = { .offset = 0 }, .msg = { .content = { .ptr = "5" } } }, |
||||
{ .event = { .offset = 0 }, .msg = { .content = { .ptr = "6" } } }, |
||||
{ .event = { .offset = 0 }, .msg = { .content = { .ptr = "7" } } }, |
||||
{ .event = { .offset = 0 }, .msg = { .content = { .ptr = "8" } } }, |
||||
}; |
||||
|
||||
#define NEVENTS (sizeof(events) / sizeof(evtimer_msg_event_t)) |
||||
|
||||
/* This thread will print the drift to stdout once per second */ |
||||
void *worker_thread(void *arg) |
||||
{ |
||||
(void) arg; |
||||
|
||||
msg_init_queue(worker_msg_queue, WORKER_MSG_QUEUE_SIZE); |
||||
while (1) { |
||||
char *ctx; |
||||
msg_t m; |
||||
|
||||
msg_receive(&m); |
||||
ctx = m.content.ptr; |
||||
printf("received msg \"%s\"\n", ctx); |
||||
} |
||||
} |
||||
|
||||
int main(void) |
||||
{ |
||||
evtimer_init_msg(&evtimer); |
||||
|
||||
/* create worker thread */ |
||||
kernel_pid_t pid = thread_create(worker_stack, sizeof(worker_stack), |
||||
THREAD_PRIORITY_MAIN - 1, |
||||
THREAD_CREATE_STACKTEST, |
||||
worker_thread, NULL, "worker"); |
||||
while (1) { |
||||
for (unsigned i = 0; i < NEVENTS; i++) { |
||||
evtimer_add_msg(&evtimer, &events[i], pid); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3 |
||||
|
||||
# Copyright (C) 2016 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. |
||||
|
||||
from __future__ import print_function |
||||
import os |
||||
import sys |
||||
import time |
||||
|
||||
sys.path.append(os.path.join(os.environ['RIOTBASE'], 'dist/tools/testrunner')) |
||||
import testrunner |
||||
|
||||
how_many = 100 |
||||
|
||||
def testfunc(child): |
||||
for i in range(how_many): |
||||
for j in range(8): |
||||
child.expect(r'received msg "%i"' % (j + 1)) |
||||
print(".", end="", flush=True) |
||||
print("") |
||||
print("Stopped after %i iterations, but should run forever." % how_many) |
||||
print("=> All tests successful") |
||||
|
||||
if __name__ == "__main__": |
||||
sys.exit(testrunner.run(testfunc, echo=False)) |
Loading…
Reference in new issue