Support for "process per testcase" framework.
authorBartlomiej Grzelewski <b.grzelewski@samsung.com>
Fri, 1 Feb 2013 16:17:44 +0000 (17:17 +0100)
committerGerrit Code Review <gerrit2@kim11>
Wed, 6 Feb 2013 09:34:09 +0000 (18:34 +0900)
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
modules/test/include/dpl/test/test_runner.h
modules/test/include/dpl/test/test_runner_child.h [new file with mode: 0644]
modules/test/src/test_runner.cpp
modules/test/src/test_runner_child.cpp [new file with mode: 0644]
tests/dpl/CMakeLists.txt
tests/dpl/test/CMakeLists.txt [new file with mode: 0644]
tests/dpl/test/main.cpp [new file with mode: 0644]
tests/dpl/test/runner_child.cpp [new file with mode: 0644]

index 69d5341..d3485cf 100644 (file)
@@ -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
 )
 
index a4db750..1c4d28a 100644 (file)
@@ -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<std::string> 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<TestRunner> 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 (file)
index 0000000..1eb9ea6
--- /dev/null
@@ -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 <dpl/test/test_runner.h>
+
+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
index 506dd10..d2296e8 100644 (file)
@@ -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 (file)
index 0000000..1f1bd98
--- /dev/null
@@ -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 <stddef.h>
+#include <dpl/test/test_runner.h>
+#include <dpl/test/test_runner_child.h>
+#include <dpl/test/test_results_collector.h>
+#include <dpl/binary_queue.h>
+#include <dpl/exception.h>
+#include <dpl/scoped_free.h>
+#include <dpl/foreach.h>
+#include <dpl/colors.h>
+#include <pcrecpp.h>
+#include <algorithm>
+#include <cstdio>
+#include <memory.h>
+#include <libgen.h>
+#include <cstring>
+#include <cstdlib>
+#include <ctime>
+#include <unistd.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dpl/utils/wrt_global_settings.h>
+
+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<int>(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<char> 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<const char *>(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<char*>(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
index b2348aa..1dea774 100644 (file)
@@ -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 (file)
index 0000000..6e34912
--- /dev/null
@@ -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 (file)
index 0000000..42ffe3a
--- /dev/null
@@ -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 <dpl/test/test_runner.h>
+
+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 (file)
index 0000000..f5ae79b
--- /dev/null
@@ -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 <dpl/test/test_runner_child.h>
+#include <unistd.h>
+#include <vector>
+#include <sys/types.h>
+#include <signal.h>
+
+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");
+}
+