From f4170d55f6938b464fdb35c3192d149d500f9a98 Mon Sep 17 00:00:00 2001 From: Bartlomiej Grzelewski Date: Fri, 1 Feb 2013 17:17:44 +0100 Subject: [PATCH] Support for "process per testcase" framework. Test defined in macro RUNNER_CHILD_TEST will be run in child process. [Issue#] SSDWSSP-62 [Problem] Some test must be run in child process. [Cause] N/A [Solution] Add new framework to test. [Verification] Successful compilation. Change-Id: I6336c195ec3c7ec5b20360c239c93c9197698822 --- modules/test/config.cmake | 2 + modules/test/include/dpl/test/test_runner.h | 12 + .../test/include/dpl/test/test_runner_child.h | 49 +++ modules/test/src/test_runner.cpp | 14 + modules/test/src/test_runner_child.cpp | 283 ++++++++++++++++++ tests/dpl/CMakeLists.txt | 1 + tests/dpl/test/CMakeLists.txt | 31 ++ tests/dpl/test/main.cpp | 28 ++ tests/dpl/test/runner_child.cpp | 91 ++++++ 9 files changed, 511 insertions(+) create mode 100644 modules/test/include/dpl/test/test_runner_child.h create mode 100644 modules/test/src/test_runner_child.cpp create mode 100644 tests/dpl/test/CMakeLists.txt create mode 100644 tests/dpl/test/main.cpp create mode 100644 tests/dpl/test/runner_child.cpp diff --git a/modules/test/config.cmake b/modules/test/config.cmake index 69d5341..d3485cf 100644 --- a/modules/test/config.cmake +++ b/modules/test/config.cmake @@ -22,6 +22,7 @@ SET(DPL_TEST_ENGINE_SOURCES ${PROJECT_SOURCE_DIR}/modules/test/src/test_results_collector.cpp ${PROJECT_SOURCE_DIR}/modules/test/src/test_runner.cpp + ${PROJECT_SOURCE_DIR}/modules/test/src/test_runner_child.cpp PARENT_SCOPE ) @@ -29,6 +30,7 @@ SET(DPL_TEST_ENGINE_SOURCES SET(DPL_TEST_ENGINE_HEADERS ${PROJECT_SOURCE_DIR}/modules/test/include/dpl/test/test_results_collector.h ${PROJECT_SOURCE_DIR}/modules/test/include/dpl/test/test_runner.h + ${PROJECT_SOURCE_DIR}/modules/test/include/dpl/test/test_runner_child.h PARENT_SCOPE ) diff --git a/modules/test/include/dpl/test/test_runner.h b/modules/test/include/dpl/test/test_runner.h index a4db750..1c4d28a 100644 --- a/modules/test/include/dpl/test/test_runner.h +++ b/modules/test/include/dpl/test/test_runner.h @@ -48,6 +48,10 @@ class TestRunner bool m_runIgnored; public: + TestRunner() + : m_terminate(false) + {} + typedef void (*TestCase)(); private: @@ -85,6 +89,10 @@ private: DPL::Atomic m_totalAssertions; + // Terminate without any logs. + // Some test requires to call fork function. + // Child process must not produce any logs and should die quietly. + bool m_terminate; void Banner(); void InvalidArgs(const std::string& message = "Invalid arguments!"); void Usage(); @@ -120,6 +128,8 @@ public: //! \param[in] aMessage error message TestFailed(const char* aTest, const char* aFile, int aLine, const std::string &aMessage); + TestFailed(const std::string &message); + std::string GetMessage() const { return m_message; @@ -156,6 +166,8 @@ public: typedef std::vector ArgsList; int ExecTestRunner(const ArgsList& args); bool getRunIgnored() const; + // The runner will terminate as soon as possible (after current test). + void terminate(); }; typedef DPL::Singleton TestRunnerSingleton; diff --git a/modules/test/include/dpl/test/test_runner_child.h b/modules/test/include/dpl/test/test_runner_child.h new file mode 100644 index 0000000..1eb9ea6 --- /dev/null +++ b/modules/test/include/dpl/test/test_runner_child.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 Samsung Electronics Co., Ltd All Rights Reserved + * + * 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 test_runner_child.h + * @author Bartlomiej Grzelewski (b.grzelewski@samsung.com) + * @version 1.0 + * @brief This file is the header file of test runner + */ +#ifndef DPL_TEST_RUNNER_CHILD_H +#define DPL_TEST_RUNNER_CHILD_H + +#include + +namespace DPL { +namespace Test { + +void RunChildProc(TestRunner::TestCase procChild); + +} // namespace Test +} // namespace DPL + +#define RUNNER_CHILD_TEST(Proc) \ + void Proc(); \ + void Proc##Child(); \ + static int Static##Proc##Init() \ + { \ + DPL::Test::TestRunnerSingleton::Instance().RegisterTest(#Proc, &Proc); \ + return 0; \ + } \ + const int DPL_UNUSED Static##Proc##InitVar = Static##Proc##Init(); \ + void Proc(){ \ + DPL::Test::RunChildProc(&Proc##Child); \ + } \ + void Proc##Child() + +#endif // DPL_TEST_RUNNER_CHILD_H diff --git a/modules/test/src/test_runner.cpp b/modules/test/src/test_runner.cpp index 506dd10..d2296e8 100644 --- a/modules/test/src/test_runner.cpp +++ b/modules/test/src/test_runner.cpp @@ -80,6 +80,11 @@ TestRunner::TestFailed::TestFailed(const char* aTest, m_message = assertMsg.str(); } +TestRunner::TestFailed::TestFailed(const std::string &message) +{ + m_message = message; +} + void TestRunner::RegisterTest(const char *testName, TestCase proc) { m_testGroups[m_currentGroup].push_back(TestCaseStruct(testName, proc)); @@ -194,6 +199,10 @@ void TestRunner::RunTests() if (m_startTestId.empty()) { RunTestCase(test); } + if (m_terminate == true) { + // Terminate quietly without any logs + return; + } } } } @@ -505,5 +514,10 @@ bool TestRunner::getRunIgnored() const return m_runIgnored; } +void TestRunner::terminate() +{ + m_terminate = true; +} + } } // namespace DPL diff --git a/modules/test/src/test_runner_child.cpp b/modules/test/src/test_runner_child.cpp new file mode 100644 index 0000000..1f1bd98 --- /dev/null +++ b/modules/test/src/test_runner_child.cpp @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2013 Samsung Electronics Co., Ltd All Rights Reserved + * + * 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 test_runner_child.cpp + * @author Bartlomiej Grzelewski (b.grzelewski@samsung.com) + * @version 1.0 + * @brief This file is the implementation file of test runner + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +const int PIPE_CLOSED = -1; +} + +namespace DPL +{ +namespace Test +{ + +class PipeWrapper : DPL::Noncopyable { +public: + enum Usage { + READONLY, + WRITEONLY + }; + + enum Status { + SUCCESS, + TIMEOUT, + ERROR + }; + + PipeWrapper(){ + if (-1 == pipe(m_pipefd)) { + m_pipefd[0] = PIPE_CLOSED; + m_pipefd[1] = PIPE_CLOSED; + } + } + + bool isReady(){ + return m_pipefd[0] != PIPE_CLOSED || m_pipefd[1] != PIPE_CLOSED; + } + + void setUsage(Usage usage) { + if (usage == READONLY) { + closeHelp(1); + } + if (usage == WRITEONLY) { + closeHelp(0); + } + } + ~PipeWrapper(){ + closeHelp(0); + closeHelp(1); + } + + Status send(int code, std::string &message) { + if (m_pipefd[1] == PIPE_CLOSED) + return ERROR; + + std::ostringstream output; + output << toBinaryString(code); + output << toBinaryString(static_cast(message.size())); + output << message; + + std::string binary = output.str(); + int size = binary.size(); + + if ((writeHelp(&size, sizeof(int)) == ERROR) || (writeHelp(binary.c_str(), size) == ERROR)) { + return ERROR; + } + return SUCCESS; + } + + Status receive(int &code, std::string &data, time_t deadline) { + if (m_pipefd[0] == PIPE_CLOSED) + return ERROR; + + int size; + Status ret; + + if ((ret = readHelp(&size, sizeof(int), deadline)) != SUCCESS) + return ret; + + std::vector buffer; + buffer.resize(size); + + if ((ret = readHelp(&buffer[0], size, deadline)) != SUCCESS) + return ret; + + try { + DPL::BinaryQueue queue; + queue.AppendCopy(&buffer[0], size); + + queue.FlattenConsume(&code, sizeof(int)); + queue.FlattenConsume(&size, sizeof(int)); + + buffer.resize(size); + + queue.FlattenConsume(&buffer[0], size); + data.assign(buffer.begin(), buffer.end()); + } catch (DPL::BinaryQueue::Exception::Base &e) { + return ERROR; + } + return SUCCESS; + } + + void closeAll() { + closeHelp(0); + closeHelp(1); + } +private: + std::string toBinaryString(int data) { + char buffer[sizeof(int)]; + memcpy(buffer, &data, sizeof(int)); + return std::string(buffer, buffer+sizeof(int)); + } + + + void closeHelp(int desc) { + if (m_pipefd[desc] != PIPE_CLOSED) { + TEMP_FAILURE_RETRY(close(m_pipefd[desc])); + m_pipefd[desc] = PIPE_CLOSED; + } + } + + Status writeHelp(const void *buffer, int size) { + int ready = 0; + const char *p = static_cast(buffer); + while (ready != size) { + int ret = write(m_pipefd[1], &p[ready], size-ready); + + if (ret == -1 && (errno == EAGAIN || errno == EINTR)) + continue; + + if (ret == -1) { + closeHelp(1); + return ERROR; + } + + ready += ret; + } + return SUCCESS; + } + + Status readHelp(void *buf, int size, time_t deadline) { + int ready = 0; + char *buffer = static_cast(buf); + while (ready != size) { + time_t wait = deadline - time(0); + wait = wait < 1 ? 1 : wait; + pollfd fds = { m_pipefd[0], POLLIN, 0}; + + int pollReturn = poll(&fds, 1, wait * 1000); + + if (pollReturn == 0) { + return TIMEOUT; // Timeout + } + + if (pollReturn < -1) { + return ERROR; + } + + int ret = read(m_pipefd[0], &buffer[ready], size-ready); + + if (ret == -1 && (errno == EAGAIN || errno == EINTR)) + continue; + + if (ret == -1 || ret == 0) { + closeHelp(0); + return ERROR; + } + + ready += ret; + } + return SUCCESS; + } + + int m_pipefd[2]; +}; + +void RunChildProc(TestRunner::TestCase procChild){ + PipeWrapper pipe; + if (!pipe.isReady()) { + throw TestRunner::TestFailed("Pipe creation failed"); + } + + pid_t pid = fork(); + + if (pid == -1) + throw TestRunner::TestFailed("Child creation failed"); + + if (pid != 0) { + // parent code + pipe.setUsage(PipeWrapper::READONLY); + + int code; + std::string message; + + int pipeReturn = pipe.receive(code, message, time(0)+10); + + if (pipeReturn != PipeWrapper::SUCCESS) { // Timeout or reading error + pipe.closeAll(); + kill(pid, SIGINT); + } + + int status; + waitpid(pid, &status, 0); + + if (pipeReturn == PipeWrapper::TIMEOUT) { + throw TestRunner::TestFailed("Timeout"); + } + + if (pipeReturn == PipeWrapper::ERROR) { + throw TestRunner::TestFailed("Reading pipe error"); + } + + if (code == 0) { + throw TestRunner::TestFailed(message); + } + } else { + // child code + + // End Runner after current test + TestRunnerSingleton::Instance().terminate(); + int code = 1; + std::string msg; + +// close(0); // stdin + close(1); // stdout + close(2); // stderr + + pipe.setUsage(PipeWrapper::WRITEONLY); + + try { + procChild(); + } catch (DPL::Test::TestRunner::TestFailed &e) { + msg = e.GetMessage(); + code = 0; + } catch (...) { // Pokemon Catch... cache them all... + msg = "unhandled exeception"; + code = 0; + } + pipe.send(code, msg); + } +} + +} // namespace Test +} // namespace DPL diff --git a/tests/dpl/CMakeLists.txt b/tests/dpl/CMakeLists.txt index b2348aa..1dea774 100644 --- a/tests/dpl/CMakeLists.txt +++ b/tests/dpl/CMakeLists.txt @@ -26,3 +26,4 @@ ADD_SUBDIRECTORY(dbus) ADD_SUBDIRECTORY(event) #ADD_SUBDIRECTORY(localization) TODO localization mockups need to be fixed ADD_SUBDIRECTORY(utils) +ADD_SUBDIRECTORY(test) diff --git a/tests/dpl/test/CMakeLists.txt b/tests/dpl/test/CMakeLists.txt new file mode 100644 index 0000000..6e34912 --- /dev/null +++ b/tests/dpl/test/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (c) 2013 Samsung Electronics Co., Ltd All Rights Reserved +# +# 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 CMakeLists.txt +# @author Bartlomiej Grzelewski (b.grzelewski@samsung.com) +# @version 1.0 +# @brief +# + +SET(TARGET_NAME "dpl-tests-test") + +# Set DPL tests sources +SET(DPL_TESTS_UTIL_SOURCES + ${TESTS_DPL_DIR}/test/main.cpp + ${TESTS_DPL_DIR}/test/runner_child.cpp +) + +#WRT_TEST_ADD_INTERNAL_DEPENDENCIES(${TARGET_NAME} ${TARGET_DPL_UTILS_EFL}) +WRT_TEST_BUILD(${TARGET_NAME} ${DPL_TESTS_UTIL_SOURCES}) +WRT_TEST_INSTALL(${TARGET_NAME}) diff --git a/tests/dpl/test/main.cpp b/tests/dpl/test/main.cpp new file mode 100644 index 0000000..42ffe3a --- /dev/null +++ b/tests/dpl/test/main.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved + * + * 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 Przemyslaw Dobrowolski (p.dobrowolsk@samsung.com) + * @version 1.0 + * @brief This file is the implementation file of main + */ +#include + +int main(int argc, char *argv[]) +{ + return DPL::Test::TestRunnerSingleton::Instance().ExecTestRunner(argc, argv); +} + diff --git a/tests/dpl/test/runner_child.cpp b/tests/dpl/test/runner_child.cpp new file mode 100644 index 0000000..f5ae79b --- /dev/null +++ b/tests/dpl/test/runner_child.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2013 Samsung Electronics Co., Ltd All Rights Reserved + * + * 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 widget_version.cpp + * @author Bartlomiej Grzelewski (b.grzelewski@samsung.com) + * @version 1.0 + * @brief Implementation file for test cases for engine internal tests + */ +#include +#include +#include +#include +#include + +RUNNER_TEST_GROUP_INIT(DPL_TESTS_TEST_CHILD) + +RUNNER_TEST(t00_pass) +{ + RUNNER_ASSERT_MSG(1, "This test should pass"); +} + +RUNNER_CHILD_TEST(t01_pass) +{ + RUNNER_ASSERT_MSG(1, "This test should pass"); +} + +RUNNER_CHILD_TEST(t02_fail) +{ + RUNNER_ASSERT_MSG(0, "This test should fail"); +} + +RUNNER_CHILD_TEST(t03_fail_timeout) +{ + sleep(20); + RUNNER_ASSERT_MSG(1, "This test should fail"); +} + +RUNNER_CHILD_TEST(t04_fail) +{ + RUNNER_ASSERT_MSG(1, "This test should fail"); + RUNNER_ASSERT_MSG(1, "This test should fail"); + RUNNER_ASSERT_MSG(1, "This test should fail"); + RUNNER_ASSERT_MSG(1, "This test should fail"); + RUNNER_ASSERT_MSG(0, "This test should fail"); +} + +RUNNER_CHILD_TEST(t05_fail_child_died) +{ + kill(getpid(), SIGKILL); + RUNNER_ASSERT_MSG(1, "This test should fail"); +} + +RUNNER_CHILD_TEST(t06_pass_8_second_test) +{ + sleep(8); + RUNNER_ASSERT_MSG(1, "This test should pass"); +} + +RUNNER_CHILD_TEST(t07_fail_unknown_exception) +{ + throw("hello"); +} + +RUNNER_CHILD_TEST(t08_fail_unknown_exception) +{ + throw(1); +} + +RUNNER_CHILD_TEST(t09_fail_you_should_see_text_normal_assert) +{ + RUNNER_ASSERT_MSG(0, "Normal assert"); +} + +RUNNER_CHILD_TEST(t10_pass) +{ + RUNNER_ASSERT_MSG(1, "Normal assert"); +} + -- 2.34.1