Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Fork of mbed-os by
features/frameworks/utest/source/utest_harness.cpp
- Committer:
- xuaner
- Date:
- 2017-07-20
- Revision:
- 1:3deb71413561
- Parent:
- 0:f269e3021894
File content as of revision 1:3deb71413561:
/****************************************************************************
* Copyright (c) 2015, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* 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.
****************************************************************************
*/
#include "utest/utest_harness.h"
#include "utest/utest_stack_trace.h"
#include "utest/utest_serial.h"
#include <stdlib.h>
using namespace utest::v1;
namespace
{
const Case *test_cases = NULL;
size_t test_length = 0;
size_t test_index_of_case = 0;
size_t test_passed = 0;
size_t test_failed = 0;
const Case *case_current = NULL;
size_t case_index = 0;
base_control_t case_control = { REPEAT_SETUP_TEARDOWN, TIMEOUT_UNDECLR };
size_t case_repeat_count = 1;
void *case_timeout_handle = NULL;
size_t case_validation_count = 0;
bool case_timeout_occurred = false;
size_t case_passed = 0;
size_t case_failed = 0;
size_t case_failed_before = 0;
struct DefaultHandlers : public handlers_t {
DefaultHandlers() : handlers_t(default_handlers) { }
DefaultHandlers(const handlers_t& other) : handlers_t(other) { }
};
SingletonPtr<DefaultHandlers> defaults;
SingletonPtr<DefaultHandlers> handlers;
location_t location = LOCATION_UNKNOWN;
utest_v1_scheduler_t scheduler = {NULL, NULL, NULL, NULL};
}
static void die() {
UTEST_LOG_FUNCTION();
while(1) ;
}
static bool is_scheduler_valid(const utest_v1_scheduler_t scheduler)
{
UTEST_LOG_FUNCTION();
return (scheduler.init && scheduler.post && scheduler.cancel && scheduler.run);
}
bool Harness::set_scheduler(const utest_v1_scheduler_t scheduler)
{
UTEST_LOG_FUNCTION();
if (is_scheduler_valid(scheduler)) {
::scheduler = scheduler;
return true;
}
return false;
}
void Harness::notify_testcases()
{
for(unsigned i = 0; i < test_length; i++) {
utest::v1::greentea_testcase_notification_handler(test_cases[i].get_description());
}
}
bool Harness::run(const Specification& specification, size_t)
{
UTEST_LOG_FUNCTION();
return run(specification);
}
bool Harness::run(const Specification& specification)
{
UTEST_LOG_FUNCTION();
// check if a specification is currently running
if (is_busy())
return false;
// if the scheduler is invalid, this is the first time we are calling
if (!is_scheduler_valid(scheduler))
scheduler = utest_v1_get_scheduler();
// if the scheduler is still invalid, abort
if (!is_scheduler_valid(scheduler))
return false;
// if the scheduler failed to initialize, abort
if (scheduler.init() != 0)
return false;
test_cases = specification.cases;
test_length = specification.length;
*defaults.get() = specification.defaults;
handlers->test_setup = defaults->get_handler(specification.setup_handler);
handlers->test_teardown = defaults->get_handler(specification.teardown_handler);
handlers->test_failure = defaults->get_handler(specification.failure_handler);
test_index_of_case = 0;
test_passed = 0;
test_failed = 0;
case_passed = 0;
case_failed = 0;
case_failed_before = 0;
location = LOCATION_TEST_SETUP;
int setup_status = 0;
failure_t failure(REASON_NONE, location);
if (handlers->test_setup) {
setup_status = handlers->test_setup(test_length);
if (setup_status == STATUS_CONTINUE) setup_status = 0;
else if (setup_status < STATUS_CONTINUE) failure.reason = REASON_TEST_SETUP;
else if (setup_status > signed(test_length)) failure.reason = REASON_CASE_INDEX;
}
if (failure.reason != REASON_NONE) {
if (handlers->test_failure) handlers->test_failure(failure);
if (handlers->test_teardown) handlers->test_teardown(0, 0, failure);
test_cases = NULL;
exit(1);
}
notify_testcases();
case_index = setup_status;
case_current = &test_cases[case_index];
scheduler.post(run_next_case, 0);
if (scheduler.run() != 0) {
failure.reason = REASON_SCHEDULER;
if (handlers->test_failure) handlers->test_failure(failure);
if (handlers->test_teardown) handlers->test_teardown(0, 0, failure);
test_cases = NULL;
exit(1);
}
return true;
}
void Harness::raise_failure(const failure_reason_t reason)
{
UTEST_LOG_FUNCTION();
// ignore a failure, if the Harness has not been initialized.
// this allows using unity assertion macros without setting up utest.
if (test_cases == NULL) return;
utest::v1::status_t fail_status = STATUS_ABORT;
if (handlers->test_failure) handlers->test_failure(failure_t(reason, location));
if (handlers->case_failure) fail_status = handlers->case_failure(case_current, failure_t(reason, location));
{
UTEST_ENTER_CRITICAL_SECTION;
if (fail_status != STATUS_IGNORE) case_failed++;
if ((fail_status == STATUS_ABORT) && case_timeout_handle)
{
scheduler.cancel(case_timeout_handle);
case_timeout_handle = NULL;
}
UTEST_LEAVE_CRITICAL_SECTION;
}
if (fail_status == STATUS_ABORT || reason & REASON_CASE_SETUP) {
if (handlers->case_teardown && location != LOCATION_CASE_TEARDOWN) {
location_t fail_loc(location);
location = LOCATION_CASE_TEARDOWN;
utest::v1::status_t teardown_status = handlers->case_teardown(case_current, case_passed, case_failed, failure_t(reason, fail_loc));
if (teardown_status < STATUS_CONTINUE) raise_failure(REASON_CASE_TEARDOWN);
else if (teardown_status > signed(test_length)) raise_failure(REASON_CASE_INDEX);
else if (teardown_status >= 0) case_index = teardown_status - 1;
// Restore case failure location once we have dealt with case teardown
location = fail_loc;
handlers->case_teardown = NULL;
}
}
if (fail_status == STATUS_ABORT) {
test_failed++;
failure_t fail(reason, location);
location = LOCATION_TEST_TEARDOWN;
if (handlers->test_teardown) handlers->test_teardown(test_passed, test_failed, fail);
exit(test_failed);
die();
}
}
void Harness::schedule_next_case()
{
UTEST_LOG_FUNCTION();
if (!case_timeout_occurred && case_failed_before == case_failed) {
case_passed++;
}
if (case_control.repeat & REPEAT_SETUP_TEARDOWN || !(case_control.repeat & (REPEAT_ON_TIMEOUT | REPEAT_ON_VALIDATE))) {
location = LOCATION_CASE_TEARDOWN;
if (handlers->case_teardown) {
utest::v1::status_t status = handlers->case_teardown(case_current, case_passed, case_failed,
case_failed ? failure_t(REASON_CASES, LOCATION_UNKNOWN) : failure_t(REASON_NONE));
if (status < STATUS_CONTINUE) raise_failure(REASON_CASE_TEARDOWN);
else if (status > signed(test_length)) raise_failure(REASON_CASE_INDEX);
else if (status >= 0) case_index = status - 1;
}
}
if (!(case_control.repeat & (REPEAT_ON_TIMEOUT | REPEAT_ON_VALIDATE))) {
if (case_failed > 0) test_failed++;
else test_passed++;
case_control = control_t(REPEAT_SETUP_TEARDOWN);
case_index++;
case_current = &test_cases[case_index];
case_passed = 0;
case_failed = 0;
case_failed_before = 0;
case_repeat_count = 1;
test_index_of_case++;
}
scheduler.post(run_next_case, 0);
}
void Harness::handle_timeout()
{
UTEST_LOG_FUNCTION();
{
UTEST_ENTER_CRITICAL_SECTION;
if (case_timeout_handle != NULL) {
case_timeout_handle = NULL;
case_timeout_occurred = true;
}
UTEST_LEAVE_CRITICAL_SECTION;
}
if (case_timeout_occurred) {
raise_failure(failure_reason_t(REASON_TIMEOUT | ((case_control.repeat & REPEAT_ON_TIMEOUT) ? REASON_IGNORE : 0)));
scheduler.post(schedule_next_case, 0);
}
}
void Harness::validate_callback(const control_t control)
{
UTEST_LOG_FUNCTION();
UTEST_ENTER_CRITICAL_SECTION;
case_validation_count++;
if (case_timeout_handle != NULL || case_control.timeout == TIMEOUT_FOREVER)
{
scheduler.cancel(case_timeout_handle);
case_timeout_handle = NULL;
control_t merged_control = case_control + control;
case_control.repeat = repeat_t(merged_control.repeat & ~REPEAT_ON_TIMEOUT);
case_control.timeout = TIMEOUT_NONE;
scheduler.post(schedule_next_case, 0);
}
UTEST_LEAVE_CRITICAL_SECTION;
}
bool Harness::is_busy()
{
UTEST_LOG_FUNCTION();
UTEST_ENTER_CRITICAL_SECTION;
bool res = false;
if (test_cases && case_current) {
res = (case_current < (test_cases + test_length));
}
UTEST_LEAVE_CRITICAL_SECTION;
return res;
}
void Harness::run_next_case()
{
UTEST_LOG_FUNCTION();
if(case_current < (test_cases + test_length))
{
handlers->case_setup = defaults->get_handler(case_current->setup_handler);
handlers->case_teardown = defaults->get_handler(case_current->teardown_handler);
handlers->case_failure = defaults->get_handler(case_current->failure_handler);
if (case_current->is_empty()) {
location = LOCATION_UNKNOWN;
raise_failure(REASON_EMPTY_CASE);
schedule_next_case();
return;
}
repeat_t setup_repeat;
{
UTEST_ENTER_CRITICAL_SECTION;
case_validation_count = 0;
case_timeout_occurred = false;
setup_repeat = case_control.repeat;
case_control = control_t();
UTEST_LEAVE_CRITICAL_SECTION;
}
if (setup_repeat & REPEAT_SETUP_TEARDOWN) {
location = LOCATION_CASE_SETUP;
if (handlers->case_setup && (handlers->case_setup(case_current, test_index_of_case) != STATUS_CONTINUE)) {
raise_failure(REASON_CASE_SETUP);
schedule_next_case();
return;
}
}
case_failed_before = case_failed;
location = LOCATION_CASE_HANDLER;
if (case_current->handler) {
case_current->handler();
} else if (case_current->control_handler) {
case_control = case_control + case_current->control_handler();
} else if (case_current->repeat_count_handler) {
case_control = case_control + case_current->repeat_count_handler(case_repeat_count);
}
case_repeat_count++;
{
UTEST_ENTER_CRITICAL_SECTION;
if (case_validation_count) case_control.repeat = repeat_t(case_control.repeat & ~REPEAT_ON_TIMEOUT);
// if timeout valid
if (case_control.timeout < TIMEOUT_UNDECLR && case_validation_count == 0) {
// if await validation _with_ timeout
if (case_control.timeout < TIMEOUT_FOREVER) {
case_timeout_handle = scheduler.post(handle_timeout, case_control.timeout);
if (case_timeout_handle == NULL) {
raise_failure(REASON_SCHEDULER);
schedule_next_case();
}
}
}
else {
scheduler.post(schedule_next_case, 0);
}
UTEST_LEAVE_CRITICAL_SECTION;
}
}
else if (handlers->test_teardown) {
location = LOCATION_TEST_TEARDOWN;
handlers->test_teardown(test_passed, test_failed, test_failed ? failure_t(REASON_CASES, LOCATION_UNKNOWN) : failure_t(REASON_NONE));
test_cases = NULL;
exit(test_failed);
} else {
exit(test_failed);
}
}
