From 964af7844dd67a45e5dac78789c30c4d636b378d Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Wed, 29 Mar 2017 14:47:21 +0200 Subject: [PATCH 01/15] Rework CMake logic around Qt and use Qt in CI --- .travis.yml | 17 ++++++++----- CMakeLists.txt | 45 +++++++++++++++++++++++++++++++--- appveyor.bat | 20 +++++++++++++++ appveyor.yml | 40 ++++++------------------------ examples/CalcQt/CMakeLists.txt | 35 +++++++------------------- travis.sh | 1 + 6 files changed, 91 insertions(+), 67 deletions(-) create mode 100644 appveyor.bat diff --git a/.travis.yml b/.travis.yml index 05685dcb..493ef248 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,5 @@ language: cpp os: - - osx - linux sudo: required dist: trusty @@ -13,14 +12,19 @@ env: - GMOCK_PATH=/usr/src/gmock #1.6.0 from ubuntu trusty repo matrix: exclude: - - os: osx - env: GMOCK_PATH=/usr/src/gmock - - os: osx - compiler: gcc #does anyone on osx use it? + - os: linux + compiler: gcc + env: GMOCK_VER=1.8.0 include: - os: linux compiler: gcc env: GMOCK_VER=1.8.0 VALGRIND_TESTS=ON + - os: osx + compiler: clang + env: GMOCK_VER=1.8.0 QTDIR=/usr/local/Cellar/qt5/5.8.0_1 + - os: osx + compiler: clang + env: GMOCK_VER=1.7.0 QTDIR=/usr/local/Cellar/qt5/5.8.0_1 addons: apt: @@ -35,9 +39,10 @@ addons: - google-mock - ninja-build - valgrind + - qtbase5-dev before_install: - - if [[ "${TRAVIS_OS_NAME}" = "osx" ]]; then brew update && brew install ninja; fi + - if [[ "${TRAVIS_OS_NAME}" = "osx" ]]; then brew update && brew install ninja && brew install qt5; fi script: ./travis.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 576afe0d..8b70ebfe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,9 @@ set(CUKE_ENABLE_EXAMPLES OFF CACHE BOOL "Enable the examples") set(GMOCK_SRC_DIR "" CACHE STRING "Google Mock framework sources path (otherwise downloaded)") set(GMOCK_VER "1.7.0" CACHE STRING "Google Mock framework version to be used") option(VALGRIND_TESTS "Run tests within Valgrind" OFF) +set(CUKE_DISABLE_QT OFF CACHE BOOL "Disable using Qt framework") +set(ignoreMe "${QT_QMAKE_EXECUTABLE}") #supress warning set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) # @@ -103,6 +105,34 @@ if(NOT CUKE_DISABLE_GTEST) endif() endif() +# +# Qt +# + +if(NOT CUKE_DISABLE_QT) + set(CMAKE_PREFIX_PATH $ENV{QTDIR}) + find_package(Qt5Core QUIET) + find_package(Qt5Widgets QUIET) + find_package(Qt5Test QUIET) + + if(${Qt5Core_FOUND} AND ${Qt5Widgets_FOUND} AND ${Qt5Test_FOUND}) + message(STATUS "Found Qt version: ${Qt5Core_VERSION_STRING}") + include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Test_INCLUDE_DIRS}) +# set(CMAKE_INCLUDE_CURRENT_DIR ON) +# set(CMAKE_AUTOMOC ON) + if (Qt5_POSITION_INDEPENDENT_CODE) + SET(CMAKE_POSITION_INDEPENDENT_CODE ON) + endif() + set(QT_LIBRARIES Qt5::Core Qt5::Widgets Qt5::Test) + else() + find_package(Qt4 COMPONENTS QtCore QtGui QtTest) + if(QT4_FOUND) + set(QT_LIBRARIES Qt4::QtCore Qt4::QtGui Qt4::QtTest) + include(${QT_USE_FILE}) + endif() + endif() +endif() + # # Valgrind # @@ -154,11 +184,8 @@ endif() # set(CUKE_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) - include_directories(${CUKE_INCLUDE_DIR}) - set(CUKE_LIBRARIES cucumber-cpp ${CUKE_EXTRA_LIBRARIES}) - add_subdirectory(src) # @@ -222,4 +249,16 @@ endif() if(CUKE_ENABLE_EXAMPLES) add_subdirectory(examples) + + #check if c++11 is nedded + if(${Qt5Core_FOUND}) + find_program(QMAKE_EXECUTABLE NAMES qmake HINTS ${QTDIR} ENV QTDIR PATH_SUFFIXES bin) + execute_process(COMMAND ${QMAKE_EXECUTABLE} -query QT_VERSION OUTPUT_VARIABLE QT_VERSION) + if(QT_VERSION GREATER 5.6.99) + message(STATUS "C++11 is needed from Qt version 5.7.0, building with c++11 enabled") + set_property(TARGET libcalcqt PROPERTY CXX_STANDARD 11) + set_property(TARGET calcqt PROPERTY CXX_STANDARD 11) + endif() + endif() endif() + diff --git a/appveyor.bat b/appveyor.bat new file mode 100644 index 00000000..1d6a25ce --- /dev/null +++ b/appveyor.bat @@ -0,0 +1,20 @@ +set PATH=C:\Ruby200\bin;%BOOST_LIBRARYDIR%;%PATH% +if defined MINGW_ROOT set PATH=%MINGW_ROOT%\bin;C:\msys64\usr\bin\;%PATH% +if defined QT_DIR set PATH=%QT_DIR%\bin;%PATH% +if "%CMAKE_GENERATOR%"=="NMake Makefiles" call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" %PLATFORM% +echo %PATH% + +git submodule init +git submodule update +call gem install bundle +call bundle install +if defined MINGW_ARCH bash -lc "pacman --needed --noconfirm -S mingw-w64-%MINGW_ARCH%-boost + +cmake -E make_directory build +cmake -E chdir build cmake -G "%CMAKE_GENERATOR%" -DCUKE_ENABLE_EXAMPLES=on -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_INCLUDEDIR="%BOOST_INCLUDEDIR%" -DBOOST_LIBRARYDIR="%BOOST_LIBRARYDIR%" -DQT_QMAKE_EXECUTABLE="%QT_DIR%/bin/qmake.exe" .. +cmake --build build + +set CTEST_OUTPUT_ON_FAILURE=ON +cmake --build build --target test +cmake --build build --target features + diff --git a/appveyor.yml b/appveyor.yml index d9ed4f28..2bbe519d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,59 +2,35 @@ version: "{branch}-ci-{build}" os: Visual Studio 2015 environment: - RUBY_VERSION: 200 matrix: - - build: mingw - platform: x86 + - CMAKE_GENERATOR: 'MSYS Makefiles' MINGW_ARCH: i686 - MSYSTEM: MINGW32 MINGW_ROOT: C:\msys64\mingw32 BOOST_ROOT: C:\msys64\mingw32 BOOST_LIBRARYDIR: C:\msys64\mingw32\lib BOOST_INCLUDEDIR: C:\msys64\mingw32\include\boost - CMAKE_GENERATOR: 'MSYS Makefiles' - - build: mingw - platform: x64 + QT_DIR: C:\Qt\5.8\mingw53_32 + - CMAKE_GENERATOR: 'MSYS Makefiles' MINGW_ARCH: x86_64 - MSYSTEM: MINGW64 MINGW_ROOT: C:\msys64\mingw64 BOOST_ROOT: C:\msys64\mingw64 BOOST_LIBRARYDIR: C:\msys64\mingw64\lib BOOST_INCLUDEDIR: C:\msys64\mingw64\include\boost - CMAKE_GENERATOR: 'MSYS Makefiles' - - build: msvc + - CMAKE_GENERATOR: 'NMake Makefiles' platform: x86 BOOST_ROOT: C:\Libraries\boost_1_59_0 BOOST_LIBRARYDIR: C:\Libraries\boost_1_59_0\lib32-msvc-14.0 BOOST_INCLUDEDIR: C:\Libraries\boost_1_59_0\boost - CMAKE_GENERATOR: 'NMake Makefiles' - - build: msvc + QT_DIR: C:\Qt\5.8\msvc2015 + - CMAKE_GENERATOR: 'NMake Makefiles' platform: x64 BOOST_ROOT: C:\Libraries\boost_1_59_0 BOOST_INCLUDEDIR: C:\Libraries\boost_1_59_0\boost BOOST_LIBRARYDIR: C:\Libraries\boost_1_59_0\lib64-msvc-14.0 - CMAKE_GENERATOR: 'NMake Makefiles' - -install: -- git submodule init -- git submodule update -- set PATH=C:\Ruby%RUBY_VERSION%\bin;%BOOST_LIBRARYDIR%;%PATH% -- gem install bundle -- bundle install -- bundle env -- if "%build%"=="mingw" set PATH=%MINGW_ROOT%\bin;C:\msys64\usr\bin\;%PATH% -- if "%build%"=="mingw" bash -lc "pacman --needed --noconfirm -S mingw-w64-%MINGW_ARCH%-boost + QT_DIR: C:\Qt\5.8\msvc2015_64 build_script: -- cmd: if "%build%"=="msvc" call "%VS140COMNTOOLS%\..\..\VC\vcvarsall.bat" %PLATFORM% -- cmd: cmake -E make_directory build -- cmd: cmake -E chdir build cmake -G "%CMAKE_GENERATOR%" -DCUKE_ENABLE_EXAMPLES=ON -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_INCLUDEDIR="%BOOST_INCLUDEDIR%" -DBOOST_LIBRARYDIR="%BOOST_LIBRARYDIR%" .. -- cmd: cmake --build build - -test_script: -- cmd: set CTEST_OUTPUT_ON_FAILURE=ON -- cmd: cmake --build build --target test -- cmd: cmake --build build --target features +- cmd: call appveyor.bat notifications: - provider: Email diff --git a/examples/CalcQt/CMakeLists.txt b/examples/CalcQt/CMakeLists.txt index 998f16bc..6ae44242 100644 --- a/examples/CalcQt/CMakeLists.txt +++ b/examples/CalcQt/CMakeLists.txt @@ -1,36 +1,19 @@ project(CalcQt) set(CALCQT_HEADERS src/CalculatorWidget.h) -set(CALCQT_SOURCES src/CalcQt.cpp src/CalculatorWidget.cpp) -include_directories(${CUKE_INCLUDE_DIRS} src) +include_directories(src) -find_package(Qt5Core QUIET) -find_package(Qt5Widgets QUIET) -find_package(Qt5Test QUIET) - -if(${Qt5Core_FOUND} AND ${Qt5Widgets_FOUND} AND ${Qt5Test_FOUND}) - set(CMAKE_INCLUDE_CURRENT_DIR ON) - set(CMAKE_AUTOMOC ON) - set(QT_LIBRARIES Qt5::Core Qt5::Widgets Qt5::Test) - - add_library(libcalcqt src/CalculatorWidget.cpp ${CALCQT_HEADERS}) - target_link_libraries(libcalcqt ${QT_LIBRARIES}) - - add_executable(calcqt ${CALCQT_SOURCES}) - target_link_libraries(calcqt libcalcqt ${QT_LIBRARIES}) -else() - find_package(Qt4 COMPONENTS QtCore QtGui QtTest) +if(QT_LIBRARIES) if(QT4_FOUND) - include(${QT_USE_FILE}) - qt4_wrap_cpp(CALCQT_HEADERS_MOC ${CALCQT_HEADERS}) - add_library(libcalcqt src/CalculatorWidget ${CALCQT_HEADERS_MOC}) - - add_executable(calcqt ${CALCQT_SOURCES} ${CALCQT_HEADERS_MOC}) - target_link_libraries(calcqt ${QT_LIBRARIES}) + qt4_wrap_cpp(CALCQT_MOC ${CALCQT_HEADERS}) + else() + qt5_wrap_cpp(CALCQT_MOC ${CALCQT_HEADERS}) endif() -endif() + add_library(libcalcqt src/CalculatorWidget.cpp ${CALCQT_MOC}) + + add_executable(calcqt src/CalcQt.cpp) + target_link_libraries(calcqt libcalcqt ${QT_LIBRARIES}) -if(QT_LIBRARIES) if(Boost_UNIT_TEST_FRAMEWORK_FOUND) include_directories(${Boost_INCLUDE_DIRS}) add_executable(BoostCalculatorQtSteps features/step_definitions/BoostCalculatorQtSteps) diff --git a/travis.sh b/travis.sh index 9279fec2..09e6c5da 100755 --- a/travis.sh +++ b/travis.sh @@ -1,6 +1,7 @@ #!/bin/sh set -e #break script on non-zero exitcode from any command set -x #display command being executed + gem install bundler bundle install From 26e6feb2c137dde88fe8e181178cd0293272f96f Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Wed, 29 Mar 2017 15:47:08 +0200 Subject: [PATCH 02/15] QtTestDriver + tests and examples --- CMakeLists.txt | 23 ++- examples/Calc/CMakeLists.txt | 5 + .../QtTestCalculatorSteps.cpp | 38 ++++ examples/CalcQt/CMakeLists.txt | 3 + .../QtTestCalculatorQtSteps.cpp | 72 +++++++ .../internal/drivers/DriverSelector.hpp | 3 + .../internal/drivers/QtTestDriver.hpp | 39 ++++ .../cucumber-cpp/internal/utils/qtCapture.hpp | 181 ++++++++++++++++++ src/CMakeLists.txt | 6 + src/drivers/QtTestDriver.cpp | 25 +++ tests/CMakeLists.txt | 6 +- .../integration/drivers/QtTestDriverTest.cpp | 65 +++++++ 12 files changed, 456 insertions(+), 10 deletions(-) create mode 100644 examples/Calc/features/step_definitions/QtTestCalculatorSteps.cpp create mode 100644 examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp create mode 100644 include/cucumber-cpp/internal/drivers/QtTestDriver.hpp create mode 100644 include/cucumber-cpp/internal/utils/qtCapture.hpp create mode 100644 src/drivers/QtTestDriver.cpp create mode 100644 tests/integration/drivers/QtTestDriverTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b70ebfe..b9c48058 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,12 +114,11 @@ if(NOT CUKE_DISABLE_QT) find_package(Qt5Core QUIET) find_package(Qt5Widgets QUIET) find_package(Qt5Test QUIET) + set(QT5_FOUND ${Qt5Core_FOUND} AND ${Qt5Widgets_FOUND} AND ${Qt5Test_FOUND}) - if(${Qt5Core_FOUND} AND ${Qt5Widgets_FOUND} AND ${Qt5Test_FOUND}) + if(QT5_FOUND) message(STATUS "Found Qt version: ${Qt5Core_VERSION_STRING}") include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Test_INCLUDE_DIRS}) -# set(CMAKE_INCLUDE_CURRENT_DIR ON) -# set(CMAKE_AUTOMOC ON) if (Qt5_POSITION_INDEPENDENT_CODE) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() @@ -249,15 +248,21 @@ endif() if(CUKE_ENABLE_EXAMPLES) add_subdirectory(examples) +endif() - #check if c++11 is nedded - if(${Qt5Core_FOUND}) - find_program(QMAKE_EXECUTABLE NAMES qmake HINTS ${QTDIR} ENV QTDIR PATH_SUFFIXES bin) - execute_process(COMMAND ${QMAKE_EXECUTABLE} -query QT_VERSION OUTPUT_VARIABLE QT_VERSION) - if(QT_VERSION GREATER 5.6.99) - message(STATUS "C++11 is needed from Qt version 5.7.0, building with c++11 enabled") +#check if c++11 is nedded +if(QT5_FOUND) + find_program(QMAKE_EXECUTABLE NAMES qmake HINTS ${QTDIR} ENV QTDIR PATH_SUFFIXES bin) + execute_process(COMMAND ${QMAKE_EXECUTABLE} -query QT_VERSION OUTPUT_VARIABLE QT_VERSION) + if(QT_VERSION GREATER 5.6.99) + message(STATUS "C++11 is needed from Qt version 5.7.0, building with c++11 enabled") + set_property(TARGET cucumber-cpp-nomain PROPERTY CXX_STANDARD 11) + set_property(TARGET cucumber-cpp PROPERTY CXX_STANDARD 11) + if(CUKE_ENABLE_EXAMPLES) set_property(TARGET libcalcqt PROPERTY CXX_STANDARD 11) set_property(TARGET calcqt PROPERTY CXX_STANDARD 11) + set_property(TARGET QtTestCalculatorQtSteps PROPERTY CXX_STANDARD 11) + set_property(TARGET QtTestCalculatorSteps PROPERTY CXX_STANDARD 11) endif() endif() endif() diff --git a/examples/Calc/CMakeLists.txt b/examples/Calc/CMakeLists.txt index 7157f3ec..865e9ff1 100644 --- a/examples/Calc/CMakeLists.txt +++ b/examples/Calc/CMakeLists.txt @@ -14,3 +14,8 @@ if(Boost_UNIT_TEST_FRAMEWORK_FOUND) add_executable(BoostCalculatorSteps features/step_definitions/BoostCalculatorSteps) target_link_libraries(BoostCalculatorSteps Calc ${CUKE_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) endif() + +if(QT_LIBRARIES) + add_executable(QtTestCalculatorSteps features/step_definitions/QtTestCalculatorSteps) + target_link_libraries(QtTestCalculatorSteps Calc ${QT_LIBRARIES} ${CUKE_LIBRARIES}) +endif() diff --git a/examples/Calc/features/step_definitions/QtTestCalculatorSteps.cpp b/examples/Calc/features/step_definitions/QtTestCalculatorSteps.cpp new file mode 100644 index 00000000..73de0f8b --- /dev/null +++ b/examples/Calc/features/step_definitions/QtTestCalculatorSteps.cpp @@ -0,0 +1,38 @@ +#include +#include + +#include "Calculator.h" + +using cucumber::ScenarioScope; + +struct CalcCtx { + Calculator calc; + double result; +}; + +GIVEN("^I have entered (\\d+) into the calculator$") { + REGEX_PARAM(double, n); + ScenarioScope context; + + context->calc.push(n); +} + +WHEN("^I press add") { + ScenarioScope context; + + context->result = context->calc.add(); +} + +WHEN("^I press divide") { + ScenarioScope context; + + context->result = context->calc.divide(); +} + +THEN("^the result should be (.*) on the screen$") { + REGEX_PARAM(double, expected); + ScenarioScope context; + + QCOMPARE(expected, context->result); +} + diff --git a/examples/CalcQt/CMakeLists.txt b/examples/CalcQt/CMakeLists.txt index 6ae44242..49406ecf 100644 --- a/examples/CalcQt/CMakeLists.txt +++ b/examples/CalcQt/CMakeLists.txt @@ -8,6 +8,9 @@ if(QT_LIBRARIES) qt4_wrap_cpp(CALCQT_MOC ${CALCQT_HEADERS}) else() qt5_wrap_cpp(CALCQT_MOC ${CALCQT_HEADERS}) + + add_executable(QtTestCalculatorQtSteps features/step_definitions/QtTestCalculatorQtSteps) + target_link_libraries(QtTestCalculatorQtSteps libcalcqt ${QT_LIBRARIES} ${CUKE_LIBRARIES}) endif() add_library(libcalcqt src/CalculatorWidget.cpp ${CALCQT_MOC}) diff --git a/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp b/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp new file mode 100644 index 00000000..008c0932 --- /dev/null +++ b/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include "CalculatorWidget.h" + +static int argc = 0; +static QApplication app(argc, 0); +static int milliseconds = -1; + +int millisecondsToWait() { + if (milliseconds < 0) + { + char* envVariable = getenv("CALCQT_STEP_DELAY"); + milliseconds = (0 != envVariable) ? atoi(envVariable) : 0; + } + return milliseconds; +} + +std::istream& operator>> (std::istream& in, QString& val) { std::string s; in >> s; val = s.c_str(); return in; } +std::ostream& operator<< (std::ostream& out, const QString& val) { out << val.toLatin1().data(); return out; } + +GIVEN("^I just turned on the calculator$") { + cucumber::ScenarioScope calculator; + calculator->move(0, 0); + calculator->show(); +#if QT_VERSION >= 0x050000 + QTest::qWaitForWindowExposed(calculator.get()); +#else + QTest::qWaitForWindowShown(calculator.get()); +#endif + QTest::qWait(millisecondsToWait()); +} + +WHEN("^I press (\\d+)$") { + REGEX_PARAM(unsigned int, n); + cucumber::ScenarioScope calculator; + QTest::keyClick(calculator.get(), Qt::Key_0 + n, Qt::NoModifier, millisecondsToWait()); +} + +WHEN("^I press add") { + cucumber::ScenarioScope calculator; + QTest::keyClick(calculator.get(), Qt::Key_Plus, Qt::NoModifier, millisecondsToWait()); +} + +WHEN("^I press calculate") { + cucumber::ScenarioScope calculator; + QTest::keyClick(calculator.get(), Qt::Key_Return, Qt::NoModifier, millisecondsToWait()); +} + +WHEN("^I press clear") { + cucumber::ScenarioScope calculator; + QTest::keyClick(calculator.get(), Qt::Key_Escape, Qt::NoModifier, millisecondsToWait()); +} + +WHEN("^I press subtract") { + cucumber::ScenarioScope calculator; + QTest::keyClick(calculator.get(), Qt::Key_Minus, Qt::NoModifier, millisecondsToWait()); +} + +THEN("^the display should be empty$") { + cucumber::ScenarioScope calculator; + QCOMPARE(0, calculator->display().size()); + QTest::qWait(millisecondsToWait()); +} + +THEN("^the display should show (.*)$") { + REGEX_PARAM(QString, expected); + cucumber::ScenarioScope calculator; + QCOMPARE(calculator->display(), expected); + QTest::qWait(millisecondsToWait()); +} diff --git a/include/cucumber-cpp/internal/drivers/DriverSelector.hpp b/include/cucumber-cpp/internal/drivers/DriverSelector.hpp index f4ac8567..ad334df2 100644 --- a/include/cucumber-cpp/internal/drivers/DriverSelector.hpp +++ b/include/cucumber-cpp/internal/drivers/DriverSelector.hpp @@ -2,4 +2,7 @@ #include "GTestDriver.hpp" #elif defined(BOOST_TEST_CASE) #include "BoostDriver.hpp" +#elif defined(QTEST_H) + #include "QtTestDriver.hpp" #endif + diff --git a/include/cucumber-cpp/internal/drivers/QtTestDriver.hpp b/include/cucumber-cpp/internal/drivers/QtTestDriver.hpp new file mode 100644 index 00000000..0e380b4d --- /dev/null +++ b/include/cucumber-cpp/internal/drivers/QtTestDriver.hpp @@ -0,0 +1,39 @@ +#ifndef CUKE_QTTESTDRIVER_HPP_ +#define CUKE_QTTESTDRIVER_HPP_ + +#include "../step/StepManager.hpp" +#include + +namespace cucumber { +namespace internal { + +class QtTestStep : public BasicStep{ + friend class QtTestObject; +public: + QtTestStep(): BasicStep() {} + +protected: + const InvokeResult invokeStepBody(); +}; + +#define STEP_INHERITANCE(step_name) ::cucumber::internal::QtTestStep + +class QtTestObject: public QObject { + Q_OBJECT +public: + QtTestObject(QtTestStep* qtTestStep): step(qtTestStep) {} + virtual ~QtTestObject() {} + +protected: + QtTestStep* step; + +private slots: + void test() const { + step->body(); + } +}; + +} +} + +#endif /* CUKE_QTTESTDRIVER_HPP_ */ diff --git a/include/cucumber-cpp/internal/utils/qtCapture.hpp b/include/cucumber-cpp/internal/utils/qtCapture.hpp new file mode 100644 index 00000000..54c632cf --- /dev/null +++ b/include/cucumber-cpp/internal/utils/qtCapture.hpp @@ -0,0 +1,181 @@ +/* Based on Sir Digby Chicken post from: + * http://stackoverflow.com/questions/5419356/redirect-stdout-stderr-to-a-string + * who granted his work on BSD license + * adjusted for Qt by konserw + */ +#ifdef _MSC_VER +#include +#include +#define popen _popen +#define pclose _pclose +#define stat _stat +#define dup _dup +#define dup2 _dup2 +#define fileno _fileno +#define close _close +#define pipe _pipe +#define read _read +#define eof _eof +#else +#include +#endif + +#include +#include + +#ifndef STD_OUT_FD +#define STD_OUT_FD (fileno(stdout)) +#endif +#ifndef STD_ERR_FD +#define STD_ERR_FD (fileno(stderr)) +#endif + +class qtCapture { +public: + static void Init() { + // make stdout & stderr streams unbuffered + // so that we don't need to flush the streams + // before capture and after capture + // (fflush can cause a deadlock if the stream is currently being + QMutexLocker locker(&m_mutex); + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + } + + static void BeginCapture() { + QMutexLocker locker(&m_mutex); + if (m_capturing) + return; + + secure_pipe(m_pipe); + m_oldStdOut = secure_dup(STD_OUT_FD); + m_oldStdErr = secure_dup(STD_ERR_FD); + secure_dup2(m_pipe[WRITE], STD_OUT_FD); + secure_dup2(m_pipe[WRITE], STD_ERR_FD); + m_capturing = true; +#ifndef _MSC_VER + secure_close(m_pipe[WRITE]); +#endif + } + + static bool IsCapturing() { + QMutexLocker locker(&m_mutex); + return m_capturing; + } + + static void EndCapture() { + QMutexLocker locker(&m_mutex); + if (!m_capturing) + return; + + m_captured.clear(); + secure_dup2(m_oldStdOut, STD_OUT_FD); + secure_dup2(m_oldStdErr, STD_ERR_FD); + + const int bufSize = 1025; + char buf[bufSize]; + int bytesRead = 0; + bool fd_blocked(false); + do { + bytesRead = 0; + fd_blocked = false; +#ifdef _MSC_VER + if (!eof(m_pipe[READ])) + bytesRead = read(m_pipe[READ], buf, bufSize-1); +#else + bytesRead = read(m_pipe[READ], buf, bufSize-1); +#endif + if (bytesRead > 0) { + buf[bytesRead] = 0; + m_captured += buf; + } else if (bytesRead < 0) { + fd_blocked = (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR); + if (fd_blocked) + QThread::msleep(10); + } + } + while(fd_blocked || bytesRead == (bufSize-1)); + + secure_close(m_oldStdOut); + secure_close(m_oldStdErr); + secure_close(m_pipe[READ]); +#ifdef _MSC_VER + secure_close(m_pipe[WRITE]); +#endif + m_capturing = false; + } + + static std::string GetCapture() { + QMutexLocker locker(&m_mutex); + return m_captured; + } + +private: + enum PIPES { READ, WRITE }; + + static int secure_dup(int src) { + int ret = -1; + bool fd_blocked = false; + do { + ret = dup(src); + fd_blocked = (errno == EINTR || errno == EBUSY); + if(fd_blocked) + QThread::msleep(10); + } while (ret < 0); + return ret; + } + + static void secure_pipe(int * pipes) { + int ret = -1; + bool fd_blocked = false; + do { +#ifdef _MSC_VER + ret = pipe(pipes, 65536, O_BINARY); +#else + ret = pipe(pipes) == -1; +#endif + fd_blocked = (errno == EINTR || errno == EBUSY); + if (fd_blocked) + QThread::msleep(10); + } while (ret < 0); + } + + static void secure_dup2(int src, int dest) { + int ret = -1; + bool fd_blocked = false; + do { + ret = dup2(src,dest); + fd_blocked = (errno == EINTR || errno == EBUSY); + if (fd_blocked) + QThread::msleep(10); + } while (ret < 0); + } + + static void secure_close(int & fd) { + int ret = -1; + bool fd_blocked = false; + do { + ret = close(fd); + fd_blocked = (errno == EINTR); + if (fd_blocked) + QThread::msleep(10); + } while (ret < 0); + + fd = -1; + } + + static int m_pipe[2]; + static int m_oldStdOut; + static int m_oldStdErr; + static bool m_capturing; + static QMutex m_mutex; + static std::string m_captured; +}; + +// actually define vars. +int qtCapture::m_pipe[2]; +int qtCapture::m_oldStdOut; +int qtCapture::m_oldStdErr; +bool qtCapture::m_capturing; +QMutex qtCapture::m_mutex; +std::string qtCapture::m_captured; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4f54a02b..1191f882 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,12 @@ set(CUKE_SOURCES connectors/wire/WireProtocolCommands.cpp ) +if(QT5_FOUND) + qt5_wrap_cpp(MOC_FILE ../include/cucumber-cpp/internal/drivers/QtTestDriver.hpp) + list(APPEND CUKE_SOURCES drivers/QtTestDriver.cpp) + list(APPEND CUKE_SOURCES ${MOC_FILE}) +endif() + if(GTEST_FOUND) list(APPEND CUKE_DEP_LIBRARIES GTest::GTest) list(APPEND CUKE_SOURCES drivers/GTestDriver.cpp) diff --git a/src/drivers/QtTestDriver.cpp b/src/drivers/QtTestDriver.cpp new file mode 100644 index 00000000..6a1495b8 --- /dev/null +++ b/src/drivers/QtTestDriver.cpp @@ -0,0 +1,25 @@ +#include "cucumber-cpp/internal/drivers/QtTestDriver.hpp" +#include "cucumber-cpp/internal/utils/qtCapture.hpp" + +#include + +namespace cucumber { +namespace internal { + +const InvokeResult QtTestStep::invokeStepBody() { + QtTestObject testObject(this); + + qtCapture::Init(); + qtCapture::BeginCapture(); + int returnValue = QTest::qExec(&testObject, 0, NULL); + qtCapture::EndCapture(); + + if(returnValue == 0) + return InvokeResult::success(); + else + return InvokeResult::failure(qtCapture::GetCapture()); +} + +} +} + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a0f2e5f9..90d44b9f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,7 @@ function(cuke_add_driver_test TEST_FILE) message(STATUS "Adding " ${TEST_NAME}) add_executable(${TEST_NAME} ${TEST_FILE}.cpp) target_link_libraries(${TEST_NAME} cucumber-cpp-nomain ${CUKE_EXTRA_LIBRARIES} ${ARGN}) - add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) + add_test(${TEST_NAME} ${TEST_NAME}) endfunction() if(GMOCK_FOUND) @@ -36,6 +36,10 @@ if(GMOCK_FOUND) cuke_add_driver_test(integration/drivers/GTestDriverTest ${CUKE_GTEST_LIBRARIES}) endif() +if(${Qt5Core_FOUND} AND ${Qt5Test_FOUND}) + cuke_add_driver_test(integration/drivers/QtTestDriverTest ${QT_LIBRARIES}) +endif() + if(Boost_UNIT_TEST_FRAMEWORK_FOUND) cuke_add_driver_test(integration/drivers/BoostDriverTest ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) endif() diff --git a/tests/integration/drivers/QtTestDriverTest.cpp b/tests/integration/drivers/QtTestDriverTest.cpp new file mode 100644 index 00000000..9a07e27e --- /dev/null +++ b/tests/integration/drivers/QtTestDriverTest.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include "../../utils/DriverTestRunner.hpp" + +using namespace cucumber; + +THEN(SUCCEED_MATCHER) { + ScenarioScope ctx; + QVERIFY(true); +} + +THEN(FAIL_MATCHER) { + ScenarioScope ctx; + QVERIFY(false); +} + +THEN(PENDING_MATCHER_1) { + pending(); +} + +THEN(PENDING_MATCHER_2) { + pending(PENDING_DESCRIPTION); +} + +using namespace cucumber::internal; + +class QtTestStepDouble : public QtTestStep { +public: + QtTestStepDouble() : QtTestStep() { + testRun = false; + } + + const InvokeResult invokeStepBody() { + return QtTestStep::invokeStepBody(); + } + + void body() { + testRun = true; + } + + bool testRun; +}; + +class QtTestDriverTest : public DriverTest { +public: + virtual void runAllTests() { + stepInvocationRunsStepBody(); + DriverTest::runAllTests(); + } + +private: + void stepInvocationRunsStepBody() { + QtTestStepDouble framework; + expectFalse("The test body has not been run", framework.testRun); + framework.invokeStepBody(); + expectTrue("The test body has been run", framework.testRun); + } +}; + +int main() { + QtTestDriverTest test; + return test.run(); +} + From 411cdbd93f4e318f6691c802bec844e87dbf447a Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Thu, 30 Mar 2017 14:03:45 +0200 Subject: [PATCH 03/15] Run qttest calculator example in travis --- travis.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/travis.sh b/travis.sh index 09e6c5da..396b8a9c 100755 --- a/travis.sh +++ b/travis.sh @@ -20,8 +20,14 @@ cmake --build build cmake --build build --target test cmake --build build --target features +QTTEST=build/examples/Calc/QtTestCalculatorSteps GTEST=build/examples/Calc/GTestCalculatorSteps BOOST=build/examples/Calc/BoostCalculatorSteps +if [ -f $QTTEST ]; then + $QTTEST >/dev/null & + cucumber examples/Calc + wait +fi if [ -f $GTEST ]; then $GTEST >/dev/null & cucumber examples/Calc From d83372365417024499abb340b2cedb4fdc6313f7 Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Thu, 30 Mar 2017 14:38:53 +0200 Subject: [PATCH 04/15] Pipe fix for mingw --- include/cucumber-cpp/internal/utils/qtCapture.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/include/cucumber-cpp/internal/utils/qtCapture.hpp b/include/cucumber-cpp/internal/utils/qtCapture.hpp index 54c632cf..8a3f6c3e 100644 --- a/include/cucumber-cpp/internal/utils/qtCapture.hpp +++ b/include/cucumber-cpp/internal/utils/qtCapture.hpp @@ -3,7 +3,8 @@ * who granted his work on BSD license * adjusted for Qt by konserw */ -#ifdef _MSC_VER +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +#define WINDOWS #include #include #define popen _popen @@ -53,7 +54,7 @@ class qtCapture { secure_dup2(m_pipe[WRITE], STD_OUT_FD); secure_dup2(m_pipe[WRITE], STD_ERR_FD); m_capturing = true; -#ifndef _MSC_VER +#ifndef WINDOWS secure_close(m_pipe[WRITE]); #endif } @@ -79,7 +80,7 @@ class qtCapture { do { bytesRead = 0; fd_blocked = false; -#ifdef _MSC_VER +#ifdef WINDOWS if (!eof(m_pipe[READ])) bytesRead = read(m_pipe[READ], buf, bufSize-1); #else @@ -99,7 +100,7 @@ class qtCapture { secure_close(m_oldStdOut); secure_close(m_oldStdErr); secure_close(m_pipe[READ]); -#ifdef _MSC_VER +#ifdef WINDOWS secure_close(m_pipe[WRITE]); #endif m_capturing = false; @@ -129,7 +130,7 @@ class qtCapture { int ret = -1; bool fd_blocked = false; do { -#ifdef _MSC_VER +#ifdef WINDOWS ret = pipe(pipes, 65536, O_BINARY); #else ret = pipe(pipes) == -1; From b1eaf2ca93ac88a28a56737466df19a9fef38d07 Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Thu, 30 Mar 2017 14:52:44 +0200 Subject: [PATCH 05/15] fix setting qt5_found variable --- CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b9c48058..9ed26321 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -114,9 +114,8 @@ if(NOT CUKE_DISABLE_QT) find_package(Qt5Core QUIET) find_package(Qt5Widgets QUIET) find_package(Qt5Test QUIET) - set(QT5_FOUND ${Qt5Core_FOUND} AND ${Qt5Widgets_FOUND} AND ${Qt5Test_FOUND}) - - if(QT5_FOUND) + if(${Qt5Core_FOUND} AND ${Qt5Widgets_FOUND} AND ${Qt5Test_FOUND}) + set(QT5_FOUND true) message(STATUS "Found Qt version: ${Qt5Core_VERSION_STRING}") include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Test_INCLUDE_DIRS}) if (Qt5_POSITION_INDEPENDENT_CODE) From 795ced69b15b07198201ce26c9152144072397d6 Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Fri, 31 Mar 2017 09:02:18 +0200 Subject: [PATCH 06/15] If c++11 is needed enable for all --- CMakeLists.txt | 79 ++++++++++++++++++++++---------------------------- 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ed26321..16da1fdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.1) project(Cucumber-Cpp) @@ -33,6 +33,40 @@ elseif(MSVC) #set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /analyze") endif() +# +# Qt +# + +if(NOT CUKE_DISABLE_QT) + set(CMAKE_PREFIX_PATH $ENV{QTDIR}) + find_package(Qt5Core QUIET) + find_package(Qt5Widgets QUIET) + find_package(Qt5Test QUIET) + if(${Qt5Core_FOUND} AND ${Qt5Widgets_FOUND} AND ${Qt5Test_FOUND}) + set(QT5_FOUND true) + message(STATUS "Found Qt version: ${Qt5Core_VERSION_STRING}") + include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Test_INCLUDE_DIRS}) + if (Qt5_POSITION_INDEPENDENT_CODE) + SET(CMAKE_POSITION_INDEPENDENT_CODE ON) + endif() + set(QT_LIBRARIES Qt5::Core Qt5::Widgets Qt5::Test) + + #check if c++11 is nedded + find_program(QMAKE_EXECUTABLE NAMES qmake HINTS ${QTDIR} ENV QTDIR PATH_SUFFIXES bin) + execute_process(COMMAND ${QMAKE_EXECUTABLE} -query QT_VERSION OUTPUT_VARIABLE QT_VERSION) + if(QT_VERSION GREATER 5.6.99) + message(STATUS "C++11 is needed from Qt version 5.7.0, building with c++11 enabled") + set(CMAKE_CXX_STANDARD 11) + endif() + else() + find_package(Qt4 COMPONENTS QtCore QtGui QtTest) + if(QT4_FOUND) + set(QT_LIBRARIES Qt4::QtCore Qt4::QtGui Qt4::QtTest) + include(${QT_USE_FILE}) + endif() + endif() +endif() + # # Boost # @@ -105,32 +139,6 @@ if(NOT CUKE_DISABLE_GTEST) endif() endif() -# -# Qt -# - -if(NOT CUKE_DISABLE_QT) - set(CMAKE_PREFIX_PATH $ENV{QTDIR}) - find_package(Qt5Core QUIET) - find_package(Qt5Widgets QUIET) - find_package(Qt5Test QUIET) - if(${Qt5Core_FOUND} AND ${Qt5Widgets_FOUND} AND ${Qt5Test_FOUND}) - set(QT5_FOUND true) - message(STATUS "Found Qt version: ${Qt5Core_VERSION_STRING}") - include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Test_INCLUDE_DIRS}) - if (Qt5_POSITION_INDEPENDENT_CODE) - SET(CMAKE_POSITION_INDEPENDENT_CODE ON) - endif() - set(QT_LIBRARIES Qt5::Core Qt5::Widgets Qt5::Test) - else() - find_package(Qt4 COMPONENTS QtCore QtGui QtTest) - if(QT4_FOUND) - set(QT_LIBRARIES Qt4::QtCore Qt4::QtGui Qt4::QtTest) - include(${QT_USE_FILE}) - endif() - endif() -endif() - # # Valgrind # @@ -249,20 +257,3 @@ if(CUKE_ENABLE_EXAMPLES) add_subdirectory(examples) endif() -#check if c++11 is nedded -if(QT5_FOUND) - find_program(QMAKE_EXECUTABLE NAMES qmake HINTS ${QTDIR} ENV QTDIR PATH_SUFFIXES bin) - execute_process(COMMAND ${QMAKE_EXECUTABLE} -query QT_VERSION OUTPUT_VARIABLE QT_VERSION) - if(QT_VERSION GREATER 5.6.99) - message(STATUS "C++11 is needed from Qt version 5.7.0, building with c++11 enabled") - set_property(TARGET cucumber-cpp-nomain PROPERTY CXX_STANDARD 11) - set_property(TARGET cucumber-cpp PROPERTY CXX_STANDARD 11) - if(CUKE_ENABLE_EXAMPLES) - set_property(TARGET libcalcqt PROPERTY CXX_STANDARD 11) - set_property(TARGET calcqt PROPERTY CXX_STANDARD 11) - set_property(TARGET QtTestCalculatorQtSteps PROPERTY CXX_STANDARD 11) - set_property(TARGET QtTestCalculatorSteps PROPERTY CXX_STANDARD 11) - endif() - endif() -endif() - From 15164eb65825d9ddf240c3b1fe12af1af06c7f2c Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Fri, 31 Mar 2017 16:59:31 +0200 Subject: [PATCH 07/15] First review --- .travis.yml | 2 +- CMakeLists.txt | 36 +++++++++++++++------------------- coveralls-cmake | 1 + examples/CalcQt/CMakeLists.txt | 1 + 4 files changed, 19 insertions(+), 21 deletions(-) create mode 160000 coveralls-cmake diff --git a/.travis.yml b/.travis.yml index 493ef248..472f0ca6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,7 +42,7 @@ addons: - qtbase5-dev before_install: - - if [[ "${TRAVIS_OS_NAME}" = "osx" ]]; then brew update && brew install ninja && brew install qt5; fi + - if [[ "${TRAVIS_OS_NAME}" = "osx" ]]; then brew update && brew install ninja qt5; fi script: ./travis.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b70ebfe..1ade7a33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,18 +2,17 @@ cmake_minimum_required(VERSION 2.8.12) project(Cucumber-Cpp) -set(CUKE_USE_STATIC_BOOST ${WIN32} CACHE BOOL "Statically link Boost (except boost::test)") -set(CUKE_USE_STATIC_GTEST ON CACHE BOOL "Statically link Google Test") - -set(CUKE_DISABLE_BOOST_TEST OFF CACHE BOOL "Disable boost:test") -set(CUKE_DISABLE_GTEST OFF CACHE BOOL "Disable Google Test framework") -set(CUKE_DISABLE_UNIT_TESTS OFF CACHE BOOL "Disable unit tests") -set(CUKE_DISABLE_E2E_TESTS OFF CACHE BOOL "Disable end-to-end tests") -set(CUKE_ENABLE_EXAMPLES OFF CACHE BOOL "Enable the examples") +option(CUKE_USE_STATIC_BOOST "Statically link Boost (except boost::test)" ${WIN32}) +option(CUKE_USE_STATIC_GTEST "Statically link Google Test" ON) +option(CUKE_DISABLE_BOOST_TEST "Disable boost:test" OFF) +option(CUKE_DISABLE_GTEST "Disable Google Test framework" OFF) +option(CUKE_DISABLE_UNIT_TESTS "Disable unit tests" OFF) +option(CUKE_DISABLE_E2E_TESTS "Disable end-to-end tests" OFF) +option(CUKE_ENABLE_EXAMPLES "Enable the examples" OFF) +option(VALGRIND_TESTS "Run tests within Valgrind" OFF) +option(CUKE_DISABLE_QT "Disable using Qt framework" OFF) set(GMOCK_SRC_DIR "" CACHE STRING "Google Mock framework sources path (otherwise downloaded)") set(GMOCK_VER "1.7.0" CACHE STRING "Google Mock framework version to be used") -option(VALGRIND_TESTS "Run tests within Valgrind" OFF) -set(CUKE_DISABLE_QT OFF CACHE BOOL "Disable using Qt framework") set(ignoreMe "${QT_QMAKE_EXECUTABLE}") #supress warning set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules) @@ -111,15 +110,12 @@ endif() if(NOT CUKE_DISABLE_QT) set(CMAKE_PREFIX_PATH $ENV{QTDIR}) - find_package(Qt5Core QUIET) - find_package(Qt5Widgets QUIET) - find_package(Qt5Test QUIET) + find_package(Qt5Core) + find_package(Qt5Widgets) + find_package(Qt5Test) if(${Qt5Core_FOUND} AND ${Qt5Widgets_FOUND} AND ${Qt5Test_FOUND}) message(STATUS "Found Qt version: ${Qt5Core_VERSION_STRING}") - include_directories(${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Test_INCLUDE_DIRS}) -# set(CMAKE_INCLUDE_CURRENT_DIR ON) -# set(CMAKE_AUTOMOC ON) if (Qt5_POSITION_INDEPENDENT_CODE) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() @@ -250,11 +246,11 @@ endif() if(CUKE_ENABLE_EXAMPLES) add_subdirectory(examples) - #check if c++11 is nedded + #check if c++11 is needed if(${Qt5Core_FOUND}) - find_program(QMAKE_EXECUTABLE NAMES qmake HINTS ${QTDIR} ENV QTDIR PATH_SUFFIXES bin) - execute_process(COMMAND ${QMAKE_EXECUTABLE} -query QT_VERSION OUTPUT_VARIABLE QT_VERSION) - if(QT_VERSION GREATER 5.6.99) +# find_program(QMAKE_EXECUTABLE NAMES qmake HINTS ${QTDIR} ENV QTDIR PATH_SUFFIXES bin) +# execute_process(COMMAND ${QMAKE_EXECUTABLE} -query QT_VERSION OUTPUT_VARIABLE QT_VERSION) + if(NOT ${Qt5Core_VERSION_STRING} VERSION_LESS 5.7) message(STATUS "C++11 is needed from Qt version 5.7.0, building with c++11 enabled") set_property(TARGET libcalcqt PROPERTY CXX_STANDARD 11) set_property(TARGET calcqt PROPERTY CXX_STANDARD 11) diff --git a/coveralls-cmake b/coveralls-cmake new file mode 160000 index 00000000..f06de69b --- /dev/null +++ b/coveralls-cmake @@ -0,0 +1 @@ +Subproject commit f06de69bbaaa7c7379d242388269a9f69dd86ff4 diff --git a/examples/CalcQt/CMakeLists.txt b/examples/CalcQt/CMakeLists.txt index 6ae44242..71c7c470 100644 --- a/examples/CalcQt/CMakeLists.txt +++ b/examples/CalcQt/CMakeLists.txt @@ -10,6 +10,7 @@ if(QT_LIBRARIES) qt5_wrap_cpp(CALCQT_MOC ${CALCQT_HEADERS}) endif() add_library(libcalcqt src/CalculatorWidget.cpp ${CALCQT_MOC}) + target_link_libraries(libcalcqt ${QT_LIBRARIES}) add_executable(calcqt src/CalcQt.cpp) target_link_libraries(calcqt libcalcqt ${QT_LIBRARIES}) From d1b3ffdaf0032cc1a33c69d7a42e72dd7cc92e19 Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Fri, 31 Mar 2017 17:00:57 +0200 Subject: [PATCH 08/15] Update CMake verision requirement --- CMakeLists.txt | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ade7a33..1b2475c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.1) project(Cucumber-Cpp) diff --git a/README.md b/README.md index d92cedc8..a548d297 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ with [CPP]. It relies on a few executables: -* [cmake](https://cmake.org/download/) 2.8.12 or later. +* [cmake](https://cmake.org/download/) 3.1 or later. Required to setup environment and build software It relies on a few libraries: From 5b10e6608aa453562c4b661ee2709b965406192c Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Fri, 31 Mar 2017 22:11:30 +0200 Subject: [PATCH 09/15] removed submodule added by mistake --- coveralls-cmake | 1 - 1 file changed, 1 deletion(-) delete mode 160000 coveralls-cmake diff --git a/coveralls-cmake b/coveralls-cmake deleted file mode 160000 index f06de69b..00000000 --- a/coveralls-cmake +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f06de69bbaaa7c7379d242388269a9f69dd86ff4 From 0202bed3eb71340eafe3b63083075145b9a8c28e Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Sat, 1 Apr 2017 16:28:20 +0200 Subject: [PATCH 10/15] Most of frirst review --- .travis.yml | 2 +- appveyor.yml | 2 +- examples/Calc/CMakeLists.txt | 2 +- examples/CalcQt/CMakeLists.txt | 1 - .../QtTestCalculatorQtSteps.cpp | 25 +-- .../cucumber-cpp/internal/utils/qtCapture.hpp | 182 ----------------- src/CMakeLists.txt | 1 + src/drivers/QtTestDriver.cpp | 183 ++++++++++++++++++ 8 files changed, 200 insertions(+), 198 deletions(-) delete mode 100644 include/cucumber-cpp/internal/utils/qtCapture.hpp diff --git a/.travis.yml b/.travis.yml index 472f0ca6..d466ae0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,7 +24,7 @@ matrix: env: GMOCK_VER=1.8.0 QTDIR=/usr/local/Cellar/qt5/5.8.0_1 - os: osx compiler: clang - env: GMOCK_VER=1.7.0 QTDIR=/usr/local/Cellar/qt5/5.8.0_1 + env: GMOCK_VER=1.7.0 addons: apt: diff --git a/appveyor.yml b/appveyor.yml index 2bbe519d..3f5307d5 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,7 +21,7 @@ environment: BOOST_ROOT: C:\Libraries\boost_1_59_0 BOOST_LIBRARYDIR: C:\Libraries\boost_1_59_0\lib32-msvc-14.0 BOOST_INCLUDEDIR: C:\Libraries\boost_1_59_0\boost - QT_DIR: C:\Qt\5.8\msvc2015 + QT_DIR: C:\Qt\5.6\msvc2015 - CMAKE_GENERATOR: 'NMake Makefiles' platform: x64 BOOST_ROOT: C:\Libraries\boost_1_59_0 diff --git a/examples/Calc/CMakeLists.txt b/examples/Calc/CMakeLists.txt index 865e9ff1..c6cda24f 100644 --- a/examples/Calc/CMakeLists.txt +++ b/examples/Calc/CMakeLists.txt @@ -15,7 +15,7 @@ if(Boost_UNIT_TEST_FRAMEWORK_FOUND) target_link_libraries(BoostCalculatorSteps Calc ${CUKE_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) endif() -if(QT_LIBRARIES) +if(QT5_FOUND) add_executable(QtTestCalculatorSteps features/step_definitions/QtTestCalculatorSteps) target_link_libraries(QtTestCalculatorSteps Calc ${QT_LIBRARIES} ${CUKE_LIBRARIES}) endif() diff --git a/examples/CalcQt/CMakeLists.txt b/examples/CalcQt/CMakeLists.txt index ee67d7e9..80a2a3a3 100644 --- a/examples/CalcQt/CMakeLists.txt +++ b/examples/CalcQt/CMakeLists.txt @@ -8,7 +8,6 @@ if(QT_LIBRARIES) qt4_wrap_cpp(CALCQT_MOC ${CALCQT_HEADERS}) else() qt5_wrap_cpp(CALCQT_MOC ${CALCQT_HEADERS}) - add_executable(QtTestCalculatorQtSteps features/step_definitions/QtTestCalculatorQtSteps) target_link_libraries(QtTestCalculatorQtSteps libcalcqt ${QT_LIBRARIES} ${CUKE_LIBRARIES}) endif() diff --git a/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp b/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp index 008c0932..95ba6515 100644 --- a/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp +++ b/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp @@ -1,9 +1,12 @@ #include -#include +#include "cucumber-cpp/autodetect.hpp" #include #include #include "CalculatorWidget.h" +std::istream& operator>> (std::istream& in, QString& val) { std::string s; in >> s; val = QString::fromLocal8Bit(s.c_str()); return in; } +std::ostream& operator<< (std::ostream& out, const QString& val) { out << val.toLocal8Bit().data(); return out; } + static int argc = 0; static QApplication app(argc, 0); static int milliseconds = -1; @@ -12,13 +15,14 @@ int millisecondsToWait() { if (milliseconds < 0) { char* envVariable = getenv("CALCQT_STEP_DELAY"); - milliseconds = (0 != envVariable) ? atoi(envVariable) : 0; + milliseconds = (envVariable) ? atoi(envVariable) : 0; } return milliseconds; } -std::istream& operator>> (std::istream& in, QString& val) { std::string s; in >> s; val = s.c_str(); return in; } -std::ostream& operator<< (std::ostream& out, const QString& val) { out << val.toLatin1().data(); return out; } +AFTER_STEP(){ + QTest::qWait(millisecondsToWait()); +} GIVEN("^I just turned on the calculator$") { cucumber::ScenarioScope calculator; @@ -29,44 +33,41 @@ GIVEN("^I just turned on the calculator$") { #else QTest::qWaitForWindowShown(calculator.get()); #endif - QTest::qWait(millisecondsToWait()); } WHEN("^I press (\\d+)$") { REGEX_PARAM(unsigned int, n); cucumber::ScenarioScope calculator; - QTest::keyClick(calculator.get(), Qt::Key_0 + n, Qt::NoModifier, millisecondsToWait()); + QTest::keyClick(calculator.get(), Qt::Key_0 + n, Qt::NoModifier, 0); } WHEN("^I press add") { cucumber::ScenarioScope calculator; - QTest::keyClick(calculator.get(), Qt::Key_Plus, Qt::NoModifier, millisecondsToWait()); + QTest::keyClick(calculator.get(), Qt::Key_Plus, Qt::NoModifier, 0); } WHEN("^I press calculate") { cucumber::ScenarioScope calculator; - QTest::keyClick(calculator.get(), Qt::Key_Return, Qt::NoModifier, millisecondsToWait()); + QTest::keyClick(calculator.get(), Qt::Key_Return, Qt::NoModifier, 0); } WHEN("^I press clear") { cucumber::ScenarioScope calculator; - QTest::keyClick(calculator.get(), Qt::Key_Escape, Qt::NoModifier, millisecondsToWait()); + QTest::keyClick(calculator.get(), Qt::Key_Escape, Qt::NoModifier, 0); } WHEN("^I press subtract") { cucumber::ScenarioScope calculator; - QTest::keyClick(calculator.get(), Qt::Key_Minus, Qt::NoModifier, millisecondsToWait()); + QTest::keyClick(calculator.get(), Qt::Key_Minus, Qt::NoModifier, 0); } THEN("^the display should be empty$") { cucumber::ScenarioScope calculator; QCOMPARE(0, calculator->display().size()); - QTest::qWait(millisecondsToWait()); } THEN("^the display should show (.*)$") { REGEX_PARAM(QString, expected); cucumber::ScenarioScope calculator; QCOMPARE(calculator->display(), expected); - QTest::qWait(millisecondsToWait()); } diff --git a/include/cucumber-cpp/internal/utils/qtCapture.hpp b/include/cucumber-cpp/internal/utils/qtCapture.hpp deleted file mode 100644 index 8a3f6c3e..00000000 --- a/include/cucumber-cpp/internal/utils/qtCapture.hpp +++ /dev/null @@ -1,182 +0,0 @@ -/* Based on Sir Digby Chicken post from: - * http://stackoverflow.com/questions/5419356/redirect-stdout-stderr-to-a-string - * who granted his work on BSD license - * adjusted for Qt by konserw - */ -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) -#define WINDOWS -#include -#include -#define popen _popen -#define pclose _pclose -#define stat _stat -#define dup _dup -#define dup2 _dup2 -#define fileno _fileno -#define close _close -#define pipe _pipe -#define read _read -#define eof _eof -#else -#include -#endif - -#include -#include - -#ifndef STD_OUT_FD -#define STD_OUT_FD (fileno(stdout)) -#endif -#ifndef STD_ERR_FD -#define STD_ERR_FD (fileno(stderr)) -#endif - -class qtCapture { -public: - static void Init() { - // make stdout & stderr streams unbuffered - // so that we don't need to flush the streams - // before capture and after capture - // (fflush can cause a deadlock if the stream is currently being - QMutexLocker locker(&m_mutex); - setvbuf(stdout, NULL, _IONBF, 0); - setvbuf(stderr, NULL, _IONBF, 0); - } - - static void BeginCapture() { - QMutexLocker locker(&m_mutex); - if (m_capturing) - return; - - secure_pipe(m_pipe); - m_oldStdOut = secure_dup(STD_OUT_FD); - m_oldStdErr = secure_dup(STD_ERR_FD); - secure_dup2(m_pipe[WRITE], STD_OUT_FD); - secure_dup2(m_pipe[WRITE], STD_ERR_FD); - m_capturing = true; -#ifndef WINDOWS - secure_close(m_pipe[WRITE]); -#endif - } - - static bool IsCapturing() { - QMutexLocker locker(&m_mutex); - return m_capturing; - } - - static void EndCapture() { - QMutexLocker locker(&m_mutex); - if (!m_capturing) - return; - - m_captured.clear(); - secure_dup2(m_oldStdOut, STD_OUT_FD); - secure_dup2(m_oldStdErr, STD_ERR_FD); - - const int bufSize = 1025; - char buf[bufSize]; - int bytesRead = 0; - bool fd_blocked(false); - do { - bytesRead = 0; - fd_blocked = false; -#ifdef WINDOWS - if (!eof(m_pipe[READ])) - bytesRead = read(m_pipe[READ], buf, bufSize-1); -#else - bytesRead = read(m_pipe[READ], buf, bufSize-1); -#endif - if (bytesRead > 0) { - buf[bytesRead] = 0; - m_captured += buf; - } else if (bytesRead < 0) { - fd_blocked = (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR); - if (fd_blocked) - QThread::msleep(10); - } - } - while(fd_blocked || bytesRead == (bufSize-1)); - - secure_close(m_oldStdOut); - secure_close(m_oldStdErr); - secure_close(m_pipe[READ]); -#ifdef WINDOWS - secure_close(m_pipe[WRITE]); -#endif - m_capturing = false; - } - - static std::string GetCapture() { - QMutexLocker locker(&m_mutex); - return m_captured; - } - -private: - enum PIPES { READ, WRITE }; - - static int secure_dup(int src) { - int ret = -1; - bool fd_blocked = false; - do { - ret = dup(src); - fd_blocked = (errno == EINTR || errno == EBUSY); - if(fd_blocked) - QThread::msleep(10); - } while (ret < 0); - return ret; - } - - static void secure_pipe(int * pipes) { - int ret = -1; - bool fd_blocked = false; - do { -#ifdef WINDOWS - ret = pipe(pipes, 65536, O_BINARY); -#else - ret = pipe(pipes) == -1; -#endif - fd_blocked = (errno == EINTR || errno == EBUSY); - if (fd_blocked) - QThread::msleep(10); - } while (ret < 0); - } - - static void secure_dup2(int src, int dest) { - int ret = -1; - bool fd_blocked = false; - do { - ret = dup2(src,dest); - fd_blocked = (errno == EINTR || errno == EBUSY); - if (fd_blocked) - QThread::msleep(10); - } while (ret < 0); - } - - static void secure_close(int & fd) { - int ret = -1; - bool fd_blocked = false; - do { - ret = close(fd); - fd_blocked = (errno == EINTR); - if (fd_blocked) - QThread::msleep(10); - } while (ret < 0); - - fd = -1; - } - - static int m_pipe[2]; - static int m_oldStdOut; - static int m_oldStdErr; - static bool m_capturing; - static QMutex m_mutex; - static std::string m_captured; -}; - -// actually define vars. -int qtCapture::m_pipe[2]; -int qtCapture::m_oldStdOut; -int qtCapture::m_oldStdErr; -bool qtCapture::m_capturing; -QMutex qtCapture::m_mutex; -std::string qtCapture::m_captured; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1191f882..9c2d0034 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -19,6 +19,7 @@ if(QT5_FOUND) qt5_wrap_cpp(MOC_FILE ../include/cucumber-cpp/internal/drivers/QtTestDriver.hpp) list(APPEND CUKE_SOURCES drivers/QtTestDriver.cpp) list(APPEND CUKE_SOURCES ${MOC_FILE}) + list(APPEND CUKE_DEP_LIBRARIES ${QT_LIBRARIES}) endif() if(GTEST_FOUND) diff --git a/src/drivers/QtTestDriver.cpp b/src/drivers/QtTestDriver.cpp index 6a1495b8..369c95de 100644 --- a/src/drivers/QtTestDriver.cpp +++ b/src/drivers/QtTestDriver.cpp @@ -3,6 +3,189 @@ #include +/* Based on Sir Digby Chicken post from: + * http://stackoverflow.com/questions/5419356/redirect-stdout-stderr-to-a-string + * who granted his work on BSD license + * adjusted for Qt by konserw + */ +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +#define WINDOWS +#include +#include +#define popen _popen +#define pclose _pclose +#define stat _stat +#define dup _dup +#define dup2 _dup2 +#define fileno _fileno +#define close _close +#define pipe _pipe +#define read _read +#define eof _eof +#else +#include +#endif + +#include +#include + +#ifndef STD_OUT_FD +#define STD_OUT_FD (fileno(stdout)) +#endif +#ifndef STD_ERR_FD +#define STD_ERR_FD (fileno(stderr)) +#endif + +class qtCapture { +public: + static void Init() { + // make stdout & stderr streams unbuffered + // so that we don't need to flush the streams + // before capture and after capture + // (fflush can cause a deadlock if the stream is currently being + QMutexLocker locker(&m_mutex); + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + } + + static void BeginCapture() { + QMutexLocker locker(&m_mutex); + if (m_capturing) + return; + + secure_pipe(m_pipe); + m_oldStdOut = secure_dup(STD_OUT_FD); + m_oldStdErr = secure_dup(STD_ERR_FD); + secure_dup2(m_pipe[WRITE], STD_OUT_FD); + secure_dup2(m_pipe[WRITE], STD_ERR_FD); + m_capturing = true; +#ifndef WINDOWS + secure_close(m_pipe[WRITE]); +#endif + } + + static bool IsCapturing() { + QMutexLocker locker(&m_mutex); + return m_capturing; + } + + static void EndCapture() { + QMutexLocker locker(&m_mutex); + if (!m_capturing) + return; + + m_captured.clear(); + secure_dup2(m_oldStdOut, STD_OUT_FD); + secure_dup2(m_oldStdErr, STD_ERR_FD); + + const int bufSize = 1025; + char buf[bufSize]; + int bytesRead = 0; + bool fd_blocked(false); + do { + bytesRead = 0; + fd_blocked = false; +#ifdef WINDOWS + if (!eof(m_pipe[READ])) + bytesRead = read(m_pipe[READ], buf, bufSize-1); +#else + bytesRead = read(m_pipe[READ], buf, bufSize-1); +#endif + if (bytesRead > 0) { + buf[bytesRead] = 0; + m_captured += buf; + } else if (bytesRead < 0) { + fd_blocked = (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR); + if (fd_blocked) + QThread::msleep(10); + } + } + while(fd_blocked || bytesRead == (bufSize-1)); + + secure_close(m_oldStdOut); + secure_close(m_oldStdErr); + secure_close(m_pipe[READ]); +#ifdef WINDOWS + secure_close(m_pipe[WRITE]); +#endif + m_capturing = false; + } + + static std::string GetCapture() { + QMutexLocker locker(&m_mutex); + return m_captured; + } + +private: + enum PIPES { READ, WRITE }; + + static int secure_dup(int src) { + int ret = -1; + bool fd_blocked = false; + do { + ret = dup(src); + fd_blocked = (errno == EINTR || errno == EBUSY); + if(fd_blocked) + QThread::msleep(10); + } while (ret < 0); + return ret; + } + + static void secure_pipe(int * pipes) { + int ret = -1; + bool fd_blocked = false; + do { +#ifdef WINDOWS + ret = pipe(pipes, 65536, O_BINARY); +#else + ret = pipe(pipes) == -1; +#endif + fd_blocked = (errno == EINTR || errno == EBUSY); + if (fd_blocked) + QThread::msleep(10); + } while (ret < 0); + } + + static void secure_dup2(int src, int dest) { + int ret = -1; + bool fd_blocked = false; + do { + ret = dup2(src,dest); + fd_blocked = (errno == EINTR || errno == EBUSY); + if (fd_blocked) + QThread::msleep(10); + } while (ret < 0); + } + + static void secure_close(int & fd) { + int ret = -1; + bool fd_blocked = false; + do { + ret = close(fd); + fd_blocked = (errno == EINTR); + if (fd_blocked) + QThread::msleep(10); + } while (ret < 0); + + fd = -1; + } + + static int m_pipe[2]; + static int m_oldStdOut; + static int m_oldStdErr; + static bool m_capturing; + static QMutex m_mutex; + static std::string m_captured; +}; + +// actually define vars. +int qtCapture::m_pipe[2]; +int qtCapture::m_oldStdOut; +int qtCapture::m_oldStdErr; +bool qtCapture::m_capturing; +QMutex qtCapture::m_mutex; +std::string qtCapture::m_captured; + namespace cucumber { namespace internal { From ff885700ba2224c18d0a458c793ef5ef4e529b8f Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Sat, 1 Apr 2017 16:40:30 +0200 Subject: [PATCH 11/15] convert qtCapture to normal class --- src/drivers/QtTestDriver.cpp | 51 ++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/src/drivers/QtTestDriver.cpp b/src/drivers/QtTestDriver.cpp index 369c95de..92efc7fd 100644 --- a/src/drivers/QtTestDriver.cpp +++ b/src/drivers/QtTestDriver.cpp @@ -1,5 +1,4 @@ #include "cucumber-cpp/internal/drivers/QtTestDriver.hpp" -#include "cucumber-cpp/internal/utils/qtCapture.hpp" #include @@ -38,7 +37,7 @@ class qtCapture { public: - static void Init() { + qtCapture(): m_capturing(false) { // make stdout & stderr streams unbuffered // so that we don't need to flush the streams // before capture and after capture @@ -47,8 +46,12 @@ class qtCapture { setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); } + ~qtCapture() { + if(m_capturing) + EndCapture(); + } - static void BeginCapture() { + void BeginCapture() { QMutexLocker locker(&m_mutex); if (m_capturing) return; @@ -64,12 +67,12 @@ class qtCapture { #endif } - static bool IsCapturing() { + bool IsCapturing() { QMutexLocker locker(&m_mutex); return m_capturing; } - static void EndCapture() { + void EndCapture() { QMutexLocker locker(&m_mutex); if (!m_capturing) return; @@ -111,7 +114,7 @@ class qtCapture { m_capturing = false; } - static std::string GetCapture() { + std::string GetCapture() { QMutexLocker locker(&m_mutex); return m_captured; } @@ -119,7 +122,7 @@ class qtCapture { private: enum PIPES { READ, WRITE }; - static int secure_dup(int src) { + int secure_dup(int src) { int ret = -1; bool fd_blocked = false; do { @@ -131,7 +134,7 @@ class qtCapture { return ret; } - static void secure_pipe(int * pipes) { + void secure_pipe(int * pipes) { int ret = -1; bool fd_blocked = false; do { @@ -146,7 +149,7 @@ class qtCapture { } while (ret < 0); } - static void secure_dup2(int src, int dest) { + void secure_dup2(int src, int dest) { int ret = -1; bool fd_blocked = false; do { @@ -157,7 +160,7 @@ class qtCapture { } while (ret < 0); } - static void secure_close(int & fd) { + void secure_close(int & fd) { int ret = -1; bool fd_blocked = false; do { @@ -170,37 +173,29 @@ class qtCapture { fd = -1; } - static int m_pipe[2]; - static int m_oldStdOut; - static int m_oldStdErr; - static bool m_capturing; - static QMutex m_mutex; - static std::string m_captured; + int m_pipe[2]; + int m_oldStdOut; + int m_oldStdErr; + bool m_capturing; + QMutex m_mutex; + std::string m_captured; }; -// actually define vars. -int qtCapture::m_pipe[2]; -int qtCapture::m_oldStdOut; -int qtCapture::m_oldStdErr; -bool qtCapture::m_capturing; -QMutex qtCapture::m_mutex; -std::string qtCapture::m_captured; - namespace cucumber { namespace internal { const InvokeResult QtTestStep::invokeStepBody() { QtTestObject testObject(this); - qtCapture::Init(); - qtCapture::BeginCapture(); + qtCapture capture; + capture.BeginCapture(); int returnValue = QTest::qExec(&testObject, 0, NULL); - qtCapture::EndCapture(); + capture.EndCapture(); if(returnValue == 0) return InvokeResult::success(); else - return InvokeResult::failure(qtCapture::GetCapture()); + return InvokeResult::failure(capture.GetCapture()); } } From 08584920f471c8ee5c632e8093263037769b27a9 Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Sat, 1 Apr 2017 16:42:49 +0200 Subject: [PATCH 12/15] revert change to add_test --- tests/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 90d44b9f..b8a04300 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,7 +3,7 @@ function(cuke_add_driver_test TEST_FILE) message(STATUS "Adding " ${TEST_NAME}) add_executable(${TEST_NAME} ${TEST_FILE}.cpp) target_link_libraries(${TEST_NAME} cucumber-cpp-nomain ${CUKE_EXTRA_LIBRARIES} ${ARGN}) - add_test(${TEST_NAME} ${TEST_NAME}) + add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}) endfunction() if(GMOCK_FOUND) From e2373da15a355e282c913aeecbc562d4b8088e69 Mon Sep 17 00:00:00 2001 From: Kamil Strzempowicz Date: Wed, 5 Apr 2017 21:37:37 +0200 Subject: [PATCH 13/15] Temporary file based solution --- src/drivers/QtTestDriver.cpp | 201 +++-------------------------------- 1 file changed, 15 insertions(+), 186 deletions(-) diff --git a/src/drivers/QtTestDriver.cpp b/src/drivers/QtTestDriver.cpp index 92efc7fd..59cf092c 100644 --- a/src/drivers/QtTestDriver.cpp +++ b/src/drivers/QtTestDriver.cpp @@ -1,201 +1,30 @@ #include "cucumber-cpp/internal/drivers/QtTestDriver.hpp" #include - -/* Based on Sir Digby Chicken post from: - * http://stackoverflow.com/questions/5419356/redirect-stdout-stderr-to-a-string - * who granted his work on BSD license - * adjusted for Qt by konserw - */ -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) -#define WINDOWS -#include -#include -#define popen _popen -#define pclose _pclose -#define stat _stat -#define dup _dup -#define dup2 _dup2 -#define fileno _fileno -#define close _close -#define pipe _pipe -#define read _read -#define eof _eof -#else -#include -#endif - -#include -#include - -#ifndef STD_OUT_FD -#define STD_OUT_FD (fileno(stdout)) -#endif -#ifndef STD_ERR_FD -#define STD_ERR_FD (fileno(stderr)) -#endif - -class qtCapture { -public: - qtCapture(): m_capturing(false) { - // make stdout & stderr streams unbuffered - // so that we don't need to flush the streams - // before capture and after capture - // (fflush can cause a deadlock if the stream is currently being - QMutexLocker locker(&m_mutex); - setvbuf(stdout, NULL, _IONBF, 0); - setvbuf(stderr, NULL, _IONBF, 0); - } - ~qtCapture() { - if(m_capturing) - EndCapture(); - } - - void BeginCapture() { - QMutexLocker locker(&m_mutex); - if (m_capturing) - return; - - secure_pipe(m_pipe); - m_oldStdOut = secure_dup(STD_OUT_FD); - m_oldStdErr = secure_dup(STD_ERR_FD); - secure_dup2(m_pipe[WRITE], STD_OUT_FD); - secure_dup2(m_pipe[WRITE], STD_ERR_FD); - m_capturing = true; -#ifndef WINDOWS - secure_close(m_pipe[WRITE]); -#endif - } - - bool IsCapturing() { - QMutexLocker locker(&m_mutex); - return m_capturing; - } - - void EndCapture() { - QMutexLocker locker(&m_mutex); - if (!m_capturing) - return; - - m_captured.clear(); - secure_dup2(m_oldStdOut, STD_OUT_FD); - secure_dup2(m_oldStdErr, STD_ERR_FD); - - const int bufSize = 1025; - char buf[bufSize]; - int bytesRead = 0; - bool fd_blocked(false); - do { - bytesRead = 0; - fd_blocked = false; -#ifdef WINDOWS - if (!eof(m_pipe[READ])) - bytesRead = read(m_pipe[READ], buf, bufSize-1); -#else - bytesRead = read(m_pipe[READ], buf, bufSize-1); -#endif - if (bytesRead > 0) { - buf[bytesRead] = 0; - m_captured += buf; - } else if (bytesRead < 0) { - fd_blocked = (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR); - if (fd_blocked) - QThread::msleep(10); - } - } - while(fd_blocked || bytesRead == (bufSize-1)); - - secure_close(m_oldStdOut); - secure_close(m_oldStdErr); - secure_close(m_pipe[READ]); -#ifdef WINDOWS - secure_close(m_pipe[WRITE]); -#endif - m_capturing = false; - } - - std::string GetCapture() { - QMutexLocker locker(&m_mutex); - return m_captured; - } - -private: - enum PIPES { READ, WRITE }; - - int secure_dup(int src) { - int ret = -1; - bool fd_blocked = false; - do { - ret = dup(src); - fd_blocked = (errno == EINTR || errno == EBUSY); - if(fd_blocked) - QThread::msleep(10); - } while (ret < 0); - return ret; - } - - void secure_pipe(int * pipes) { - int ret = -1; - bool fd_blocked = false; - do { -#ifdef WINDOWS - ret = pipe(pipes, 65536, O_BINARY); -#else - ret = pipe(pipes) == -1; -#endif - fd_blocked = (errno == EINTR || errno == EBUSY); - if (fd_blocked) - QThread::msleep(10); - } while (ret < 0); - } - - void secure_dup2(int src, int dest) { - int ret = -1; - bool fd_blocked = false; - do { - ret = dup2(src,dest); - fd_blocked = (errno == EINTR || errno == EBUSY); - if (fd_blocked) - QThread::msleep(10); - } while (ret < 0); - } - - void secure_close(int & fd) { - int ret = -1; - bool fd_blocked = false; - do { - ret = close(fd); - fd_blocked = (errno == EINTR); - if (fd_blocked) - QThread::msleep(10); - } while (ret < 0); - - fd = -1; - } - - int m_pipe[2]; - int m_oldStdOut; - int m_oldStdErr; - bool m_capturing; - QMutex m_mutex; - std::string m_captured; -}; +#include +#include namespace cucumber { namespace internal { const InvokeResult QtTestStep::invokeStepBody() { - QtTestObject testObject(this); - - qtCapture capture; - capture.BeginCapture(); - int returnValue = QTest::qExec(&testObject, 0, NULL); - capture.EndCapture(); + QTemporaryFile file; + QString fileName; + if(!file.open()) { + return InvokeResult::failure("Unable to open temporary file needed for this test"); + } + file.close(); + QtTestObject testObject(this); + int returnValue = QTest::qExec(&testObject, QStringList() << "test" << "-o" << file.fileName()); if(returnValue == 0) return InvokeResult::success(); else - return InvokeResult::failure(capture.GetCapture()); + { + file.open(); + QTextStream ts(&file); + return InvokeResult::failure(ts.readAll().toLocal8Bit()); + } } } From 288a6ad64d8218e704e88a776628b7bcf952d686 Mon Sep 17 00:00:00 2001 From: konserw Date: Thu, 13 Apr 2017 11:35:09 +0200 Subject: [PATCH 14/15] Second review --- src/drivers/QtTestDriver.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/drivers/QtTestDriver.cpp b/src/drivers/QtTestDriver.cpp index 59cf092c..92afa509 100644 --- a/src/drivers/QtTestDriver.cpp +++ b/src/drivers/QtTestDriver.cpp @@ -9,21 +9,18 @@ namespace internal { const InvokeResult QtTestStep::invokeStepBody() { QTemporaryFile file; - QString fileName; - if(!file.open()) { + if (!file.open()) { return InvokeResult::failure("Unable to open temporary file needed for this test"); } - file.close(); QtTestObject testObject(this); int returnValue = QTest::qExec(&testObject, QStringList() << "test" << "-o" << file.fileName()); - if(returnValue == 0) + if (returnValue == 0) return InvokeResult::success(); else { - file.open(); QTextStream ts(&file); - return InvokeResult::failure(ts.readAll().toLocal8Bit()); + return InvokeResult::failure(ts.readAll().toUtf8().data()); } } From 742b88de4ba0d0e1faeef20ac5caebab00d64c26 Mon Sep 17 00:00:00 2001 From: konserw Date: Thu, 13 Apr 2017 11:52:28 +0200 Subject: [PATCH 15/15] examples --- .../Calc/features/step_definitions/QtTestCalculatorSteps.cpp | 2 +- .../features/step_definitions/QtTestCalculatorQtSteps.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/Calc/features/step_definitions/QtTestCalculatorSteps.cpp b/examples/Calc/features/step_definitions/QtTestCalculatorSteps.cpp index 73de0f8b..3b6c9589 100644 --- a/examples/Calc/features/step_definitions/QtTestCalculatorSteps.cpp +++ b/examples/Calc/features/step_definitions/QtTestCalculatorSteps.cpp @@ -33,6 +33,6 @@ THEN("^the result should be (.*) on the screen$") { REGEX_PARAM(double, expected); ScenarioScope context; - QCOMPARE(expected, context->result); + QCOMPARE(context->result, expected); } diff --git a/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp b/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp index 95ba6515..25e34e50 100644 --- a/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp +++ b/examples/CalcQt/features/step_definitions/QtTestCalculatorQtSteps.cpp @@ -63,7 +63,7 @@ WHEN("^I press subtract") { THEN("^the display should be empty$") { cucumber::ScenarioScope calculator; - QCOMPARE(0, calculator->display().size()); + QCOMPARE(calculator->display().size(), 0); } THEN("^the display should show (.*)$") {