Exercises related to Testing
Write a test program for unique_ptr
Exercice 1
Based on the instructions given in the testing
codelab, add
the following test function to the “test-ptr” test: test that the ownership is
passed correctly between two instances of `std::unique_ptr
Implement the test and verify that it is successful.
Solution
/**
* Test that multiple instances of unique pointers correctly manage the
* ownership of the the object
*/
void test_unique_ptr() {
std::unique_ptr<Test> unique_ptr1(nullptr);
// Sanity-check value of counter
TEST_ASSERT_EQUAL(0, Test::_instanceCount);
// Create and destroy shared pointer in given scope
{
std::unique_ptr<Test> unique_ptr2(new Test);
TEST_ASSERT_EQUAL(1, Test::_instanceCount);
// move ownership of unique_ptr2 to unique_ptr1
// unique_ptr1 = unique_ptr2; is not allowed
// must use the std::move() semantics
unique_ptr1 = std::move(unique_ptr2);
// still one instance only
TEST_ASSERT_EQUAL(1, Test::_instanceCount);
TEST_ASSERT_EQUAL(Test::kMagicNumber, unique_ptr1->_value);
// unique_ptr2 does not own ptr any more
TEST_ASSERT_EQUAL(nullptr, unique_ptr2.get());
}
// unique_ptr1 still owns an instance
TEST_ASSERT_EQUAL(1, Test::_instanceCount);
// release unique_ptr1
unique_ptr1 = nullptr;
// unique_ptr instance has been released
TEST_ASSERT_EQUAL(0, Test::_instanceCount);
}
Write a test program for raw pointers
Exercice 2
Based on the instructions given in the testing codelab, add the following test function to the “test-ptr” test: test that when creating a raw pointer and deallocating it correctly, the destructor is called as expected.
Implement the test and verify that it is successful.
Solution
/**
* Test that when delete is not called with a raw pointer then a memory leak occurs
*/
void test_raw_pointers() {
Test* raw_ptr1(nullptr);
// Sanity-check value of counter
TEST_ASSERT_EQUAL(0, Test::_instanceCount);
// allocate the raw pointer in a given scope
{
raw_ptr1 = new Test;
TEST_ASSERT_EQUAL(1, Test::_instanceCount);
Test* raw_ptr2 = raw_ptr1;
// still one instance only
TEST_ASSERT_EQUAL(1, Test::_instanceCount);
TEST_ASSERT_EQUAL(Test::kMagicNumber, raw_ptr1->_value);
TEST_ASSERT(raw_ptr1 == raw_ptr2);
}
// no delete called yet
TEST_ASSERT_EQUAL(1, Test::_instanceCount);
// call delete
delete raw_ptr1;
raw_ptr1 = nullptr;
// delete was called -> the object must be destroyed
TEST_ASSERT_EQUAL(0, Test::_instanceCount);
}
Write a test program for the Mbed OS Queue
component
In the Multi-tasking using Mbed OS codelab, a number of mechanisms for programming multi-tasking applications have been reviewed. Among those, the Mbed OS queue mechanism was presented with a number of examples.
Exercice 3
Many different test cases can be written for verifying the correctness of the class implementation:
- The function for getting an element from the queue must return
false
if one tries to get an element from an empty queue with a timeout value of0
. This test is implemented in theget_empty_no_timeout()
function. - The function for inserting an element to the queue must succeed when the queue
is not full. It must return false if one tries to insert an element to a full
queue with a given timeout value. In addition, the time spent in the
try_put_for()
method must correspond to the timeout value. This test is implemented in theput_full_timeout()
function. - Testing two scenarios where two separate threads attempt to get and put
elements from a queue with correct behavior: elements cannot be inserted in
the queue when the queue is full and elements cannot be obtained from the
queue when the queue is empty. These two tests are implemented in the
test_get_empty_waitforever()
andtest_put_full_waitforever
functions.
The tests described above must be integrated in a test program under “TESTS/simple-test/basic-queue-test”.
Solution
// Copyright 2022 Haute école d'ingénierie et d'architecture de Fribourg
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/****************************************************************************
* @file main.cpp
* @author Serge Ayer <serge.ayer@hefr.ch>
*
* @brief Simple example of test program for the Queue class
*
* @date 2022-09-01
* @version 0.1.0
***************************************************************************/
#include <chrono>
#include <cstdint>
#include <ratio>
#include "greentea-client/test_env.h"
#include "mbed.h"
#include "unity/unity.h"
#include "utest/utest.h"
using namespace utest::v1;
// constants used in test functions
static constexpr std::chrono::milliseconds TEST_TIMEOUT = 50ms;
static constexpr std::chrono::milliseconds DELTA = 1ms;
// test handler functions
/** Test get empty without timeout
*
* Given an empty queue with one slot for uint32_t data.
* When a thread tries to get one messages without timeout
* then the try_get() method returns false.
*/
static void get_empty_no_timeout() {
// create a simple queue
Queue<uint32_t, 1> q;
// try to get an element from the empty queue
uint32_t* v = nullptr;
bool res = q.try_get(&v);
// should not succeed
TEST_ASSERT_FALSE(res);
}
/** Test put full timeout
*
* Given a queue with two slots for uint32_t data.
* When a thread tries to insert two messages with @ TEST_TIMEOUT timeout,
* then the first try_put_for() operation succeeds and
* the second try_put_for() operation fails with the expected timeout.
*/
static void put_full_timeout() {
// create a queue with 2 elements
Queue<uint32_t, 2> q;
// insert one element
uint32_t localMsg = 0;
bool res = q.try_put_for(TEST_TIMEOUT, &localMsg);
// should succeed
TEST_ASSERT_TRUE(res);
// insert a second element
res = q.try_put_for(TEST_TIMEOUT, &localMsg);
// should succeed
TEST_ASSERT_TRUE(res);
// try to get an element with a given timeout
Timer timer;
timer.start();
res = q.try_put_for(TEST_TIMEOUT, &localMsg);
// should fail
TEST_ASSERT_FALSE(res);
// elapsed time should match the timeout value
TEST_ASSERT_INT_WITHIN(std::chrono::microseconds(DELTA).count(),
std::chrono::microseconds(TEST_TIMEOUT).count(),
timer.elapsed_time().count());
}
/** Test put full with forever timeout.
*
* Given the main thread and a consumer thread- Given a queue with one slot for uint32_t
* data. When the main thread puts a message to the queue and tries to put second one with
* @a Kernel::wait_for_u32_forever timeout Then thread waits for a slot to become empty in
* the queue When the consumer thread takes one message out of the queue Then the main
* thread successfully inserts message into the queue.
*/
// global variable used for checking messages between consumer and producer
uint32_t msg = 0;
// function used by a separate thread for putting one message to a queue
void thread_put_uint_msg(Queue<uint32_t, 1>* q) {
// wait before producing
ThisThread::sleep_for(TEST_TIMEOUT);
// try to consume one element with infinite timeout
msg = 2;
bool res = q->try_put_for(Kernel::wait_for_u32_forever, &msg);
// should succeed (since one element was consumed)
TEST_ASSERT_TRUE(res);
}
void test_get_empty_waitforever() {
// create a queue with one element
Queue<uint32_t, 1> q;
// The consumer thread will run as the main thread
// launch a separate thread as a producer of data (with limited stack size)
static constexpr uint32_t THREAD_STACK_SIZE = 512;
Thread t(osPriorityNormal, THREAD_STACK_SIZE);
t.start(callback(thread_put_uint_msg, &q));
// try to consume one element
uint32_t* value = nullptr;
Timer timer;
timer.start();
bool res = q.try_get_for(Kernel::wait_for_u32_forever, &value);
// should succeed since one element was produced
TEST_ASSERT_TRUE(res);
// the consumed value should match the produced one
TEST_ASSERT_EQUAL(&msg, value);
// elapsed time should match the timeout value
TEST_ASSERT_INT_WITHIN(std::chrono::microseconds(DELTA).count(),
std::chrono::microseconds(TEST_TIMEOUT).count(),
timer.elapsed_time().count());
// wait for the producer thread to exit
t.join();
}
// function used by a separate thread for getting one message from a queue
void thread_get_uint_msg(Queue<uint32_t, 1>* q) {
// wait before consuming
ThisThread::sleep_for(TEST_TIMEOUT);
// try to consume one element with infinite timeout
uint32_t* value = nullptr;
bool res = q->try_get_for(Kernel::wait_for_u32_forever, &value);
// should succeed (since one element was produced)
TEST_ASSERT_TRUE(res);
// the value consumed should match the value produced
TEST_ASSERT_EQUAL(&msg, value);
}
void test_put_full_waitforever() {
// create a queue with one element
Queue<uint32_t, 1> q;
// The producer thread will run as the main thread
// launch a separate thread as a consumer of data (with limited stack size)
static constexpr uint32_t THREAD_STACK_SIZE = 512;
Thread t(osPriorityNormal, THREAD_STACK_SIZE);
t.start(callback(thread_get_uint_msg, &q));
// producing one element should succeed
msg = 1;
bool res = q.try_put(&msg);
TEST_ASSERT_TRUE(res);
// try to produce a second element
Timer timer;
timer.start();
res = q.try_put_for(Kernel::wait_for_u32_forever, &msg);
// should succeed since the element produced above was consumed
TEST_ASSERT_TRUE(res);
// elapsed time should match the timeout value
TEST_ASSERT_INT_WITHIN(std::chrono::microseconds(DELTA).count(),
std::chrono::microseconds(TEST_TIMEOUT).count(),
timer.elapsed_time().count());
// TEST_ASSERT_DURATION_WITHIN(TEST_TIMEOUT / 10, TEST_TIMEOUT, timer.elapsed_time());
//
// wait for the consumer thread to exit
t.join();
}
static utest::v1::status_t greentea_setup(const size_t number_of_cases) {
// Here, we specify the timeout (60s) and the host test (a built-in host test or the
// name of our Python file)
GREENTEA_SETUP(60, "default_auto");
return greentea_test_setup_handler(number_of_cases);
}
// List of test cases in this file
static Case cases[] = {
Case("try to get an element from empty queue without timeout", get_empty_no_timeout),
Case("try to put an element to full queue with timeout", put_full_timeout),
Case("try to get elements from empty queue forever", test_get_empty_waitforever),
Case("try to put elements to full queue forever", test_put_full_waitforever)};
static Specification specification(greentea_setup, cases);
int main() { return !Harness::run(specification); }