diff --git a/TESTS/mbed_drivers/mem_trace/main.cpp b/TESTS/mbed_drivers/mem_trace/main.cpp index 5b10b45a62..286be0377b 100644 --- a/TESTS/mbed_drivers/mem_trace/main.cpp +++ b/TESTS/mbed_drivers/mem_trace/main.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013-2016, ARM Limited, All Rights Reserved + * Copyright (c) 2013-2017, ARM Limited, All Rights Reserved * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -28,15 +28,27 @@ #error [NOT_SUPPORTED] test not supported #endif -using namespace utest::v1; +using utest::v1::Case; + /******************************************************************************/ /* Helper functions and data structures */ /******************************************************************************/ +#define THREAD_STACK_SIZE 384 +#define NUM_TEST_THREADS 3 + +template +class TestThread : public Thread { + uint8_t stack[STACK_SIZE]; +public: + TestThread() : Thread(PRIORITY, STACK_SIZE, stack) { } +}; + // This structure keeps data about the various memory allocation operations, // as traced by 'test_trace_cb' below. #define TEST_MAX_MEMORY_OPS 10 + // Trace results for all possible operations typedef struct { uint8_t op; @@ -58,24 +70,28 @@ typedef struct { } free_info; }; } mem_trace_data_t; + // Memory operation statistics typedef struct { mem_trace_data_t op_data[TEST_MAX_MEMORY_OPS]; uint32_t total_ops; bool invalid_op, overflow; } stats_t; + static stats_t stats; + // Clear all the memory statistics -static void test_clear_stats() { +static void test_clear_stats() +{ memset(&stats, 0, sizeof(stats)); } // Memory tracer callback that records each operation in "stats" (above) -extern "C" void test_trace_cb(uint8_t op, void *res, void *caller, ...) { +extern "C" void test_trace_cb(uint8_t op, void *res, void *caller, ...) +{ va_list va; mem_trace_data_t *pmem = stats.op_data + stats.total_ops; - if (stats.total_ops >= TEST_MAX_MEMORY_OPS) { stats.overflow = true; return; @@ -110,180 +126,305 @@ extern "C" void test_trace_cb(uint8_t op, void *res, void *caller, ...) { } // Generic sanity checks for the tracer -static void check_sanity(uint32_t expected_ops) { +static void check_sanity(uint32_t expected_ops) +{ TEST_ASSERT_FALSE(stats.overflow); TEST_ASSERT_FALSE(stats.invalid_op); - TEST_ASSERT_EQUAL_UINT32(stats.total_ops, expected_ops); + TEST_ASSERT_EQUAL_UINT32(expected_ops, stats.total_ops); } // Check a "malloc" operation -static void check_malloc_op(const mem_trace_data_t *p, void *expected_res, size_t expected_arg_size) { - TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_MALLOC); - TEST_ASSERT_EQUAL_PTR(p->res, expected_res); - TEST_ASSERT_EQUAL_UINT32(p->malloc_info.arg_size, expected_arg_size); +static void check_malloc_op(const mem_trace_data_t *p, void *expected_res, size_t expected_arg_size) +{ + TEST_ASSERT_EQUAL_UINT8(MBED_MEM_TRACE_MALLOC, p->op); + TEST_ASSERT_EQUAL_PTR(expected_res, p->res); + TEST_ASSERT_EQUAL_UINT32(expected_arg_size, p->malloc_info.arg_size); } // Check a "free" operation -static void check_free_op(const mem_trace_data_t *p, void *expected_arg_ptr) { - TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_FREE); - TEST_ASSERT_EQUAL_PTR(p->free_info.arg_ptr, expected_arg_ptr); +static void check_free_op(const mem_trace_data_t *p, void *expected_arg_ptr) +{ + TEST_ASSERT_EQUAL_UINT8(MBED_MEM_TRACE_FREE, p->op); + TEST_ASSERT_EQUAL_PTR(expected_arg_ptr, p->free_info.arg_ptr); } // Check a "realloc" operation -static void check_realloc_op(const mem_trace_data_t *p, void *expected_res, void *expected_arg_ptr, size_t expected_arg_size) { - TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_REALLOC); - TEST_ASSERT_EQUAL_PTR(p->res, expected_res); - TEST_ASSERT_EQUAL_UINT32(p->realloc_info.arg_ptr, expected_arg_ptr); - TEST_ASSERT_EQUAL_UINT32(p->realloc_info.arg_size, expected_arg_size); +static void check_realloc_op(const mem_trace_data_t *p, void *expected_res, void *expected_arg_ptr, size_t expected_arg_size) +{ + TEST_ASSERT_EQUAL_UINT8(MBED_MEM_TRACE_REALLOC, p->op); + TEST_ASSERT_EQUAL_PTR(expected_res, p->res); + TEST_ASSERT_EQUAL_UINT32(expected_arg_ptr, p->realloc_info.arg_ptr); + TEST_ASSERT_EQUAL_UINT32(expected_arg_size, p->realloc_info.arg_size); } // Check a "calloc" operation -static void check_calloc_op(const mem_trace_data_t *p, void *expected_res, size_t expected_arg_nmemb, size_t expected_arg_size) { - TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_CALLOC); - TEST_ASSERT_EQUAL_PTR(p->res, expected_res); - TEST_ASSERT_EQUAL_UINT32(p->calloc_info.arg_nmemb, expected_arg_nmemb); - TEST_ASSERT_EQUAL_UINT32(p->calloc_info.arg_size, expected_arg_size); +static void check_calloc_op(const mem_trace_data_t *p, void *expected_res, size_t expected_arg_nmemb, size_t expected_arg_size) +{ + TEST_ASSERT_EQUAL_UINT8(MBED_MEM_TRACE_CALLOC, p->op); + TEST_ASSERT_EQUAL_PTR(expected_res, p->res); + TEST_ASSERT_EQUAL_UINT32(expected_arg_nmemb, p->calloc_info.arg_nmemb); + TEST_ASSERT_EQUAL_UINT32(expected_arg_size, p->calloc_info.arg_size); } -/******************************************************************************/ -/* Tests */ -/******************************************************************************/ +// Memory tracer callback to test thread safety +extern "C" void test_trace_cb_multithread(uint8_t op, void *res, void *caller, ...) +{ + volatile static int trace_guard = 0; + trace_guard++; + TEST_ASSERT_TRUE_MESSAGE(trace_guard == 1, "Race condition occurred !!!!"); + trace_guard--; +} -// Allocate a single buffer, then free it. Check that tracing matches the operations. -static void test_case_single_malloc_free() { +// Thread function +void malloc_free(volatile bool *thread_continue) +{ + const size_t block_size = 126; + + while(*thread_continue) { + void *p = malloc(block_size); + TEST_ASSERT_NOT_EQUAL(p, NULL); + free(p); + } +} + + +/** Test single malloc/free tracing + * + * Given a memory trace mechanism + * When perform single memory allocation/deallocation using malloc/free + * Then tracing matches the operations + * + */ +static void test_case_single_malloc_free() +{ + const uint32_t num_op = 2; const size_t block_size = 126; const mem_trace_data_t *pmem = stats.op_data; test_clear_stats(); mbed_mem_trace_set_callback(test_trace_cb); + // Allocate a single memory block void *p = malloc(block_size); TEST_ASSERT_NOT_EQUAL(p, NULL); + // Free the memory block free(p); + // Stop tracing mbed_mem_trace_set_callback(NULL); + // Check tracer result - check_sanity(2); - check_malloc_op(pmem ++, p, block_size); + check_sanity(num_op); + check_malloc_op(pmem++, p, block_size); check_free_op(pmem, p); } -// Test all memory operations (malloc, realloc, free, calloc) -static void test_case_all_memory_ops() { + +/** Test all memory operations (malloc, realloc, free, calloc) tracing + * + * Given a memory trace mechanism + * When perform all memory operations + * Then tracing matches the operations + * + */ +static void test_case_all_memory_ops() +{ + const uint32_t num_op = 5; const size_t malloc_size = 40, realloc_size = 80, nmemb = 25, size = 10; const mem_trace_data_t *pmem = stats.op_data; test_clear_stats(); mbed_mem_trace_set_callback(test_trace_cb); + // Allocate a single memory block, the realloc it void *p_malloc = malloc(malloc_size); TEST_ASSERT_NOT_EQUAL(p_malloc, NULL); void *p_realloc = realloc(p_malloc, realloc_size); TEST_ASSERT_NOT_EQUAL(p_realloc, NULL); + // Use calloc() now void *p_calloc = calloc(nmemb, size); - //TEST_ASSERT_NOT_EQUAL(p_calloc, NULL); + TEST_ASSERT_NOT_EQUAL(p_calloc, NULL); + // Free the realloc() pointer first, then the calloc() one free(p_realloc); free(p_calloc); + // Stop tracing mbed_mem_trace_set_callback(NULL); + // Check tracer result - check_sanity(6); - check_malloc_op(pmem ++, p_malloc, malloc_size); - check_realloc_op(pmem ++, p_realloc, p_malloc, realloc_size); - // calloc() calls malloc() internally - check_malloc_op(pmem ++, p_calloc, nmemb * size); - check_calloc_op(pmem ++, p_calloc, nmemb, size); - check_free_op(pmem ++, p_realloc); + check_sanity(num_op); + check_malloc_op(pmem++, p_malloc, malloc_size); + check_realloc_op(pmem++, p_realloc, p_malloc, realloc_size); + check_calloc_op(pmem++, p_calloc, nmemb, size); + check_free_op(pmem++, p_realloc); check_free_op(pmem, p_calloc); } -// Test that tracing is off when using a NULL callback -static void test_case_trace_off() { + +/** Test that tracing is off when using a NULL callback + * + * Given a memory trace mechanism + * When tracing is turned off + * Then performed memory operations doesn't report any tracing + * + */ +static void test_case_trace_off() +{ + const uint32_t num_op = 0; const size_t malloc_size = 10; test_clear_stats(); // We don't want any tracing mbed_mem_trace_set_callback(NULL); + // Allocate a buffer and free it void *p_malloc = malloc(malloc_size); TEST_ASSERT_NOT_EQUAL(p_malloc, NULL); free(p_malloc); + // Check that we didn't trace anything - check_sanity(0); + check_sanity(num_op); } -// Test partial tracing (start tracing, stop tracing, restart later) -static void test_case_partial_trace() { + +/** Test partial tracing + * + * Given a memory trace mechanism + * When perform memory operations while tracing is on then off and on again + * Then tracing report only part of operations + * + */ +static void test_case_partial_trace() +{ + const uint32_t num_op = 2; const size_t malloc_size_1 = 20, malloc_size_2 = 30; const mem_trace_data_t *pmem = stats.op_data; test_clear_stats(); + // Start tracing mbed_mem_trace_set_callback(test_trace_cb); + // Allocate a buffer void *p_malloc_1 = malloc(malloc_size_1); TEST_ASSERT_NOT_EQUAL(p_malloc_1, NULL); + // Disable tracing before freeing the first buffer mbed_mem_trace_set_callback(NULL); free(p_malloc_1); + // Allocate another buffer (still not traced) void *p_malloc_2 = malloc(malloc_size_2); TEST_ASSERT_NOT_EQUAL(p_malloc_2, NULL); + // Re-enable tracing mbed_mem_trace_set_callback(test_trace_cb); + // And free the second buffer (this operation should be tracer) free(p_malloc_2); + // Stop tracing mbed_mem_trace_set_callback(NULL); + // Check tracer result - check_sanity(2); - check_malloc_op(pmem ++, p_malloc_1, malloc_size_1); + check_sanity(num_op); + check_malloc_op(pmem++, p_malloc_1, malloc_size_1); check_free_op(pmem, p_malloc_2); } -// Test new/delete tracing -static void test_case_new_delete() { + +/** Test new/delete tracing + * + * Given a memory trace mechanism + * When memory allocation/deallocation is performed using new/delete + * Then tracing matches the operations + * + */ +static void test_case_new_delete() +{ + const uint32_t num_op = 4; const mem_trace_data_t *pmem = stats.op_data; test_clear_stats(); + // Start tracing mbed_mem_trace_set_callback(test_trace_cb); + // Test new, new[], delete and delete[] int *p_int = new int; int *p_int_array = new int[10]; delete p_int; delete[] p_int_array; + // Stop tracing mbed_mem_trace_set_callback(NULL); + // Check tracer result - check_sanity(4); - check_malloc_op(pmem ++, p_int, sizeof(int)); - check_malloc_op(pmem ++, p_int_array, 10 * sizeof(int)); - check_free_op(pmem ++, p_int); - check_free_op(pmem ++, p_int_array); + check_sanity(num_op); + check_malloc_op(pmem++, p_int, sizeof(int)); + check_malloc_op(pmem++, p_int_array, 10 * sizeof(int)); + check_free_op(pmem++, p_int); + check_free_op(pmem, p_int_array); } -static Case cases[] = { - Case("single malloc/free", test_case_single_malloc_free), - Case("all memory operations", test_case_all_memory_ops), - Case("trace off", test_case_trace_off), - Case("partial trace", test_case_partial_trace), - Case("test new/delete", test_case_new_delete) + +/** Test tracing thread safety + * + * Given a memory trace mechanism and multiple threads are started in parallel + * When each of the threads perform memory allocation/deallocation (thus uses memory trace mechanisms) + * Then tracing is protected against simultaneous multithreaded access + * + */ +static void test_case_multithread_malloc_free() +{ + const uint32_t wait_time_us = 10000; + volatile bool threads_continue; + TestThread threads[NUM_TEST_THREADS]; + + mbed_mem_trace_set_callback(test_trace_cb_multithread); + + threads_continue = true; + for (int i = 0; i < NUM_TEST_THREADS; i++) { + threads[i].start(callback(malloc_free, &threads_continue)); + } + + Thread::wait(wait_time_us); + threads_continue = false; + + for (int i = 0; i < NUM_TEST_THREADS; i++) { + threads[i].join(); + } + + mbed_mem_trace_set_callback(NULL); +} + + + +static Case cases[] = +{ + Case("Test single malloc/free trace", test_case_single_malloc_free), + Case("Test all memory operations trace", test_case_all_memory_ops), + Case("Test trace off", test_case_trace_off), + Case("Test partial trace", test_case_partial_trace), + Case("Test new/delete trace", test_case_new_delete), + Case("Test multithreaded trace", test_case_multithread_malloc_free) }; -static status_t greentea_test_setup(const size_t number_of_cases) { - GREENTEA_SETUP(20, "default_auto"); - return greentea_test_setup_handler(number_of_cases); +static utest::v1::status_t greentea_test_setup(const size_t number_of_cases) +{ + GREENTEA_SETUP(15, "default_auto"); + return utest::v1::greentea_test_setup_handler(number_of_cases); } -static Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler); +static utest::v1::Specification specification(greentea_test_setup, cases, utest::v1::greentea_test_teardown_handler); -int main() { +int main() +{ // Disable stdout buffering to prevent any unwanted allocations setvbuf(stdout, NULL, _IONBF, 0); - Harness::run(specification); -} + return !utest::v1::Harness::run(specification); +}