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.
137 lines
4 KiB
C++
137 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
|