OVMS3-idf/components/pthread/test/test_pthread_cxx.cpp
Angus Gratton 852462f4e2 test: Fix possible race in pthread C++ test
Also use TEST_ASSERT_EQUAL to get better debugging

Debugging intermittent UT failures on S2 release config

In the old version, the 300ms delay in between the two kinds of test
was supposed to keep the tasks in lockstep so it didn't matter that
global_sp was protected by two muxes.

However it seems like sometimes they could get out of sync -
I think because of a race in the sleep_until test. If the
second counter ticks over at that exact moment sleeping starts,
then the task doesn't sleep and will immediately keep running
 for the next iteration, possibly racing the other tasks.
2019-12-19 15:55:51 +11:00

138 lines
4 KiB
C++

#include <iostream>
#include <sstream>
#include <thread>
#include <mutex>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#if __GTHREADS && __GTHREADS_CXX0X
#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include "esp_log.h"
const static char *TAG = "pthread_test";
static std::mutex mtx;
static std::shared_ptr<int> global_sp_mtx; // protected by mux
static std::recursive_mutex recur_mtx;
static std::shared_ptr<int> global_sp_recur_mtx; // protected by recursive mux
static void thread_do_nothing() {}
static void thread_main()
{
std::cout << "thread_main CXX " << std::hex << std::this_thread::get_id() << std::endl;
std::chrono::milliseconds dur = std::chrono::milliseconds(10);
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
int old_val, new_val;
// mux test
mtx.lock();
old_val = *global_sp_mtx;
std::this_thread::yield();
(*global_sp_mtx)++;
std::this_thread::yield();
new_val = *global_sp_mtx;
mtx.unlock();
std::cout << "thread " << std::hex << std::this_thread::get_id() << ": nrec " << i << " val= " << *global_sp_mtx << std::endl;
TEST_ASSERT_EQUAL(old_val + 1, new_val);
// sleep_for test
std::this_thread::sleep_for(dur);
// recursive mux test
recur_mtx.lock();
recur_mtx.lock();
old_val = *global_sp_recur_mtx;
std::this_thread::yield();
(*global_sp_recur_mtx)++;
std::this_thread::yield();
new_val = *global_sp_recur_mtx;
recur_mtx.unlock();
recur_mtx.unlock();
std::cout << "thread " << std::hex << std::this_thread::get_id() << ": rec " << i << " val= " << *global_sp_recur_mtx << std::endl;
TEST_ASSERT_EQUAL(old_val + 1, new_val);
}
// sleep_until test
using std::chrono::system_clock;
std::time_t tt = system_clock::to_time_t(system_clock::now());
struct std::tm *ptm = std::localtime(&tt);
ptm->tm_sec++;
std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));
}
}
TEST_CASE("pthread C++", "[pthread]")
{
global_sp_mtx.reset(new int(1));
global_sp_recur_mtx.reset(new int(-1000));
std::thread t1(thread_do_nothing);
t1.join();
std::thread t2(thread_main);
std::cout << "Detach thread " << std::hex << t2.get_id() << std::endl;
t2.detach();
TEST_ASSERT_FALSE(t2.joinable());
std::thread t3(thread_main);
std::thread t4(thread_main);
if (t3.joinable()) {
std::cout << "Join thread " << std::hex << t3.get_id() << std::endl;
t3.join();
}
if (t4.joinable()) {
std::cout << "Join thread " << std::hex << t4.get_id() << std::endl;
t4.join();
}
global_sp_mtx.reset(); // avoid reported leak
global_sp_recur_mtx.reset();
}
static void task_test_sandbox()
{
std::stringstream ss;
ESP_LOGI(TAG, "About to create a string stream");
ESP_LOGI(TAG, "About to write to string stream");
ss << "Hello World!";
ESP_LOGI(TAG, "About to extract from stringstream");
ESP_LOGI(TAG, "Text: %s", ss.str().c_str());
}
static void task_test_sandbox_c(void *arg)
{
bool *running = (bool *)arg;
// wrap thread func to ensure that all C++ stack objects are cleaned up by their destructors
task_test_sandbox();
ESP_LOGI(TAG, "Task stk_wm = %d", uxTaskGetStackHighWaterMark(NULL));
if (running) {
*running = false;
vTaskDelete(NULL);
}
}
TEST_CASE("pthread mix C/C++", "[pthread]")
{
bool c_running = true;
std::thread t1(task_test_sandbox);
xTaskCreatePinnedToCore((TaskFunction_t)&task_test_sandbox_c, "task_test_sandbox", 3072, &c_running, 5, NULL, 0);
while (c_running) {
vTaskDelay(1);
}
if (t1.joinable()) {
std::cout << "Join thread " << std::hex << t1.get_id() << std::endl;
t1.join();
}
}
#endif