mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-01-18 12:52:44 +01:00
tests/thread_float: improve and add test script
- Perform the same computation over and over again. If the results differ, context switches have an impact on the calculation (e.g. when the FPU internally uses more bits than a float, but that bits are not saved / restored on context switch) - Give the three threads the names "t1", "t2", and "t3" and print them on console, instead of the process ID. This makes interpretation of the output easier, as the process IDs depend e.g. on whether a given platforms requires an idle thread or not. - Do not use the thread ID in the calculation, but the number at the end of the thread name. This will result in the number printed only depending on the precision of the (software) FPU and the printf() implementation, and not on which threads are created in which order (including the idle thread) - Add a script to support running `make test` Update tests/thread_float/tests/01-run.py Co-authored-by: Alexandre Abadie <alexandre.abadie@inria.fr>
This commit is contained in:
parent
9256970517
commit
f62b662b08
@ -3,6 +3,11 @@ include ../Makefile.tests_common
|
||||
USEMODULE += printf_float
|
||||
USEMODULE += xtimer
|
||||
|
||||
# native has known issues: the context switch via glibc's setcontext()
|
||||
# apparently doesn't properly save and restore the FPU state. This results in
|
||||
# occasionally wrong results (often nan) being printed for the same calculation
|
||||
TEST_ON_CI_BLACKLIST += native
|
||||
|
||||
#DISABLE_MODULE += cortexm_fpu
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
|
16
tests/thread_float/README.md
Normal file
16
tests/thread_float/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
Testing for Absence of Interactions between Floating Point Calculations and Context Switches
|
||||
============================================================================================
|
||||
|
||||
This tests launches three threads, t1, t2 and t3 that will perform a long and costly series of
|
||||
floating point calculations with different input data, while a software timer triggers context
|
||||
switches. The threads t1 and t3 will print the results. All threads will do this in an endless
|
||||
loop.
|
||||
|
||||
This allows for testing the following:
|
||||
|
||||
1. When using the pseudo module `printf_float`, floating point numbers can be correctly printed
|
||||
2. `THREAD_STACKSIZE_MAIN` is large enough to print floats without stack overflows.
|
||||
3. Context switches while the (soft) FPU is busy does not result in precision loss.
|
||||
- This could happen if the FPU state is not properly saved / restored on context switch. This
|
||||
could be needed if the FPU internally uses a higher resolution that `float` / `double`
|
||||
(e.g. the x86 FPU uses 80 bits internally, instead of 64 bits for double)
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (C) 2017 OTA keys S.A.
|
||||
* 2021 Otto-von-Guericke-Universität Magdeburg
|
||||
*
|
||||
* 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
|
||||
@ -14,11 +15,13 @@
|
||||
* @brief Thread test application
|
||||
*
|
||||
* @author Vincent Dupont <vincent@otakeys.com>
|
||||
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "thread.h"
|
||||
#include "msg.h"
|
||||
@ -44,46 +47,29 @@ static void timer_cb(void *arg)
|
||||
xtimer_set(&timer, OFFSET);
|
||||
}
|
||||
|
||||
static void *thread1(void *arg)
|
||||
static void *thread_1_2_3(void *_arg)
|
||||
{
|
||||
(void) arg;
|
||||
|
||||
const char *arg = _arg;
|
||||
float f, init;
|
||||
|
||||
printf("THREAD %" PRIkernel_pid " start\n", thread_getpid());
|
||||
mutex_lock(&lock);
|
||||
printf("THREAD %s start\n", arg);
|
||||
mutex_unlock(&lock);
|
||||
|
||||
init = 1.0 * thread_getpid();
|
||||
/* Use number at end of thread name, e.g. 3 for "t3", to seed the calculation */
|
||||
init = 1.0 * (arg[strlen(arg) - 1] - '0');
|
||||
f = init;
|
||||
|
||||
while (1) {
|
||||
for (unsigned long i = 0; i < 10000ul; i++) {
|
||||
f = f + 1.0 / f;
|
||||
}
|
||||
/* only t1 and t3 should print */
|
||||
if (strcmp("t2", arg) != 0) {
|
||||
mutex_lock(&lock);
|
||||
printf("T(%" PRIkernel_pid "): %f\n", thread_getpid(), (double)f);
|
||||
printf("%s: %f\n", arg, (double)f);
|
||||
mutex_unlock(&lock);
|
||||
init += 1.0;
|
||||
f = init;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *thread2(void *arg)
|
||||
{
|
||||
(void) arg;
|
||||
|
||||
float f, init;
|
||||
|
||||
printf("THREAD %" PRIkernel_pid " start\n", thread_getpid());
|
||||
|
||||
init = 1.0 * thread_getpid();
|
||||
f = init;
|
||||
|
||||
while (1) {
|
||||
for (unsigned long i = 0; i < 100000ul; i++) {
|
||||
f = f + 1.0 / f;
|
||||
}
|
||||
init += 1.0;
|
||||
f = init;
|
||||
}
|
||||
return NULL;
|
||||
@ -91,15 +77,18 @@ static void *thread2(void *arg)
|
||||
|
||||
int main(void)
|
||||
{
|
||||
const char *t1_name = "t1";
|
||||
const char *t2_name = "t2";
|
||||
const char *t3_name = "t3";
|
||||
p1 = thread_create(t1_stack, sizeof(t1_stack), THREAD_PRIORITY_MAIN + 1,
|
||||
THREAD_CREATE_WOUT_YIELD | THREAD_CREATE_STACKTEST,
|
||||
thread1, NULL, "nr1");
|
||||
thread_1_2_3, (void *)t1_name, t1_name);
|
||||
p2 = thread_create(t2_stack, sizeof(t2_stack), THREAD_PRIORITY_MAIN + 1,
|
||||
THREAD_CREATE_WOUT_YIELD | THREAD_CREATE_STACKTEST,
|
||||
thread2, NULL, "nr2");
|
||||
thread_1_2_3, (void *)t2_name, t2_name);
|
||||
p3 = thread_create(t3_stack, sizeof(t3_stack), THREAD_PRIORITY_MAIN + 1,
|
||||
THREAD_CREATE_WOUT_YIELD | THREAD_CREATE_STACKTEST,
|
||||
thread1, NULL, "nr3");
|
||||
thread_1_2_3, (void *)t3_name, t3_name);
|
||||
puts("THREADS CREATED\n");
|
||||
|
||||
timer.callback = timer_cb;
|
||||
|
81
tests/thread_float/tests/01-run.py
Executable file
81
tests/thread_float/tests/01-run.py
Executable file
@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2021 Otto-von-Guericke-Universität Magdeburg
|
||||
#
|
||||
# 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.
|
||||
|
||||
# @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
|
||||
|
||||
import sys
|
||||
from testrunner import run
|
||||
|
||||
MIN_PRINTS = 5
|
||||
|
||||
|
||||
def assertAlmostEqual(first, second, delta=0.05):
|
||||
assert first + delta > second
|
||||
assert first - delta < second
|
||||
|
||||
|
||||
def same_computation_as_in_c_prog(thread_num):
|
||||
f = 1.0 * thread_num
|
||||
for _ in range(10000):
|
||||
f = f + 1.0 / f
|
||||
return f
|
||||
|
||||
|
||||
def testfunc(child):
|
||||
child.expect_exact("THREADS CREATED")
|
||||
child.expect_exact("THREAD t1 start")
|
||||
child.expect_exact("THREAD t2 start")
|
||||
child.expect_exact("THREAD t3 start")
|
||||
|
||||
child.expect(r"t(\d): (\d{3}\.\d+)\r\n")
|
||||
first_thread = int(child.match.group(1))
|
||||
# Note: intentionally keeping the float output as string to also test that printf("%f", ...)
|
||||
# prints the exact same char sequence for the same float value each time
|
||||
first_result = child.match.group(2)
|
||||
|
||||
# wait for second thread to print, but wait at most 50 messages
|
||||
second_thread = None
|
||||
for _ in range(50):
|
||||
child.expect(r"t(\d): (\d{3}\.\d+)\r\n")
|
||||
if int(child.match.group(1)) != first_thread:
|
||||
second_thread = int(child.match.group(1))
|
||||
second_result = child.match.group(2)
|
||||
break
|
||||
|
||||
assert second_thread is not None, "both threads t1 and t3 should print"
|
||||
assert first_thread in [1, 3], "only thread t1 and t3 should print"
|
||||
assert second_thread in [1, 3], "only thread t1 and t3 should print"
|
||||
assertAlmostEqual(float(first_result), same_computation_as_in_c_prog(first_thread))
|
||||
assertAlmostEqual(float(second_result), same_computation_as_in_c_prog(second_thread))
|
||||
|
||||
count_first_thread = 0
|
||||
count_second_thread = 0
|
||||
|
||||
# wait for both threads to print at least MIN_PRINTS times, but wait at most 100 messages
|
||||
for _ in range(100):
|
||||
child.expect(r"t(\d): (\d{3}\.\d+)\r\n")
|
||||
thread = int(child.match.group(1))
|
||||
assert thread in [1, 3], "only thread t1 and t3 should print"
|
||||
result = child.match.group(2)
|
||||
|
||||
if thread == first_thread:
|
||||
assert result == first_result, "same calculation but different result"
|
||||
count_first_thread += 1
|
||||
else:
|
||||
assert result == second_result, "same calculation but different result"
|
||||
count_second_thread += 1
|
||||
|
||||
if (count_first_thread >= MIN_PRINTS) and (count_second_thread >= MIN_PRINTS):
|
||||
break
|
||||
|
||||
msg = f"Either t1 or t3 printed less than {MIN_PRINTS} times within 100 messages"
|
||||
assert (count_first_thread >= MIN_PRINTS) and (count_second_thread >= MIN_PRINTS), msg
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run(testfunc))
|
Loading…
Reference in New Issue
Block a user