mbed-os for GR-LYCHEE
Dependents: mbed-os-example-blinky-gr-lychee GR-Boads_Camera_sample GR-Boards_Audio_Recoder GR-Boads_Camera_DisplayApp ... more
Diff: features/frameworks/utest/source/utest_harness.cpp
- Revision:
- 0:f782d9c66c49
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/features/frameworks/utest/source/utest_harness.cpp Fri Feb 02 05:42:23 2018 +0000 @@ -0,0 +1,375 @@ +/**************************************************************************** + * 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); + } +}