mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-07 12:12:39 +02:00
Merge pull request #14783 from NissimHadar/20816-installOnAndroid
Case 20816: install on android (Mac and Windows installers)
This commit is contained in:
commit
466da1bd05
38 changed files with 1803 additions and 1032 deletions
|
@ -82,7 +82,7 @@ if (ANDROID)
|
|||
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
|
||||
add_definitions(-DHIFI_ANDROID_APP=\"${HIFI_ANDROID_APP}\")
|
||||
else ()
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine)
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine Xml)
|
||||
endif ()
|
||||
|
||||
if (USE_GLES AND (NOT ANDROID))
|
||||
|
|
36
cmake/macros/FixupNitpick.cmake
Normal file
36
cmake/macros/FixupNitpick.cmake
Normal file
|
@ -0,0 +1,36 @@
|
|||
#
|
||||
# FixupNitpick.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Copyright 2019 High Fidelity, Inc.
|
||||
# Created by Nissim Hadar on January 14th, 2016
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
macro(fixup_nitpick)
|
||||
if (APPLE)
|
||||
string(REPLACE " " "\\ " ESCAPED_BUNDLE_NAME ${NITPICK_BUNDLE_NAME})
|
||||
string(REPLACE " " "\\ " ESCAPED_INSTALL_PATH ${NITPICK_INSTALL_DIR})
|
||||
set(_NITPICK_INSTALL_PATH "${ESCAPED_INSTALL_PATH}/${ESCAPED_BUNDLE_NAME}.app")
|
||||
|
||||
find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS "${QT_DIR}/bin" NO_DEFAULT_PATH)
|
||||
|
||||
if (NOT MACDEPLOYQT_COMMAND AND (PRODUCTION_BUILD OR PR_BUILD))
|
||||
message(FATAL_ERROR "Could not find macdeployqt at ${QT_DIR}/bin.\
|
||||
It is required to produce a relocatable nitpick application.\
|
||||
Check that the environment variable QT_DIR points to your Qt installation.\
|
||||
")
|
||||
endif ()
|
||||
|
||||
install(CODE "
|
||||
execute_process(COMMAND ${MACDEPLOYQT_COMMAND}\
|
||||
\${CMAKE_INSTALL_PREFIX}/${_NITPICK_INSTALL_PATH}/\
|
||||
-verbose=2 -qmldir=${CMAKE_SOURCE_DIR}/interface/resources/qml/\
|
||||
)"
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
|
||||
endif ()
|
||||
endmacro()
|
|
@ -77,6 +77,9 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
add_definitions(-DDEV_BUILD)
|
||||
endif ()
|
||||
|
||||
set(NITPICK_BUNDLE_NAME "nitpick")
|
||||
set(NITPICK_ICON_PREFIX "nitpick")
|
||||
|
||||
string(TIMESTAMP BUILD_TIME "%d/%m/%Y")
|
||||
|
||||
# if STABLE_BUILD is 1, PRODUCTION_BUILD must be 1 and
|
||||
|
@ -140,8 +143,9 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
|
||||
set(DMG_SUBFOLDER_ICON "${HF_CMAKE_DIR}/installer/install-folder.rsrc")
|
||||
|
||||
set(CONSOLE_INSTALL_DIR ${DMG_SUBFOLDER_NAME})
|
||||
set(CONSOLE_INSTALL_DIR ${DMG_SUBFOLDER_NAME})
|
||||
set(INTERFACE_INSTALL_DIR ${DMG_SUBFOLDER_NAME})
|
||||
set(NITPICK_INSTALL_DIR ${DMG_SUBFOLDER_NAME})
|
||||
|
||||
if (CLIENT_ONLY)
|
||||
set(CONSOLE_EXEC_NAME "Console.app")
|
||||
|
@ -159,11 +163,14 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
|
||||
set(INTERFACE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${INTERFACE_BUNDLE_NAME}.app")
|
||||
set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.icns")
|
||||
set(NITPICK_ICON_FILENAME "${NITPICK_ICON_PREFIX}.icns")
|
||||
else ()
|
||||
if (WIN32)
|
||||
set(CONSOLE_INSTALL_DIR "server-console")
|
||||
set(NITPICK_INSTALL_DIR "nitpick")
|
||||
else ()
|
||||
set(CONSOLE_INSTALL_DIR ".")
|
||||
set(NITPICK_INSTALL_DIR ".")
|
||||
endif ()
|
||||
|
||||
set(COMPONENT_INSTALL_DIR ".")
|
||||
|
@ -173,6 +180,7 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
if (WIN32)
|
||||
set(INTERFACE_EXEC_PREFIX "interface")
|
||||
set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.ico")
|
||||
set(NITPICK_ICON_FILENAME "${NITPICK_ICON_PREFIX}.ico")
|
||||
|
||||
set(CONSOLE_EXEC_NAME "server-console.exe")
|
||||
|
||||
|
|
|
@ -1,63 +1,163 @@
|
|||
set (TARGET_NAME nitpick)
|
||||
set(TARGET_NAME nitpick)
|
||||
project(${TARGET_NAME})
|
||||
|
||||
# Automatically run UIC and MOC. This replaces the older WRAP macros
|
||||
SET (CMAKE_AUTOUIC ON)
|
||||
SET (CMAKE_AUTOMOC ON)
|
||||
set(CUSTOM_NITPICK_QRC_PATHS "")
|
||||
|
||||
setup_hifi_project (Core Widgets Network Xml)
|
||||
link_hifi_libraries ()
|
||||
find_npm()
|
||||
|
||||
# FIX: Qt was built with -reduce-relocations
|
||||
if (Qt5_POSITION_INDEPENDENT_CODE)
|
||||
SET (CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc)
|
||||
set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/compiledResources/resources.rcc)
|
||||
generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_NITPICK_QRC_PATHS} GLOBS *)
|
||||
|
||||
# Qt includes
|
||||
include_directories (${CMAKE_CURRENT_SOURCE_DIR})
|
||||
include_directories (${Qt5Core_INCLUDE_DIRS})
|
||||
include_directories (${Qt5Widgets_INCLUDE_DIRS})
|
||||
add_custom_command(
|
||||
OUTPUT ${RESOURCES_RCC}
|
||||
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
|
||||
COMMAND "${QT_DIR}/bin/rcc"
|
||||
ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC}
|
||||
)
|
||||
|
||||
set (QT_LIBRARIES Qt5::Core Qt5::Widgets QT::Gui Qt5::Xml)
|
||||
# grab the implementation and header files from src dirs
|
||||
file(GLOB_RECURSE NITPICK_SRCS "src/*.cpp" "src/*.h")
|
||||
GroupSources("src")
|
||||
list(APPEND NITPICK_SRCS ${RESOURCES_RCC})
|
||||
|
||||
if (WIN32)
|
||||
# Do not show Console
|
||||
set_property (TARGET nitpick PROPERTY WIN32_EXECUTABLE true)
|
||||
endif()
|
||||
find_package(Qt5 COMPONENTS Widgets)
|
||||
|
||||
target_zlib()
|
||||
add_dependency_external_projects (quazip)
|
||||
find_package (QuaZip REQUIRED)
|
||||
target_include_directories( ${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES})
|
||||
|
||||
package_libraries_for_deployment()
|
||||
# grab the ui files in ui
|
||||
file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
|
||||
source_group("UI Files" FILES ${QT_UI_FILES})
|
||||
|
||||
if (WIN32)
|
||||
add_paths_to_fixup_libs (${QUAZIP_DLL_PATH})
|
||||
# have qt5 wrap them and generate the appropriate header files
|
||||
qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}")
|
||||
|
||||
find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH)
|
||||
|
||||
if (NOT WINDEPLOYQT_COMMAND)
|
||||
message(FATAL_ERROR "Could not find windeployqt at ${QT_DIR}/bin. windeployqt is required.")
|
||||
# add them to the nitpick source files
|
||||
set(NITPICK_SRCS ${NITPICK_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
|
||||
|
||||
if (APPLE)
|
||||
# configure CMake to use a custom Info.plist
|
||||
set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in)
|
||||
|
||||
if (PRODUCTION_BUILD)
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.nitpick)
|
||||
else ()
|
||||
if (DEV_BUILD)
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.nitpick-dev)
|
||||
elseif (PR_BUILD)
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.nitpick-pr)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# add a post-build command to call windeployqt to copy Qt plugins
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> \"$<TARGET_FILE:${TARGET_NAME}>\""
|
||||
)
|
||||
|
||||
# add a custom command to copy the empty Apps/Data High Fidelity folder (i.e. - a valid folder with no entities)
|
||||
# this also copied to the containing folder, to facilitate running from Visual Studio
|
||||
# set how the icon shows up in the Info.plist file
|
||||
set(MACOSX_BUNDLE_ICON_FILE "${NITPICK_ICON_FILENAME}")
|
||||
|
||||
# set where in the bundle to put the resources file
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/icon/${NITPICK_ICON_FILENAME} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
|
||||
# append the discovered resources to our list of nitpick sources
|
||||
list(APPEND NITPICK_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/icon/${NITPICK_ICON_FILENAME})
|
||||
endif()
|
||||
|
||||
# create the executable, make it a bundle on OS X
|
||||
if (APPLE)
|
||||
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${NITPICK_SRCS} ${QM})
|
||||
|
||||
# make sure the output name for the .app bundle is correct
|
||||
# Fix up the rpath so macdeployqt works
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks")
|
||||
elseif (WIN32)
|
||||
# configure an rc file for the chosen icon
|
||||
set(CONFIGURE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/icon/${NITPICK_ICON_FILENAME}")
|
||||
set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc")
|
||||
configure_file("${HF_CMAKE_DIR}/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT})
|
||||
|
||||
set(APP_FULL_NAME "High Fidelity Nitpick")
|
||||
set(CONFIGURE_VERSION_INFO_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.rc")
|
||||
configure_file("${HF_CMAKE_DIR}/templates/VersionInfo.rc.in" ${CONFIGURE_VERSION_INFO_RC_OUTPUT})
|
||||
|
||||
# add an executable that also has the icon itself and the configured rc file as resources
|
||||
add_executable(${TARGET_NAME} WIN32 ${NITPICK_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT} ${CONFIGURE_VERSION_INFO_RC_OUTPUT})
|
||||
else ()
|
||||
add_executable(${TARGET_NAME} ${NITPICK_SRCS} ${QM})
|
||||
endif ()
|
||||
|
||||
add_dependencies(${TARGET_NAME} resources)
|
||||
|
||||
# disable /OPT:REF and /OPT:ICF for the Debug builds
|
||||
# This will prevent the following linker warnings
|
||||
# LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification
|
||||
if (WIN32)
|
||||
set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG "/OPT:NOREF /OPT:NOICF")
|
||||
endif()
|
||||
|
||||
link_hifi_libraries(entities-renderer)
|
||||
|
||||
# perform standard include and linking for found externals
|
||||
foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
|
||||
if (${${EXTERNAL}_UPPERCASE}_REQUIRED)
|
||||
find_package(${EXTERNAL} REQUIRED)
|
||||
else ()
|
||||
find_package(${EXTERNAL})
|
||||
endif ()
|
||||
|
||||
if (${${EXTERNAL}_UPPERCASE}_FOUND AND NOT DISABLE_${${EXTERNAL}_UPPERCASE})
|
||||
add_definitions(-DHAVE_${${EXTERNAL}_UPPERCASE})
|
||||
|
||||
# include the library directories (ignoring warnings)
|
||||
if (NOT ${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS)
|
||||
set(${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIR})
|
||||
endif ()
|
||||
|
||||
include_directories(SYSTEM ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS})
|
||||
|
||||
# perform the system include hack for OS X to ignore warnings
|
||||
if (APPLE)
|
||||
foreach(EXTERNAL_INCLUDE_DIR ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS})
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${EXTERNAL_INCLUDE_DIR}")
|
||||
endforeach()
|
||||
endif ()
|
||||
|
||||
if (NOT ${${EXTERNAL}_UPPERCASE}_LIBRARIES)
|
||||
set(${${EXTERNAL}_UPPERCASE}_LIBRARIES ${${${EXTERNAL}_UPPERCASE}_LIBRARY})
|
||||
endif ()
|
||||
|
||||
if (NOT APPLE OR NOT ${${EXTERNAL}_UPPERCASE} MATCHES "SIXENSE")
|
||||
target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES})
|
||||
elseif (APPLE AND NOT INSTALLER_BUILD)
|
||||
add_definitions(-DSIXENSE_LIB_FILENAME=\"${${${EXTERNAL}_UPPERCASE}_LIBRARY_RELEASE}\")
|
||||
endif ()
|
||||
endif ()
|
||||
endforeach()
|
||||
|
||||
# include headers for nitpick and NitpickConfig.
|
||||
include_directories("${PROJECT_SOURCE_DIR}/src")
|
||||
|
||||
if (UNIX AND NOT ANDROID)
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
# Linux
|
||||
target_link_libraries(${TARGET_NAME} pthread atomic)
|
||||
else ()
|
||||
# OSX
|
||||
target_link_libraries(${TARGET_NAME} pthread)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
# add a custom command to copy the empty AppData High Fidelity folder (i.e. - a valid folder with no entities)
|
||||
if (WIN32)
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$<TARGET_FILE_DIR:${TARGET_NAME}>/AppDataHighFidelity"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "AppDataHighFidelity"
|
||||
)
|
||||
|
||||
|
||||
if (RELEASE_TYPE STREQUAL "DEV")
|
||||
# This to enable running from the IDE
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "AppDataHighFidelity"
|
||||
)
|
||||
endif ()
|
||||
|
||||
# add a custom command to copy the SSL DLLs
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
|
@ -65,11 +165,40 @@ if (WIN32)
|
|||
COMMAND "${CMAKE_COMMAND}" -E copy_directory "$ENV{VCPKG_ROOT}/installed/x64-windows/bin" "$<TARGET_FILE_DIR:${TARGET_NAME}>"
|
||||
)
|
||||
elseif (APPLE)
|
||||
# add a custom command to copy the empty Apps/Data High Fidelity folder (i.e. - a valid folder with no entities)
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$<TARGET_FILE_DIR:${TARGET_NAME}>/AppDataHighFidelity"
|
||||
)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
# setup install of OS X nitpick bundle
|
||||
install(TARGETS ${TARGET_NAME}
|
||||
BUNDLE DESTINATION ${NITPICK_INSTALL_DIR}
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
|
||||
# call the fixup_nitpick macro to add required bundling commands for installation
|
||||
fixup_nitpick()
|
||||
elseif (WIN32)
|
||||
# link target to external libraries
|
||||
# setup install of executable and things copied by fixup/windeployqt
|
||||
install(
|
||||
DIRECTORY "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
|
||||
DESTINATION ${NITPICK_INSTALL_DIR}
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
PATTERN "*.pdb" EXCLUDE
|
||||
PATTERN "*.lib" EXCLUDE
|
||||
PATTERN "*.exp" EXCLUDE
|
||||
)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
set(EXTRA_DEPLOY_OPTIONS "--qmldir \"${PROJECT_SOURCE_DIR}/resources/qml\"")
|
||||
|
||||
set(TARGET_INSTALL_DIR ${NITPICK_INSTALL_DIR})
|
||||
set(TARGET_INSTALL_COMPONENT ${CLIENT_COMPONENT})
|
||||
|
||||
package_libraries_for_deployment()
|
||||
endif()
|
||||
|
|
|
@ -6,89 +6,61 @@ Nitpick is a stand alone application that provides a mechanism for regression te
|
|||
* The result, if any test failed, is a zipped folder describing the failure.
|
||||
|
||||
Nitpick has 5 functions, separated into separate tabs:
|
||||
|
||||
1. Creating tests, MD files and recursive scripts
|
||||
1. Windows task bar utility (Windows only)
|
||||
1. Running tests
|
||||
1. Evaluating the results of running tests
|
||||
1. Web interface
|
||||
|
||||
## Build (for developers)
|
||||
Nitpick is built as part of the High Fidelity build.
|
||||
XXXX refers to the version number - replace as necessary. For example, replace with 3.2.11
|
||||
### Creating installers
|
||||
#### Windows
|
||||
1. Perform Release build
|
||||
1. Verify that 7Zip is installed.
|
||||
1. cd to the `build\tools\nitpick\Release` directory
|
||||
1. Delete any existing installers (named nitpick-installer-###.exe)
|
||||
1. Select all, right-click and select 7-Zip->Add to archive...
|
||||
1. Set Archive format to 7z
|
||||
1. Check "Create SFX archive
|
||||
1. Enter installer name (i.e. `nitpick-installer-vXXXX.exe`)
|
||||
1. Click "OK"
|
||||
1. Copy created installer to https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe: aws s3 cp nitpick-installer-vXXXX.exe s3://hifi-qa/nitpick/Mac/nitpick-installer-vXXXX.exe
|
||||
#### Mac
|
||||
These steps assume the hifi repository has been cloned to `~/hifi`.
|
||||
1. (first time) Install brew
|
||||
In a terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)`
|
||||
1. (First time) install create-dmg:
|
||||
In a terminal: `brew install create-dmg`
|
||||
1. Perform Release build
|
||||
1. In a terminal: cd to the `build/tools/nitpick/Release` folder
|
||||
1. Copy the quazip dynamic library (note final period):
|
||||
In a terminal: `cp ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib .`
|
||||
1. Change the loader instruction to find the dynamic library locally
|
||||
In a terminal: `install_name_tool -change ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib libquazip5.1.dylib nitpick`
|
||||
1. Delete any existing disk images. In a terminal: `rm *.dmg`
|
||||
1. Create installer (note final period).In a terminal: `create-dmg --volname nitpick-installer-vXXXX nitpick-installer-vXXXX.dmg .`
|
||||
Make sure to wait for completion.
|
||||
1. Copy created installer to AWS: `~/Library/Python/3.7/bin/aws s3 cp nitpick-installer-vXXXX.dmg s3://hifi-qa/nitpick/Mac/nitpick-installer-vXXXX.dmg`
|
||||
### Installation
|
||||
#### Windows
|
||||
1. (First time) download and install vc_redist.x64.exe (available at https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe)
|
||||
## Installation
|
||||
`nitpick` is packaged with High Fidelity PR and Development builds.
|
||||
### Windows
|
||||
1. (First time) download and install Python 3 from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/python-3.7.0-amd64.exe (also located at https://www.python.org/downloads/)
|
||||
1. Click the "add python to path" checkbox on the python installer
|
||||
1. After installation - create an environment variable called PYTHON_PATH and set it to the folder containing the Python executable.
|
||||
1. (First time) download and install AWS CLI from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/AWSCLI64PY3.msi (also available at https://aws.amazon.com/cli/
|
||||
1. Open a new command prompt and run `aws configure`
|
||||
1. Open a new command prompt and run
|
||||
`aws configure`
|
||||
1. Enter the AWS account number
|
||||
1. Enter the secret key
|
||||
1. Leave region name and ouput format as default [None]
|
||||
1. Install the latest release of Boto3 via pip: `pip install boto3`
|
||||
1. Install the latest release of Boto3 via pip:
|
||||
`pip install boto3`
|
||||
|
||||
1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe>)
|
||||
1. Double click on the installer and install to a convenient location
|
||||

|
||||
|
||||
1. __To run nitpick, double click **nitpick.exe**__
|
||||
#### Mac
|
||||
1. (First time) Download adb (Android Debug Bridge) from *https://dl.google.com/android/repository/platform-tools-latest-windows.zip*
|
||||
1. Copy the downloaded file to (for example) **C:\adb** and extract in place.
|
||||
Verify you see *adb.exe* in **C:\adb\platform-tools\\**.
|
||||
1. Create an environment variable named ADB_PATH and set its value to the installation location (e.g. **C:\adb**)
|
||||
### Mac
|
||||
1. (first time) Install brew
|
||||
In a terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)`
|
||||
In a terminal:
|
||||
`/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"`
|
||||
Note that you will need to press RETURN again, and will then be asked for your password.
|
||||
1. (First time) install Qt:
|
||||
In a terminal: `brew install qt`
|
||||
1. (First time) install Python from https://www.python.org/downloads/release/python-370/ (**macOS 64-bit installer** or **macOS 64-bit/32-bit installer**)
|
||||
1. After installation - In a terminal: run `open "/Applications/Python 3.6/Install Certificates.command"`. This is needed because the Mac Python supplied no longer links with the deprecated Apple-supplied system OpenSSL libraries but rather supplies a private copy of OpenSSL 1.0.2 which does not automatically access the system default root certificates.
|
||||
In a terminal:
|
||||
`brew install qt`
|
||||
1. (First time) install Python from https://www.python.org/downloads/release/python-370/ (*macOS 64-bit installer* or *macOS 64-bit/32-bit installer*)
|
||||
1. After installation - In a terminal: run
|
||||
`open "/Applications/Python 3.7/Install Certificates.command"`.
|
||||
This is needed because the Mac Python supplied no longer links with the deprecated Apple-supplied system OpenSSL libraries but rather supplies a private copy of OpenSSL 1.0.2 which does not automatically access the system default root certificates.
|
||||
1. Verify that `/usr/local/bin/python3` exists.
|
||||
1. (First time - AWS interface) Install pip with the script provided by the Python Packaging Authority:
|
||||
In a terminal: `curl -O https://bootstrap.pypa.io/get-pip.py`
|
||||
In a terminal: `python3 get-pip.py --user`
|
||||
In a terminal:
|
||||
`curl -O https://bootstrap.pypa.io/get-pip.py`
|
||||
In a terminal:
|
||||
`python3 get-pip.py --user`
|
||||
1. Use pip to install the AWS CLI.
|
||||
`pip3 install awscli --upgrade --user`
|
||||
`pip3 install awscli --upgrade --user`
|
||||
This will install aws in your user. For user XXX, aws will be located in ~/Library/Python/3.7/bin
|
||||
1. Open a new command prompt and run `~/Library/Python/3.7/bin/aws configure`
|
||||
1. Open a new command prompt and run
|
||||
`~/Library/Python/3.7/bin/aws configure`
|
||||
1. Enter the AWS account number
|
||||
1. Enter the secret key
|
||||
1. Leave region name and ouput format as default [None]
|
||||
1. Install the latest release of Boto3 via pip: pip3 install boto3
|
||||
1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Mac/nitpick-installer-vXXXX.dmg>).
|
||||
1. Double-click on the downloaded image to mount it
|
||||
1. Create a folder for the nitpick files (e.g. ~/nitpick)
|
||||
If this folder exists then delete all it's contents.
|
||||
1. Copy the downloaded files to the folder
|
||||
In a terminal:
|
||||
`cd ~/nitpick`
|
||||
`cp -r /Volumes/nitpick-installer-vXXXX/* .`
|
||||
|
||||
1. __To run nitpick, cd to the folder that you copied to and run `./nitpick`__
|
||||
1. Install the latest release of Boto3 via pip: pip3 install boto3
|
||||
1. (First time)Install adb (the Android Debug Bridge) - in a terminal:
|
||||
`brew cask install android-platform-tools`
|
||||
# Usage
|
||||
## Create
|
||||

|
||||
|
@ -167,7 +139,7 @@ nitpick.runRecursive();
|
|||
In this case all recursive scripts, from the selected folder down, are created.
|
||||
|
||||
Running this function in the tests root folder will create (or update) all the recursive scripts.
|
||||
## Windows
|
||||
## Windows (only)
|
||||

|
||||
|
||||
This tab is Windows-specific. It provides buttons to hide and show the task bar.
|
||||
|
|
BIN
tools/nitpick/compiledResources/resources.rcc
Normal file
BIN
tools/nitpick/compiledResources/resources.rcc
Normal file
Binary file not shown.
BIN
tools/nitpick/icon/nitpick.icns
Normal file
BIN
tools/nitpick/icon/nitpick.icns
Normal file
Binary file not shown.
BIN
tools/nitpick/icon/nitpick.ico
Normal file
BIN
tools/nitpick/icon/nitpick.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 51 KiB |
|
@ -16,7 +16,7 @@
|
|||
#include <QObject>
|
||||
#include <QTextStream>
|
||||
|
||||
#include "ui/BusyWindow.h"
|
||||
#include "BusyWindow.h"
|
||||
|
||||
#include "PythonInterface.h"
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
#include "ui_MismatchWindow.h"
|
||||
|
||||
#include "../common.h"
|
||||
#include "common.h"
|
||||
|
||||
class MismatchWindow : public QDialog, public Ui::MismatchWindow {
|
||||
Q_OBJECT
|
|
@ -26,7 +26,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) {
|
|||
|
||||
_signalMapper = new QSignalMapper();
|
||||
|
||||
connect(_ui.actionClose, &QAction::triggered, this, &Nitpick::on_closeButton_clicked);
|
||||
connect(_ui.actionClose, &QAction::triggered, this, &Nitpick::on_closePushbutton_clicked);
|
||||
connect(_ui.actionAbout, &QAction::triggered, this, &Nitpick::about);
|
||||
connect(_ui.actionContent, &QAction::triggered, this, &Nitpick::content);
|
||||
|
||||
|
@ -35,10 +35,12 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) {
|
|||
_ui.tabWidget->removeTab(1);
|
||||
#endif
|
||||
|
||||
_ui.statusLabel->setText("");
|
||||
_ui.plainTextEdit->setReadOnly(true);
|
||||
_ui.statusLabelOnDesktop->setText("");
|
||||
_ui.statusLabelOnMobile->setText("");
|
||||
|
||||
_ui.plainTextEdit->setReadOnly(true);
|
||||
|
||||
setWindowTitle("Nitpick - v1.3.2");
|
||||
setWindowTitle("Nitpick - v2.0.1");
|
||||
}
|
||||
|
||||
Nitpick::~Nitpick() {
|
||||
|
@ -48,8 +50,12 @@ Nitpick::~Nitpick() {
|
|||
delete _test;
|
||||
}
|
||||
|
||||
if (_testRunner) {
|
||||
delete _testRunner;
|
||||
if (_testRunnerDesktop) {
|
||||
delete _testRunnerDesktop;
|
||||
}
|
||||
|
||||
if (_testRunnerMobile) {
|
||||
delete _testRunnerMobile;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,10 +86,38 @@ void Nitpick::setup() {
|
|||
timeEdits.emplace_back(_ui.timeEdit3);
|
||||
timeEdits.emplace_back(_ui.timeEdit4);
|
||||
|
||||
if (_testRunner) {
|
||||
delete _testRunner;
|
||||
// Create the two test runners
|
||||
if (_testRunnerDesktop) {
|
||||
delete _testRunnerDesktop;
|
||||
}
|
||||
_testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel, _ui.checkBoxServerless, _ui.checkBoxRunLatest, _ui.urlLineEdit, _ui.runNowButton);
|
||||
_testRunnerDesktop = new TestRunnerDesktop(
|
||||
dayCheckboxes,
|
||||
timeEditCheckboxes,
|
||||
timeEdits,
|
||||
_ui.workingFolderRunOnDesktopLabel,
|
||||
_ui.checkBoxServerless,
|
||||
_ui.runLatestOnDesktopCheckBox,
|
||||
_ui.urlOnDesktopLineEdit,
|
||||
_ui.runNowPushbutton,
|
||||
_ui.statusLabelOnDesktop
|
||||
);
|
||||
|
||||
if (_testRunnerMobile) {
|
||||
delete _testRunnerMobile;
|
||||
}
|
||||
_testRunnerMobile = new TestRunnerMobile(
|
||||
_ui.workingFolderRunOnMobileLabel,
|
||||
_ui.connectDevicePushbutton,
|
||||
_ui.pullFolderPushbutton,
|
||||
_ui.detectedDeviceLabel,
|
||||
_ui.folderLineEdit,
|
||||
_ui.downloadAPKPushbutton,
|
||||
_ui.installAPKPushbutton,
|
||||
_ui.runInterfacePushbutton,
|
||||
_ui.runLatestOnMobileCheckBox,
|
||||
_ui.urlOnMobileLineEdit,
|
||||
_ui.statusLabelOnMobile
|
||||
);
|
||||
}
|
||||
|
||||
void Nitpick::startTestsEvaluation(const bool isRunningFromCommandLine,
|
||||
|
@ -98,9 +132,9 @@ void Nitpick::startTestsEvaluation(const bool isRunningFromCommandLine,
|
|||
void Nitpick::on_tabWidget_currentChanged(int index) {
|
||||
// Enable the GitHub edit boxes as required
|
||||
#ifdef Q_OS_WIN
|
||||
if (index == 0 || index == 2 || index == 3) {
|
||||
if (index == 0 || index == 2 || index == 3 || index == 4) {
|
||||
#else
|
||||
if (index == 0 || index == 1 || index == 2) {
|
||||
if (index == 0 || index == 1 || index == 2 || index == 3) {
|
||||
#endif
|
||||
_ui.userLineEdit->setDisabled(false);
|
||||
_ui.branchLineEdit->setDisabled(false);
|
||||
|
@ -110,43 +144,43 @@ void Nitpick::on_tabWidget_currentChanged(int index) {
|
|||
}
|
||||
}
|
||||
|
||||
void Nitpick::on_evaluateTestsButton_clicked() {
|
||||
void Nitpick::on_evaluateTestsPushbutton_clicked() {
|
||||
_test->startTestsEvaluation(false, false);
|
||||
}
|
||||
|
||||
void Nitpick::on_createRecursiveScriptButton_clicked() {
|
||||
void Nitpick::on_createRecursiveScriptPushbutton_clicked() {
|
||||
_test->createRecursiveScript();
|
||||
}
|
||||
|
||||
void Nitpick::on_createAllRecursiveScriptsButton_clicked() {
|
||||
void Nitpick::on_createAllRecursiveScriptsPushbutton_clicked() {
|
||||
_test->createAllRecursiveScripts();
|
||||
}
|
||||
|
||||
void Nitpick::on_createTestsButton_clicked() {
|
||||
void Nitpick::on_createTestsPushbutton_clicked() {
|
||||
_test->createTests();
|
||||
}
|
||||
|
||||
void Nitpick::on_createMDFileButton_clicked() {
|
||||
void Nitpick::on_createMDFilePushbutton_clicked() {
|
||||
_test->createMDFile();
|
||||
}
|
||||
|
||||
void Nitpick::on_createAllMDFilesButton_clicked() {
|
||||
void Nitpick::on_createAllMDFilesPushbutton_clicked() {
|
||||
_test->createAllMDFiles();
|
||||
}
|
||||
|
||||
void Nitpick::on_createTestAutoScriptButton_clicked() {
|
||||
void Nitpick::on_createTestAutoScriptPushbutton_clicked() {
|
||||
_test->createTestAutoScript();
|
||||
}
|
||||
|
||||
void Nitpick::on_createAllTestAutoScriptsButton_clicked() {
|
||||
void Nitpick::on_createAllTestAutoScriptsPushbutton_clicked() {
|
||||
_test->createAllTestAutoScripts();
|
||||
}
|
||||
|
||||
void Nitpick::on_createTestsOutlineButton_clicked() {
|
||||
void Nitpick::on_createTestsOutlinePushbutton_clicked() {
|
||||
_test->createTestsOutline();
|
||||
}
|
||||
|
||||
void Nitpick::on_createTestRailTestCasesButton_clicked() {
|
||||
void Nitpick::on_createTestRailTestCasesPushbutton_clicked() {
|
||||
_test->createTestRailTestCases();
|
||||
}
|
||||
|
||||
|
@ -154,29 +188,29 @@ void Nitpick::on_createTestRailRunButton_clicked() {
|
|||
_test->createTestRailRun();
|
||||
}
|
||||
|
||||
void Nitpick::on_setWorkingFolderButton_clicked() {
|
||||
_testRunner->setWorkingFolder();
|
||||
void Nitpick::on_setWorkingFolderRunOnDesktopPushbutton_clicked() {
|
||||
_testRunnerDesktop->setWorkingFolderAndEnableControls();
|
||||
}
|
||||
|
||||
void Nitpick::enableRunTabControls() {
|
||||
_ui.runNowButton->setEnabled(true);
|
||||
_ui.runNowPushbutton->setEnabled(true);
|
||||
_ui.daysGroupBox->setEnabled(true);
|
||||
_ui.timesGroupBox->setEnabled(true);
|
||||
}
|
||||
|
||||
void Nitpick::on_runNowButton_clicked() {
|
||||
_testRunner->run();
|
||||
void Nitpick::on_runNowPushbutton_clicked() {
|
||||
_testRunnerDesktop->run();
|
||||
}
|
||||
|
||||
void Nitpick::on_checkBoxRunLatest_clicked() {
|
||||
_ui.urlLineEdit->setEnabled(!_ui.checkBoxRunLatest->isChecked());
|
||||
void Nitpick::on_runLatestOnDesktopCheckBox_clicked() {
|
||||
_ui.urlOnDesktopLineEdit->setEnabled(!_ui.runLatestOnDesktopCheckBox->isChecked());
|
||||
}
|
||||
|
||||
void Nitpick::automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures) {
|
||||
_testRunner->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures);
|
||||
_testRunnerDesktop->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures);
|
||||
}
|
||||
|
||||
void Nitpick::on_updateTestRailRunResultsButton_clicked() {
|
||||
void Nitpick::on_updateTestRailRunResultsPushbutton_clicked() {
|
||||
_test->updateTestRailRunResult();
|
||||
}
|
||||
|
||||
|
@ -184,7 +218,7 @@ void Nitpick::on_updateTestRailRunResultsButton_clicked() {
|
|||
// if (uState & ABS_AUTOHIDE) on_showTaskbarButton_clicked();
|
||||
// else on_hideTaskbarButton_clicked();
|
||||
//
|
||||
void Nitpick::on_hideTaskbarButton_clicked() {
|
||||
void Nitpick::on_hideTaskbarPushbutton_clicked() {
|
||||
#ifdef Q_OS_WIN
|
||||
APPBARDATA abd = { sizeof abd };
|
||||
UINT uState = (UINT)SHAppBarMessage(ABM_GETSTATE, &abd);
|
||||
|
@ -194,7 +228,7 @@ void Nitpick::on_hideTaskbarButton_clicked() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void Nitpick::on_showTaskbarButton_clicked() {
|
||||
void Nitpick::on_showTaskbarPushbutton_clicked() {
|
||||
#ifdef Q_OS_WIN
|
||||
APPBARDATA abd = { sizeof abd };
|
||||
UINT uState = (UINT)SHAppBarMessage(ABM_GETSTATE, &abd);
|
||||
|
@ -204,7 +238,7 @@ void Nitpick::on_showTaskbarButton_clicked() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void Nitpick::on_closeButton_clicked() {
|
||||
void Nitpick::on_closePushbutton_clicked() {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
@ -216,7 +250,7 @@ void Nitpick::on_createXMLScriptRadioButton_clicked() {
|
|||
_test->setTestRailCreateMode(XML);
|
||||
}
|
||||
|
||||
void Nitpick::on_createWebPagePushButton_clicked() {
|
||||
void Nitpick::on_createWebPagePushbutton_clicked() {
|
||||
_test->createWebPage(_ui.updateAWSCheckBox, _ui.awsURLLineEdit);
|
||||
}
|
||||
|
||||
|
@ -273,9 +307,13 @@ void Nitpick::saveFile(int index) {
|
|||
disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int)));
|
||||
if (_caller == _test) {
|
||||
_test->finishTestsEvaluation();
|
||||
} else if (_caller == _testRunner) {
|
||||
_testRunner->downloadComplete();
|
||||
} else if (_caller == _testRunnerDesktop) {
|
||||
_testRunnerDesktop->downloadComplete();
|
||||
} else if (_caller == _testRunnerMobile) {
|
||||
_testRunnerMobile->downloadComplete();
|
||||
}
|
||||
|
||||
_ui.progressBar->setVisible(false);
|
||||
} else {
|
||||
_ui.progressBar->setValue(_numberOfFilesDownloaded);
|
||||
}
|
||||
|
@ -305,10 +343,35 @@ QString Nitpick::getSelectedBranch() {
|
|||
return _ui.branchLineEdit->text();
|
||||
}
|
||||
|
||||
void Nitpick::updateStatusLabel(const QString& status) {
|
||||
_ui.statusLabel->setText(status);
|
||||
}
|
||||
|
||||
void Nitpick::appendLogWindow(const QString& message) {
|
||||
_ui.plainTextEdit->appendPlainText(message);
|
||||
}
|
||||
|
||||
// Test on Mobile
|
||||
void Nitpick::on_setWorkingFolderRunOnMobilePushbutton_clicked() {
|
||||
_testRunnerMobile->setWorkingFolderAndEnableControls();
|
||||
}
|
||||
|
||||
void Nitpick::on_connectDevicePushbutton_clicked() {
|
||||
_testRunnerMobile->connectDevice();
|
||||
}
|
||||
|
||||
void Nitpick::on_runLatestOnMobileCheckBox_clicked() {
|
||||
_ui.urlOnMobileLineEdit->setEnabled(!_ui.runLatestOnMobileCheckBox->isChecked());
|
||||
}
|
||||
|
||||
void Nitpick::on_downloadAPKPushbutton_clicked() {
|
||||
_testRunnerMobile->downloadAPK();
|
||||
}
|
||||
|
||||
void Nitpick::on_installAPKPushbutton_clicked() {
|
||||
_testRunnerMobile->installAPK();
|
||||
}
|
||||
|
||||
void Nitpick::on_runInterfacePushbutton_clicked() {
|
||||
_testRunnerMobile->runInterface();
|
||||
}
|
||||
|
||||
void Nitpick::on_pullFolderPushbutton_clicked() {
|
||||
_testRunnerMobile->pullFolder();
|
||||
}
|
|
@ -15,11 +15,13 @@
|
|||
#include <QTextEdit>
|
||||
#include "ui_Nitpick.h"
|
||||
|
||||
#include "../Downloader.h"
|
||||
#include "../Test.h"
|
||||
#include "Downloader.h"
|
||||
#include "Test.h"
|
||||
|
||||
#include "../TestRunner.h"
|
||||
#include "../AWSInterface.h"
|
||||
#include "TestRunnerDesktop.h"
|
||||
#include "TestRunnerMobile.h"
|
||||
|
||||
#include "AWSInterface.h"
|
||||
|
||||
class Nitpick : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
@ -49,54 +51,66 @@ public:
|
|||
|
||||
void enableRunTabControls();
|
||||
|
||||
void updateStatusLabel(const QString& status);
|
||||
void appendLogWindow(const QString& message);
|
||||
|
||||
private slots:
|
||||
void on_closePushbutton_clicked();
|
||||
|
||||
void on_tabWidget_currentChanged(int index);
|
||||
|
||||
void on_evaluateTestsButton_clicked();
|
||||
void on_createRecursiveScriptButton_clicked();
|
||||
void on_createAllRecursiveScriptsButton_clicked();
|
||||
void on_createTestsButton_clicked();
|
||||
void on_evaluateTestsPushbutton_clicked();
|
||||
void on_createRecursiveScriptPushbutton_clicked();
|
||||
void on_createAllRecursiveScriptsPushbutton_clicked();
|
||||
void on_createTestsPushbutton_clicked();
|
||||
|
||||
void on_createMDFileButton_clicked();
|
||||
void on_createAllMDFilesButton_clicked();
|
||||
void on_createMDFilePushbutton_clicked();
|
||||
void on_createAllMDFilesPushbutton_clicked();
|
||||
|
||||
void on_createTestAutoScriptButton_clicked();
|
||||
void on_createAllTestAutoScriptsButton_clicked();
|
||||
void on_createTestAutoScriptPushbutton_clicked();
|
||||
void on_createAllTestAutoScriptsPushbutton_clicked();
|
||||
|
||||
void on_createTestsOutlineButton_clicked();
|
||||
void on_createTestsOutlinePushbutton_clicked();
|
||||
|
||||
void on_createTestRailTestCasesButton_clicked();
|
||||
void on_createTestRailTestCasesPushbutton_clicked();
|
||||
void on_createTestRailRunButton_clicked();
|
||||
|
||||
void on_setWorkingFolderButton_clicked();
|
||||
void on_runNowButton_clicked();
|
||||
void on_setWorkingFolderRunOnDesktopPushbutton_clicked();
|
||||
void on_runNowPushbutton_clicked();
|
||||
|
||||
void on_checkBoxRunLatest_clicked();
|
||||
void on_runLatestOnDesktopCheckBox_clicked();
|
||||
|
||||
void on_updateTestRailRunResultsButton_clicked();
|
||||
void on_updateTestRailRunResultsPushbutton_clicked();
|
||||
|
||||
void on_hideTaskbarButton_clicked();
|
||||
void on_showTaskbarButton_clicked();
|
||||
void on_hideTaskbarPushbutton_clicked();
|
||||
void on_showTaskbarPushbutton_clicked();
|
||||
|
||||
void on_createPythonScriptRadioButton_clicked();
|
||||
void on_createXMLScriptRadioButton_clicked();
|
||||
|
||||
void on_createWebPagePushButton_clicked();
|
||||
|
||||
void on_closeButton_clicked();
|
||||
void on_createWebPagePushbutton_clicked();
|
||||
|
||||
void saveFile(int index);
|
||||
|
||||
void about();
|
||||
void content();
|
||||
|
||||
// Run on Mobile controls
|
||||
void on_setWorkingFolderRunOnMobilePushbutton_clicked();
|
||||
void on_connectDevicePushbutton_clicked();
|
||||
void on_runLatestOnMobileCheckBox_clicked();
|
||||
|
||||
void on_downloadAPKPushbutton_clicked();
|
||||
void on_installAPKPushbutton_clicked();
|
||||
void on_runInterfacePushbutton_clicked();
|
||||
|
||||
void on_pullFolderPushbutton_clicked();
|
||||
|
||||
private:
|
||||
Ui::NitpickClass _ui;
|
||||
Test* _test{ nullptr };
|
||||
TestRunner* _testRunner{ nullptr };
|
||||
|
||||
TestRunnerDesktop* _testRunnerDesktop{ nullptr };
|
||||
TestRunnerMobile* _testRunnerMobile{ nullptr };
|
||||
|
||||
AWSInterface _awsInterface;
|
||||
|
|
@ -16,13 +16,13 @@
|
|||
PythonInterface::PythonInterface() {
|
||||
#ifdef Q_OS_WIN
|
||||
if (QProcessEnvironment::systemEnvironment().contains("PYTHON_PATH")) {
|
||||
QString _pythonPath = QProcessEnvironment::systemEnvironment().value("PYTHON_PATH");
|
||||
if (!QFile::exists(_pythonPath + "/" + _pythonExe)) {
|
||||
QMessageBox::critical(0, _pythonExe, QString("Python executable not found in ") + _pythonPath);
|
||||
QString pythonPath = QProcessEnvironment::systemEnvironment().value("PYTHON_PATH");
|
||||
if (!QFile::exists(pythonPath + "/" + _pythonExe)) {
|
||||
QMessageBox::critical(0, _pythonExe, QString("Python executable not found in ") + pythonPath);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
_pythonCommand = _pythonPath + "/" + _pythonExe;
|
||||
_pythonCommand = pythonPath + "/" + _pythonExe;
|
||||
} else {
|
||||
QMessageBox::critical(0, "PYTHON_PATH not defined",
|
||||
"Please set PYTHON_PATH to directory containing the Python executable");
|
||||
|
@ -31,7 +31,7 @@ PythonInterface::PythonInterface() {
|
|||
#elif defined Q_OS_MAC
|
||||
_pythonCommand = "/usr/local/bin/python3";
|
||||
if (!QFile::exists(_pythonCommand)) {
|
||||
QMessageBox::critical(0, "PYTHON_PATH not defined",
|
||||
QMessageBox::critical(0, "python not found",
|
||||
"python3 not found at " + _pythonCommand);
|
||||
exit(-1);
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#include <quazip5/quazip.h>
|
||||
#include <quazip5/JlCompress.h>
|
||||
|
||||
#include "ui/Nitpick.h"
|
||||
#include "Nitpick.h"
|
||||
extern Nitpick* nitpick;
|
||||
|
||||
#include <math.h>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
#include "AWSInterface.h"
|
||||
#include "ImageComparer.h"
|
||||
#include "ui/MismatchWindow.h"
|
||||
#include "MismatchWindow.h"
|
||||
#include "TestRailInterface.h"
|
||||
|
||||
class Step {
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
#ifndef hifi_test_testrail_interface_h
|
||||
#define hifi_test_testrail_interface_h
|
||||
|
||||
#include "ui/BusyWindow.h"
|
||||
#include "ui/TestRailTestCasesSelectorWindow.h"
|
||||
#include "ui/TestRailRunSelectorWindow.h"
|
||||
#include "ui/TestRailResultsSelectorWindow.h"
|
||||
#include "BusyWindow.h"
|
||||
#include "TestRailTestCasesSelectorWindow.h"
|
||||
#include "TestRailRunSelectorWindow.h"
|
||||
#include "TestRailResultsSelectorWindow.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QtXml/QDomDocument>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
// TestRunner.cpp
|
||||
//
|
||||
// Created by Nissim Hadar on 1 Sept 2018.
|
||||
// Created by Nissim Hadar on 23 Jan 2019.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
@ -9,70 +9,12 @@
|
|||
//
|
||||
#include "TestRunner.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QFileDialog>
|
||||
|
||||
#include "ui/Nitpick.h"
|
||||
#include "Nitpick.h"
|
||||
extern Nitpick* nitpick;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#endif
|
||||
|
||||
// TODO: for debug
|
||||
#include <iostream>
|
||||
|
||||
TestRunner::TestRunner(std::vector<QCheckBox*> dayCheckboxes,
|
||||
std::vector<QCheckBox*> timeEditCheckboxes,
|
||||
std::vector<QTimeEdit*> timeEdits,
|
||||
QLabel* workingFolderLabel,
|
||||
QCheckBox* runServerless,
|
||||
QCheckBox* runLatest,
|
||||
QLineEdit* url,
|
||||
QPushButton* runNow,
|
||||
QObject* parent) :
|
||||
QObject(parent) {
|
||||
_dayCheckboxes = dayCheckboxes;
|
||||
_timeEditCheckboxes = timeEditCheckboxes;
|
||||
_timeEdits = timeEdits;
|
||||
_workingFolderLabel = workingFolderLabel;
|
||||
_runServerless = runServerless;
|
||||
_runLatest = runLatest;
|
||||
_url = url;
|
||||
_runNow = runNow;
|
||||
|
||||
_installerThread = new QThread();
|
||||
_installerWorker = new Worker();
|
||||
|
||||
_installerWorker->moveToThread(_installerThread);
|
||||
_installerThread->start();
|
||||
connect(this, SIGNAL(startInstaller()), _installerWorker, SLOT(runCommand()));
|
||||
connect(_installerWorker, SIGNAL(commandComplete()), this, SLOT(installationComplete()));
|
||||
|
||||
_interfaceThread = new QThread();
|
||||
_interfaceWorker = new Worker();
|
||||
|
||||
_interfaceThread->start();
|
||||
_interfaceWorker->moveToThread(_interfaceThread);
|
||||
connect(this, SIGNAL(startInterface()), _interfaceWorker, SLOT(runCommand()));
|
||||
connect(_interfaceWorker, SIGNAL(commandComplete()), this, SLOT(interfaceExecutionComplete()));
|
||||
}
|
||||
|
||||
TestRunner::~TestRunner() {
|
||||
delete _installerThread;
|
||||
delete _installerWorker;
|
||||
|
||||
delete _interfaceThread;
|
||||
delete _interfaceWorker;
|
||||
|
||||
if (_timer) {
|
||||
delete _timer;
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunner::setWorkingFolder() {
|
||||
void TestRunner::setWorkingFolder(QLabel* workingFolderLabel) {
|
||||
// Everything will be written to this folder
|
||||
QString previousSelection = _workingFolder;
|
||||
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
|
||||
|
@ -80,8 +22,8 @@ void TestRunner::setWorkingFolder() {
|
|||
parent += "/";
|
||||
}
|
||||
|
||||
_workingFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a temporary folder for installation", parent,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
_workingFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a working folder for temporary files", parent,
|
||||
QFileDialog::ShowDirsOnly);
|
||||
|
||||
// If user canceled then restore previous selection and return
|
||||
if (_workingFolder == "") {
|
||||
|
@ -89,643 +31,25 @@ void TestRunner::setWorkingFolder() {
|
|||
return;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
_installationFolder = _workingFolder + "/High Fidelity";
|
||||
#elif defined Q_OS_MAC
|
||||
_installationFolder = _workingFolder + "/High_Fidelity";
|
||||
#endif
|
||||
workingFolderLabel->setText(QDir::toNativeSeparators(_workingFolder));
|
||||
|
||||
// This file is used for debug purposes.
|
||||
_logFile.setFileName(_workingFolder + "/log.txt");
|
||||
|
||||
nitpick->enableRunTabControls();
|
||||
_workingFolderLabel->setText(QDir::toNativeSeparators(_workingFolder));
|
||||
|
||||
_timer = new QTimer(this);
|
||||
connect(_timer, SIGNAL(timeout()), this, SLOT(checkTime()));
|
||||
_timer->start(30 * 1000); //time specified in ms
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// Create MAC shell scripts
|
||||
QFile script;
|
||||
|
||||
// This script waits for a process to start
|
||||
script.setFileName(_workingFolder + "/waitForStart.sh");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'waitForStart.sh'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
script.write("#!/bin/sh\n\n");
|
||||
script.write("PROCESS=\"$1\"\n");
|
||||
script.write("until (pgrep -x $PROCESS >nul)\n");
|
||||
script.write("do\n");
|
||||
script.write("\techo waiting for \"$1\" to start\n");
|
||||
script.write("\tsleep 2\n");
|
||||
script.write("done\n");
|
||||
script.write("echo \"$1\" \"started\"\n");
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
|
||||
// The Mac shell command returns immediately. This little script waits for a process to finish
|
||||
script.setFileName(_workingFolder + "/waitForFinish.sh");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'waitForFinish.sh'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
script.write("#!/bin/sh\n\n");
|
||||
script.write("PROCESS=\"$1\"\n");
|
||||
script.write("while (pgrep -x $PROCESS >nul)\n");
|
||||
script.write("do\n");
|
||||
script.write("\techo waiting for \"$1\" to finish\n");
|
||||
script.write("\tsleep 2\n");
|
||||
script.write("done\n");
|
||||
script.write("echo \"$1\" \"finished\"\n");
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
|
||||
// Create an AppleScript to resize Interface. This is needed so that snapshots taken
|
||||
// with the primary camera will be the correct size.
|
||||
// This will be run from a normal shell script
|
||||
script.setFileName(_workingFolder + "/setInterfaceSizeAndPosition.scpt");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'setInterfaceSizeAndPosition.scpt'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
script.write("set width to 960\n");
|
||||
script.write("set height to 540\n");
|
||||
script.write("set x to 100\n");
|
||||
script.write("set y to 100\n\n");
|
||||
script.write("tell application \"System Events\" to tell application process \"interface\" to tell window 1 to set {size, position} to {{width, height}, {x, y}}\n");
|
||||
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
|
||||
script.setFileName(_workingFolder + "/setInterfaceSizeAndPosition.sh");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'setInterfaceSizeAndPosition.sh'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
script.write("#!/bin/sh\n\n");
|
||||
script.write("echo resizing interface\n");
|
||||
script.write(("osascript " + _workingFolder + "/setInterfaceSizeAndPosition.scpt\n").toStdString().c_str());
|
||||
script.write("echo resize complete\n");
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestRunner::run() {
|
||||
_runNow->setEnabled(false);
|
||||
|
||||
_testStartDateTime = QDateTime::currentDateTime();
|
||||
_automatedTestIsRunning = true;
|
||||
|
||||
// Initial setup
|
||||
_branch = nitpick->getSelectedBranch();
|
||||
_user = nitpick->getSelectedUser();
|
||||
|
||||
// This will be restored at the end of the tests
|
||||
saveExistingHighFidelityAppDataFolder();
|
||||
|
||||
void TestRunner::downloadBuildXml(void* caller) {
|
||||
// Download the latest High Fidelity build XML.
|
||||
// Note that this is not needed for PR builds (or whenever `Run Latest` is unchecked)
|
||||
// It is still downloaded, to simplify the flow
|
||||
buildXMLDownloaded = false;
|
||||
|
||||
QStringList urls;
|
||||
QStringList filenames;
|
||||
|
||||
urls << DEV_BUILD_XML_URL;
|
||||
filenames << DEV_BUILD_XML_FILENAME;
|
||||
|
||||
updateStatusLabel("Downloading Build XML");
|
||||
|
||||
buildXMLDownloaded = false;
|
||||
nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this);
|
||||
|
||||
// `downloadComplete` will run after download has completed
|
||||
}
|
||||
|
||||
void TestRunner::downloadComplete() {
|
||||
if (!buildXMLDownloaded) {
|
||||
// Download of Build XML has completed
|
||||
buildXMLDownloaded = true;
|
||||
|
||||
// Download the High Fidelity installer
|
||||
QStringList urls;
|
||||
QStringList filenames;
|
||||
if (_runLatest->isChecked()) {
|
||||
parseBuildInformation();
|
||||
|
||||
_installerFilename = INSTALLER_FILENAME_LATEST;
|
||||
|
||||
urls << _buildInformation.url;
|
||||
filenames << _installerFilename;
|
||||
} else {
|
||||
QString urlText = _url->text();
|
||||
urls << urlText;
|
||||
_installerFilename = getInstallerNameFromURL(urlText);
|
||||
filenames << _installerFilename;
|
||||
}
|
||||
|
||||
updateStatusLabel("Downloading installer");
|
||||
|
||||
nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this);
|
||||
|
||||
// `downloadComplete` will run again after download has completed
|
||||
|
||||
} else {
|
||||
// Download of Installer has completed
|
||||
appendLog(QString("Tests started at ") + QString::number(_testStartDateTime.time().hour()) + ":" +
|
||||
QString("%1").arg(_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " +
|
||||
_testStartDateTime.date().toString("ddd, MMM d, yyyy"));
|
||||
|
||||
updateStatusLabel("Installing");
|
||||
|
||||
// Kill any existing processes that would interfere with installation
|
||||
killProcesses();
|
||||
|
||||
runInstaller();
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunner::runInstaller() {
|
||||
// Qt cannot start an installation process using QProcess::start (Qt Bug 9761)
|
||||
// To allow installation, the installer is run using the `system` command
|
||||
|
||||
QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_installationFolder) };
|
||||
|
||||
QString installerFullPath = _workingFolder + "/" + _installerFilename;
|
||||
|
||||
QString commandLine;
|
||||
#ifdef Q_OS_WIN
|
||||
commandLine = "\"" + QDir::toNativeSeparators(installerFullPath) + "\"" + " /S /D=" + QDir::toNativeSeparators(_installationFolder);
|
||||
#elif defined Q_OS_MAC
|
||||
// Create installation shell script
|
||||
QFile script;
|
||||
script.setFileName(_workingFolder + "/install_app.sh");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'install_app.sh'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (!QDir().exists(_installationFolder)) {
|
||||
QDir().mkdir(_installationFolder);
|
||||
}
|
||||
|
||||
// This script installs High Fidelity. It is run as "yes | install_app.sh... so "yes" is killed at the end
|
||||
script.write("#!/bin/sh\n\n");
|
||||
script.write("VOLUME=`hdiutil attach \"$1\" | grep Volumes | awk '{print $3}'`\n");
|
||||
|
||||
QString folderName {"High Fidelity"};
|
||||
if (!_runLatest->isChecked()) {
|
||||
folderName += QString(" - ") + getPRNumberFromURL(_url->text());
|
||||
}
|
||||
|
||||
script.write((QString("cp -rf \"$VOLUME/") + folderName + "/interface.app\" \"" + _workingFolder + "/High_Fidelity/\"\n").toStdString().c_str());
|
||||
script.write((QString("cp -rf \"$VOLUME/") + folderName + "/Sandbox.app\" \"" + _workingFolder + "/High_Fidelity/\"\n").toStdString().c_str());
|
||||
|
||||
script.write("hdiutil detach \"$VOLUME\"\n");
|
||||
script.write("killall yes\n");
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
commandLine = "yes | " + _workingFolder + "/install_app.sh " + installerFullPath;
|
||||
#endif
|
||||
appendLog(commandLine);
|
||||
_installerWorker->setCommandLine(commandLine);
|
||||
emit startInstaller();
|
||||
}
|
||||
|
||||
void TestRunner::installationComplete() {
|
||||
verifyInstallationSucceeded();
|
||||
|
||||
createSnapshotFolder();
|
||||
|
||||
updateStatusLabel("Running tests");
|
||||
|
||||
if (!_runServerless->isChecked()) {
|
||||
startLocalServerProcesses();
|
||||
}
|
||||
|
||||
runInterfaceWithTestScript();
|
||||
}
|
||||
|
||||
void TestRunner::verifyInstallationSucceeded() {
|
||||
// Exit if the executables are missing.
|
||||
// On Windows, the reason is probably that UAC has blocked the installation. This is treated as a critical error
|
||||
#ifdef Q_OS_WIN
|
||||
QFileInfo interfaceExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe");
|
||||
QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe");
|
||||
QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe");
|
||||
|
||||
if (!interfaceExe.exists() || !assignmentClientExe.exists() || !domainServerExe.exists()) {
|
||||
QMessageBox::critical(0, "Installation of High Fidelity has failed", "Please verify that UAC has been disabled");
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestRunner::saveExistingHighFidelityAppDataFolder() {
|
||||
QString dataDirectory{ "NOT FOUND" };
|
||||
#ifdef Q_OS_WIN
|
||||
dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming";
|
||||
#elif defined Q_OS_MAC
|
||||
dataDirectory = QDir::homePath() + "/Library/Application Support";
|
||||
#endif
|
||||
if (_runLatest->isChecked()) {
|
||||
_appDataFolder = dataDirectory + "/High Fidelity";
|
||||
} else {
|
||||
// We are running a PR build
|
||||
_appDataFolder = dataDirectory + "/High Fidelity - " + getPRNumberFromURL(_url->text());
|
||||
}
|
||||
|
||||
_savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME;
|
||||
if (QDir(_savedAppDataFolder).exists()) {
|
||||
_savedAppDataFolder.removeRecursively();
|
||||
}
|
||||
if (_appDataFolder.exists()) {
|
||||
// The original folder is saved in a unique name
|
||||
_appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path());
|
||||
}
|
||||
|
||||
// Copy an "empty" AppData folder (i.e. no entities)
|
||||
copyFolder(QDir::currentPath() + "/AppDataHighFidelity", _appDataFolder.path());
|
||||
}
|
||||
|
||||
void TestRunner::createSnapshotFolder() {
|
||||
_snapshotFolder = _workingFolder + "/" + SNAPSHOT_FOLDER_NAME;
|
||||
|
||||
// Just delete all PNGs from the folder if it already exists
|
||||
if (QDir(_snapshotFolder).exists()) {
|
||||
// Note that we cannot use just a `png` filter, as the filenames include periods
|
||||
// Also, delete any `jpg` and `txt` files
|
||||
// The idea is to leave only previous zipped result folders
|
||||
QDirIterator it(_snapshotFolder);
|
||||
while (it.hasNext()) {
|
||||
QString filename = it.next();
|
||||
if (filename.right(4) == ".png" || filename.right(4) == ".jpg" || filename.right(4) == ".txt") {
|
||||
QFile::remove(filename);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
QDir().mkdir(_snapshotFolder);
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunner::killProcesses() {
|
||||
#ifdef Q_OS_WIN
|
||||
try {
|
||||
QStringList processesToKill = QStringList() << "interface.exe"
|
||||
<< "assignment-client.exe"
|
||||
<< "domain-server.exe"
|
||||
<< "server-console.exe";
|
||||
|
||||
// Loop until all pending processes to kill have actually died
|
||||
QStringList pendingProcessesToKill;
|
||||
do {
|
||||
pendingProcessesToKill.clear();
|
||||
|
||||
// Get list of running tasks
|
||||
HANDLE processSnapHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (processSnapHandle == INVALID_HANDLE_VALUE) {
|
||||
throw("Process snapshot creation failure");
|
||||
}
|
||||
|
||||
PROCESSENTRY32 processEntry32;
|
||||
processEntry32.dwSize = sizeof(PROCESSENTRY32);
|
||||
if (!Process32First(processSnapHandle, &processEntry32)) {
|
||||
CloseHandle(processSnapHandle);
|
||||
throw("Process32First failed");
|
||||
}
|
||||
|
||||
// Kill any task in the list
|
||||
do {
|
||||
foreach (QString process, processesToKill)
|
||||
if (QString(processEntry32.szExeFile) == process) {
|
||||
QString commandLine = "taskkill /im " + process + " /f >nul";
|
||||
system(commandLine.toStdString().c_str());
|
||||
pendingProcessesToKill << process;
|
||||
}
|
||||
} while (Process32Next(processSnapHandle, &processEntry32));
|
||||
|
||||
QThread::sleep(2);
|
||||
} while (!pendingProcessesToKill.isEmpty());
|
||||
|
||||
} catch (QString errorMessage) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage);
|
||||
exit(-1);
|
||||
} catch (...) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error");
|
||||
exit(-1);
|
||||
}
|
||||
#elif defined Q_OS_MAC
|
||||
QString commandLine;
|
||||
|
||||
commandLine = QString("killall interface") + "; " + _workingFolder +"/waitForFinish.sh interface";
|
||||
system(commandLine.toStdString().c_str());
|
||||
|
||||
commandLine = QString("killall Sandbox") + "; " + _workingFolder +"/waitForFinish.sh Sandbox";
|
||||
system(commandLine.toStdString().c_str());
|
||||
|
||||
commandLine = QString("killall Console") + "; " + _workingFolder +"/waitForFinish.sh Console";
|
||||
system(commandLine.toStdString().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestRunner::startLocalServerProcesses() {
|
||||
QString commandLine;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
commandLine =
|
||||
"start \"domain-server.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe\"";
|
||||
system(commandLine.toStdString().c_str());
|
||||
|
||||
commandLine =
|
||||
"start \"assignment-client.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe\" -n 6";
|
||||
system(commandLine.toStdString().c_str());
|
||||
|
||||
#elif defined Q_OS_MAC
|
||||
commandLine = "open \"" +_installationFolder + "/Sandbox.app\"";
|
||||
system(commandLine.toStdString().c_str());
|
||||
#endif
|
||||
|
||||
// Give server processes time to stabilize
|
||||
QThread::sleep(20);
|
||||
}
|
||||
|
||||
void TestRunner::runInterfaceWithTestScript() {
|
||||
QString url = QString("hifi://localhost");
|
||||
if (_runServerless->isChecked()) {
|
||||
// Move to an empty area
|
||||
url = "file:///~serverless/tutorial.json";
|
||||
} else {
|
||||
url = "hifi://localhost";
|
||||
}
|
||||
|
||||
QString deleteScript =
|
||||
QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/utils/deleteNearbyEntities.js";
|
||||
|
||||
QString testScript =
|
||||
QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js";
|
||||
|
||||
QString commandLine;
|
||||
#ifdef Q_OS_WIN
|
||||
QString exeFile;
|
||||
// First, run script to delete any entities in test area
|
||||
// Note that this will run to completion before continuing
|
||||
exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\"";
|
||||
commandLine = "start /wait \"\" " + exeFile +
|
||||
" --url " + url +
|
||||
" --no-updater" +
|
||||
" --no-login-suggestion"
|
||||
" --testScript " + deleteScript + " quitWhenFinished";
|
||||
|
||||
system(commandLine.toStdString().c_str());
|
||||
|
||||
// Now run the test suite
|
||||
exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\"";
|
||||
commandLine = exeFile +
|
||||
" --url " + url +
|
||||
" --no-updater" +
|
||||
" --no-login-suggestion"
|
||||
" --testScript " + testScript + " quitWhenFinished" +
|
||||
" --testResultsLocation " + _snapshotFolder;
|
||||
|
||||
_interfaceWorker->setCommandLine(commandLine);
|
||||
emit startInterface();
|
||||
#elif defined Q_OS_MAC
|
||||
QFile script;
|
||||
script.setFileName(_workingFolder + "/runInterfaceTests.sh");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'runInterfaceTests.sh'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
script.write("#!/bin/sh\n\n");
|
||||
|
||||
// First, run script to delete any entities in test area
|
||||
commandLine =
|
||||
"open -W \"" +_installationFolder + "/interface.app\" --args" +
|
||||
" --url " + url +
|
||||
" --no-updater" +
|
||||
" --no-login-suggestion"
|
||||
" --testScript " + deleteScript + " quitWhenFinished\n";
|
||||
|
||||
script.write(commandLine.toStdString().c_str());
|
||||
|
||||
// On The Mac, we need to resize Interface. The Interface window opens a few seconds after the process
|
||||
// has started.
|
||||
// Before starting interface, start a process that will resize interface 10s after it opens
|
||||
commandLine = _workingFolder +"/waitForStart.sh interface && sleep 10 && " + _workingFolder +"/setInterfaceSizeAndPosition.sh &\n";
|
||||
script.write(commandLine.toStdString().c_str());
|
||||
|
||||
commandLine =
|
||||
"open \"" +_installationFolder + "/interface.app\" --args" +
|
||||
" --url " + url +
|
||||
" --no-updater" +
|
||||
" --no-login-suggestion"
|
||||
" --testScript " + testScript + " quitWhenFinished" +
|
||||
" --testResultsLocation " + _snapshotFolder +
|
||||
" && " + _workingFolder +"/waitForFinish.sh interface\n";
|
||||
|
||||
script.write(commandLine.toStdString().c_str());
|
||||
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
|
||||
commandLine = _workingFolder + "/runInterfaceTests.sh";
|
||||
_interfaceWorker->setCommandLine(commandLine);
|
||||
|
||||
emit startInterface();
|
||||
#endif
|
||||
|
||||
// Helpful for debugging
|
||||
appendLog(commandLine);
|
||||
}
|
||||
|
||||
void TestRunner::interfaceExecutionComplete() {
|
||||
QFileInfo testCompleted(QDir::toNativeSeparators(_snapshotFolder) +"/tests_completed.txt");
|
||||
if (!testCompleted.exists()) {
|
||||
QMessageBox::critical(0, "Tests not completed", "Interface seems to have crashed before completion of the test scripts\nExisting images will be evaluated");
|
||||
}
|
||||
|
||||
evaluateResults();
|
||||
|
||||
killProcesses();
|
||||
|
||||
// The High Fidelity AppData folder will be restored after evaluation has completed
|
||||
}
|
||||
|
||||
void TestRunner::evaluateResults() {
|
||||
updateStatusLabel("Evaluating results");
|
||||
nitpick->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user);
|
||||
}
|
||||
|
||||
void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int numberOfFailures) {
|
||||
addBuildNumberToResults(zippedFolder);
|
||||
restoreHighFidelityAppDataFolder();
|
||||
|
||||
updateStatusLabel("Testing complete");
|
||||
|
||||
QDateTime currentDateTime = QDateTime::currentDateTime();
|
||||
|
||||
QString completionText = QString("Tests completed at ") + QString::number(currentDateTime.time().hour()) + ":" +
|
||||
QString("%1").arg(currentDateTime.time().minute(), 2, 10, QChar('0')) + ", on " +
|
||||
currentDateTime.date().toString("ddd, MMM d, yyyy");
|
||||
|
||||
if (numberOfFailures == 0) {
|
||||
completionText += "; no failures";
|
||||
} else if (numberOfFailures == 1) {
|
||||
completionText += "; 1 failure";
|
||||
} else {
|
||||
completionText += QString("; ") + QString::number(numberOfFailures) + " failures";
|
||||
}
|
||||
appendLog(completionText);
|
||||
|
||||
_automatedTestIsRunning = false;
|
||||
|
||||
_runNow->setEnabled(true);
|
||||
}
|
||||
|
||||
void TestRunner::addBuildNumberToResults(QString zippedFolderName) {
|
||||
QString augmentedFilename;
|
||||
if (!_runLatest->isChecked()) {
|
||||
augmentedFilename = zippedFolderName.replace("local", getPRNumberFromURL(_url->text()));
|
||||
} else {
|
||||
augmentedFilename = zippedFolderName.replace("local", _buildInformation.build);
|
||||
}
|
||||
QFile::rename(zippedFolderName, augmentedFilename);
|
||||
}
|
||||
|
||||
void TestRunner::restoreHighFidelityAppDataFolder() {
|
||||
_appDataFolder.removeRecursively();
|
||||
|
||||
if (_savedAppDataFolder != QDir()) {
|
||||
_appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path());
|
||||
}
|
||||
}
|
||||
|
||||
// Copies a folder recursively
|
||||
void TestRunner::copyFolder(const QString& source, const QString& destination) {
|
||||
try {
|
||||
if (!QFileInfo(source).isDir()) {
|
||||
// just a file copy
|
||||
QFile::copy(source, destination);
|
||||
} else {
|
||||
QDir destinationDir(destination);
|
||||
if (!destinationDir.cdUp()) {
|
||||
throw("'source '" + source + "'seems to be a root folder");
|
||||
}
|
||||
|
||||
if (!destinationDir.mkdir(QFileInfo(destination).fileName())) {
|
||||
throw("Could not create destination folder '" + destination + "'");
|
||||
}
|
||||
|
||||
QStringList fileNames =
|
||||
QDir(source).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
|
||||
|
||||
foreach (const QString& fileName, fileNames) {
|
||||
copyFolder(QString(source + "/" + fileName), QString(destination + "/" + fileName));
|
||||
}
|
||||
}
|
||||
} catch (QString errorMessage) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage);
|
||||
exit(-1);
|
||||
} catch (...) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunner::checkTime() {
|
||||
// No processing is done if a test is running
|
||||
if (_automatedTestIsRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
|
||||
// Check day of week
|
||||
if (!_dayCheckboxes.at(now.date().dayOfWeek() - 1)->isChecked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the time
|
||||
bool timeToRun{ false };
|
||||
|
||||
for (size_t i = 0; i < std::min(_timeEditCheckboxes.size(), _timeEdits.size()); ++i) {
|
||||
if (_timeEditCheckboxes[i]->isChecked() && (_timeEdits[i]->time().hour() == now.time().hour()) &&
|
||||
(_timeEdits[i]->time().minute() == now.time().minute())) {
|
||||
timeToRun = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeToRun) {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunner::updateStatusLabel(const QString& message) {
|
||||
nitpick->updateStatusLabel(message);
|
||||
}
|
||||
|
||||
void TestRunner::appendLog(const QString& message) {
|
||||
if (!_logFile.open(QIODevice::Append | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open the log file");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
_logFile.write(message.toStdString().c_str());
|
||||
_logFile.write("\n");
|
||||
_logFile.close();
|
||||
|
||||
nitpick->appendLogWindow(message);
|
||||
}
|
||||
|
||||
QString TestRunner::getInstallerNameFromURL(const QString& url) {
|
||||
// An example URL: https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe
|
||||
// On Mac, replace `exe` with `dmg`
|
||||
try {
|
||||
QStringList urlParts = url.split("/");
|
||||
return urlParts[urlParts.size() - 1];
|
||||
} catch (QString errorMessage) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage);
|
||||
exit(-1);
|
||||
} catch (...) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
QString TestRunner::getPRNumberFromURL(const QString& url) {
|
||||
try {
|
||||
QStringList urlParts = url.split("/");
|
||||
QStringList filenameParts = urlParts[urlParts.size() - 1].split("-");
|
||||
if (filenameParts.size() <= 3) {
|
||||
#ifdef Q_OS_WIN
|
||||
throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`";
|
||||
#elif defined Q_OS_MAC
|
||||
throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.dmg`";
|
||||
#endif
|
||||
}
|
||||
return filenameParts[filenameParts.size() - 2];
|
||||
} catch (QString errorMessage) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage);
|
||||
exit(-1);
|
||||
} catch (...) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error");
|
||||
exit(-1);
|
||||
}
|
||||
nitpick->downloadFiles(urls, _workingFolder, filenames, caller);
|
||||
}
|
||||
|
||||
void TestRunner::parseBuildInformation() {
|
||||
|
@ -802,15 +126,48 @@ void TestRunner::parseBuildInformation() {
|
|||
}
|
||||
_buildInformation.url = element.text();
|
||||
|
||||
} catch (QString errorMessage) {
|
||||
}
|
||||
catch (QString errorMessage) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage);
|
||||
exit(-1);
|
||||
} catch (...) {
|
||||
}
|
||||
catch (...) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
QString TestRunner::getInstallerNameFromURL(const QString& url) {
|
||||
// An example URL: https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe
|
||||
// On Mac, replace `exe` with `dmg`
|
||||
try {
|
||||
QStringList urlParts = url.split("/");
|
||||
return urlParts[urlParts.size() - 1];
|
||||
}
|
||||
catch (QString errorMessage) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage);
|
||||
exit(-1);
|
||||
}
|
||||
catch (...) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunner::appendLog(const QString& message) {
|
||||
if (!_logFile.open(QIODevice::Append | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open the log file");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
_logFile.write(message.toStdString().c_str());
|
||||
_logFile.write("\n");
|
||||
_logFile.close();
|
||||
|
||||
nitpick->appendLogWindow(message);
|
||||
}
|
||||
|
||||
void Worker::setCommandLine(const QString& commandLine) {
|
||||
_commandLine = commandLine;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//
|
||||
// TestRunner.h
|
||||
//
|
||||
// Created by Nissim Hadar on 1 Sept 2018.
|
||||
// Created by Nissim Hadar on 23 Jan 2019.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
@ -16,10 +16,9 @@
|
|||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QObject>
|
||||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
#include <QTimeEdit>
|
||||
#include <QTimer>
|
||||
|
||||
class Worker;
|
||||
|
||||
class BuildInformation {
|
||||
public:
|
||||
|
@ -27,67 +26,28 @@ public:
|
|||
QString url;
|
||||
};
|
||||
|
||||
class Worker;
|
||||
|
||||
class TestRunner : public QObject {
|
||||
Q_OBJECT
|
||||
class TestRunner {
|
||||
public:
|
||||
explicit TestRunner(std::vector<QCheckBox*> dayCheckboxes,
|
||||
std::vector<QCheckBox*> timeEditCheckboxes,
|
||||
std::vector<QTimeEdit*> timeEdits,
|
||||
QLabel* workingFolderLabel,
|
||||
QCheckBox* runServerless,
|
||||
QCheckBox* runLatest,
|
||||
QLineEdit* url,
|
||||
QPushButton* runNow,
|
||||
QObject* parent = 0);
|
||||
void setWorkingFolder(QLabel* workingFolderLabel);
|
||||
void downloadBuildXml(void* caller);
|
||||
void parseBuildInformation();
|
||||
QString getInstallerNameFromURL(const QString& url);
|
||||
|
||||
~TestRunner();
|
||||
|
||||
void setWorkingFolder();
|
||||
|
||||
void run();
|
||||
|
||||
void downloadComplete();
|
||||
void runInstaller();
|
||||
void verifyInstallationSucceeded();
|
||||
|
||||
void saveExistingHighFidelityAppDataFolder();
|
||||
void restoreHighFidelityAppDataFolder();
|
||||
|
||||
void createSnapshotFolder();
|
||||
|
||||
void killProcesses();
|
||||
void startLocalServerProcesses();
|
||||
|
||||
void runInterfaceWithTestScript();
|
||||
|
||||
void evaluateResults();
|
||||
void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures);
|
||||
void addBuildNumberToResults(QString zippedFolderName);
|
||||
|
||||
void copyFolder(const QString& source, const QString& destination);
|
||||
|
||||
void updateStatusLabel(const QString& message);
|
||||
void appendLog(const QString& message);
|
||||
|
||||
QString getInstallerNameFromURL(const QString& url);
|
||||
QString getPRNumberFromURL(const QString& url);
|
||||
protected:
|
||||
QLabel* _workingFolderLabel;
|
||||
QLabel* _statusLabel;
|
||||
QLineEdit* _url;
|
||||
QCheckBox* _runLatest;
|
||||
|
||||
void parseBuildInformation();
|
||||
QString _workingFolder;
|
||||
|
||||
private slots:
|
||||
void checkTime();
|
||||
void installationComplete();
|
||||
void interfaceExecutionComplete();
|
||||
const QString DEV_BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" };
|
||||
const QString DEV_BUILD_XML_FILENAME{ "dev-builds.xml" };
|
||||
|
||||
signals:
|
||||
void startInstaller();
|
||||
void startInterface();
|
||||
void startResize();
|
||||
|
||||
private:
|
||||
bool _automatedTestIsRunning{ false };
|
||||
bool buildXMLDownloaded;
|
||||
BuildInformation _buildInformation;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
const QString INSTALLER_FILENAME_LATEST{ "HighFidelity-Beta-latest-dev.exe" };
|
||||
|
@ -97,47 +57,10 @@ private:
|
|||
const QString INSTALLER_FILENAME_LATEST{ "" };
|
||||
#endif
|
||||
|
||||
QString _installerURL;
|
||||
QString _installerFilename;
|
||||
const QString DEV_BUILD_XML_URL{ "https://highfidelity.com/dev-builds.xml" };
|
||||
const QString DEV_BUILD_XML_FILENAME{ "dev-builds.xml" };
|
||||
|
||||
bool buildXMLDownloaded;
|
||||
|
||||
QDir _appDataFolder;
|
||||
QDir _savedAppDataFolder;
|
||||
|
||||
QString _workingFolder;
|
||||
QString _installationFolder;
|
||||
QString _snapshotFolder;
|
||||
|
||||
const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" };
|
||||
const QString SNAPSHOT_FOLDER_NAME{ "snapshots" };
|
||||
|
||||
QString _branch;
|
||||
QString _user;
|
||||
|
||||
std::vector<QCheckBox*> _dayCheckboxes;
|
||||
std::vector<QCheckBox*> _timeEditCheckboxes;
|
||||
std::vector<QTimeEdit*> _timeEdits;
|
||||
QLabel* _workingFolderLabel;
|
||||
QCheckBox* _runServerless;
|
||||
QCheckBox* _runLatest;
|
||||
QLineEdit* _url;
|
||||
QPushButton* _runNow;
|
||||
QTimer* _timer;
|
||||
|
||||
QFile _logFile;
|
||||
|
||||
QDateTime _testStartDateTime;
|
||||
|
||||
QThread* _installerThread;
|
||||
QThread* _interfaceThread;
|
||||
|
||||
Worker* _installerWorker;
|
||||
Worker* _interfaceWorker;
|
||||
|
||||
BuildInformation _buildInformation;
|
||||
private:
|
||||
QFile _logFile;
|
||||
};
|
||||
|
||||
class Worker : public QObject {
|
||||
|
@ -150,10 +73,9 @@ public slots:
|
|||
|
||||
signals:
|
||||
void commandComplete();
|
||||
void startInstaller();
|
||||
void startInterface();
|
||||
|
||||
|
||||
private:
|
||||
QString _commandLine;
|
||||
};
|
||||
#endif // hifi_testRunner_h
|
||||
|
||||
#endif
|
||||
|
|
681
tools/nitpick/src/TestRunnerDesktop.cpp
Normal file
681
tools/nitpick/src/TestRunnerDesktop.cpp
Normal file
|
@ -0,0 +1,681 @@
|
|||
//
|
||||
// TestRunnerDesktop.cpp
|
||||
//
|
||||
// Created by Nissim Hadar on 1 Sept 2018.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "TestRunnerDesktop.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#endif
|
||||
|
||||
#include "Nitpick.h"
|
||||
extern Nitpick* nitpick;
|
||||
|
||||
TestRunnerDesktop::TestRunnerDesktop(
|
||||
std::vector<QCheckBox*> dayCheckboxes,
|
||||
std::vector<QCheckBox*> timeEditCheckboxes,
|
||||
std::vector<QTimeEdit*> timeEdits,
|
||||
QLabel* workingFolderLabel,
|
||||
QCheckBox* runServerless,
|
||||
QCheckBox* runLatest,
|
||||
QLineEdit* url,
|
||||
QPushButton* runNow,
|
||||
QLabel* statusLabel,
|
||||
|
||||
QObject* parent
|
||||
) : QObject(parent)
|
||||
{
|
||||
_dayCheckboxes = dayCheckboxes;
|
||||
_timeEditCheckboxes = timeEditCheckboxes;
|
||||
_timeEdits = timeEdits;
|
||||
_workingFolderLabel = workingFolderLabel;
|
||||
_runServerless = runServerless;
|
||||
_runLatest = runLatest;
|
||||
_url = url;
|
||||
_runNow = runNow;
|
||||
_statusLabel = statusLabel;
|
||||
|
||||
_installerThread = new QThread();
|
||||
_installerWorker = new InstallerWorker();
|
||||
|
||||
_installerWorker->moveToThread(_installerThread);
|
||||
_installerThread->start();
|
||||
connect(this, SIGNAL(startInstaller()), _installerWorker, SLOT(runCommand()));
|
||||
connect(_installerWorker, SIGNAL(commandComplete()), this, SLOT(installationComplete()));
|
||||
|
||||
_interfaceThread = new QThread();
|
||||
_interfaceWorker = new InterfaceWorker();
|
||||
|
||||
_interfaceThread->start();
|
||||
_interfaceWorker->moveToThread(_interfaceThread);
|
||||
connect(this, SIGNAL(startInterface()), _interfaceWorker, SLOT(runCommand()));
|
||||
connect(_interfaceWorker, SIGNAL(commandComplete()), this, SLOT(interfaceExecutionComplete()));
|
||||
}
|
||||
|
||||
TestRunnerDesktop::~TestRunnerDesktop() {
|
||||
delete _installerThread;
|
||||
delete _installerWorker;
|
||||
|
||||
delete _interfaceThread;
|
||||
delete _interfaceWorker;
|
||||
|
||||
if (_timer) {
|
||||
delete _timer;
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::setWorkingFolderAndEnableControls() {
|
||||
setWorkingFolder(_workingFolderLabel);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
_installationFolder = _workingFolder + "/High Fidelity";
|
||||
#elif defined Q_OS_MAC
|
||||
_installationFolder = _workingFolder + "/High_Fidelity";
|
||||
#endif
|
||||
|
||||
nitpick->enableRunTabControls();
|
||||
|
||||
_timer = new QTimer(this);
|
||||
connect(_timer, SIGNAL(timeout()), this, SLOT(checkTime()));
|
||||
_timer->start(30 * 1000); //time specified in ms
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// Create MAC shell scripts
|
||||
QFile script;
|
||||
|
||||
// This script waits for a process to start
|
||||
script.setFileName(_workingFolder + "/waitForStart.sh");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'waitForStart.sh'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
script.write("#!/bin/sh\n\n");
|
||||
script.write("PROCESS=\"$1\"\n");
|
||||
script.write("until (pgrep -x $PROCESS >nul)\n");
|
||||
script.write("do\n");
|
||||
script.write("\techo waiting for \"$1\" to start\n");
|
||||
script.write("\tsleep 2\n");
|
||||
script.write("done\n");
|
||||
script.write("echo \"$1\" \"started\"\n");
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
|
||||
// The Mac shell command returns immediately. This little script waits for a process to finish
|
||||
script.setFileName(_workingFolder + "/waitForFinish.sh");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'waitForFinish.sh'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
script.write("#!/bin/sh\n\n");
|
||||
script.write("PROCESS=\"$1\"\n");
|
||||
script.write("while (pgrep -x $PROCESS >nul)\n");
|
||||
script.write("do\n");
|
||||
script.write("\techo waiting for \"$1\" to finish\n");
|
||||
script.write("\tsleep 2\n");
|
||||
script.write("done\n");
|
||||
script.write("echo \"$1\" \"finished\"\n");
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
|
||||
// Create an AppleScript to resize Interface. This is needed so that snapshots taken
|
||||
// with the primary camera will be the correct size.
|
||||
// This will be run from a normal shell script
|
||||
script.setFileName(_workingFolder + "/setInterfaceSizeAndPosition.scpt");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'setInterfaceSizeAndPosition.scpt'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
script.write("set width to 960\n");
|
||||
script.write("set height to 540\n");
|
||||
script.write("set x to 100\n");
|
||||
script.write("set y to 100\n\n");
|
||||
script.write("tell application \"System Events\" to tell application process \"interface\" to tell window 1 to set {size, position} to {{width, height}, {x, y}}\n");
|
||||
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
|
||||
script.setFileName(_workingFolder + "/setInterfaceSizeAndPosition.sh");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'setInterfaceSizeAndPosition.sh'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
script.write("#!/bin/sh\n\n");
|
||||
script.write("echo resizing interface\n");
|
||||
script.write(("osascript " + _workingFolder + "/setInterfaceSizeAndPosition.scpt\n").toStdString().c_str());
|
||||
script.write("echo resize complete\n");
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::run() {
|
||||
_runNow->setEnabled(false);
|
||||
|
||||
_testStartDateTime = QDateTime::currentDateTime();
|
||||
_automatedTestIsRunning = true;
|
||||
|
||||
// Initial setup
|
||||
_branch = nitpick->getSelectedBranch();
|
||||
_user = nitpick->getSelectedUser();
|
||||
|
||||
// This will be restored at the end of the tests
|
||||
saveExistingHighFidelityAppDataFolder();
|
||||
|
||||
_statusLabel->setText("Downloading Build XML");
|
||||
downloadBuildXml((void*)this);
|
||||
|
||||
// `downloadComplete` will run after download has completed
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::downloadComplete() {
|
||||
if (!buildXMLDownloaded) {
|
||||
// Download of Build XML has completed
|
||||
buildXMLDownloaded = true;
|
||||
|
||||
// Download the High Fidelity installer
|
||||
QStringList urls;
|
||||
QStringList filenames;
|
||||
if (_runLatest->isChecked()) {
|
||||
parseBuildInformation();
|
||||
|
||||
_installerFilename = INSTALLER_FILENAME_LATEST;
|
||||
|
||||
urls << _buildInformation.url;
|
||||
filenames << _installerFilename;
|
||||
} else {
|
||||
QString urlText = _url->text();
|
||||
urls << urlText;
|
||||
_installerFilename = getInstallerNameFromURL(urlText);
|
||||
filenames << _installerFilename;
|
||||
}
|
||||
|
||||
_statusLabel->setText("Downloading installer");
|
||||
|
||||
nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this);
|
||||
|
||||
// `downloadComplete` will run again after download has completed
|
||||
|
||||
} else {
|
||||
// Download of Installer has completed
|
||||
appendLog(QString("Tests started at ") + QString::number(_testStartDateTime.time().hour()) + ":" +
|
||||
QString("%1").arg(_testStartDateTime.time().minute(), 2, 10, QChar('0')) + ", on " +
|
||||
_testStartDateTime.date().toString("ddd, MMM d, yyyy"));
|
||||
|
||||
_statusLabel->setText("Installing");
|
||||
|
||||
// Kill any existing processes that would interfere with installation
|
||||
killProcesses();
|
||||
|
||||
runInstaller();
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::runInstaller() {
|
||||
// Qt cannot start an installation process using QProcess::start (Qt Bug 9761)
|
||||
// To allow installation, the installer is run using the `system` command
|
||||
|
||||
QStringList arguments{ QStringList() << QString("/S") << QString("/D=") + QDir::toNativeSeparators(_installationFolder) };
|
||||
|
||||
QString installerFullPath = _workingFolder + "/" + _installerFilename;
|
||||
|
||||
QString commandLine;
|
||||
#ifdef Q_OS_WIN
|
||||
commandLine = "\"" + QDir::toNativeSeparators(installerFullPath) + "\"" + " /S /D=" + QDir::toNativeSeparators(_installationFolder);
|
||||
#elif defined Q_OS_MAC
|
||||
// Create installation shell script
|
||||
QFile script;
|
||||
script.setFileName(_workingFolder + "/install_app.sh");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'install_app.sh'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (!QDir().exists(_installationFolder)) {
|
||||
QDir().mkdir(_installationFolder);
|
||||
}
|
||||
|
||||
// This script installs High Fidelity. It is run as "yes | install_app.sh... so "yes" is killed at the end
|
||||
script.write("#!/bin/sh\n\n");
|
||||
script.write("VOLUME=`hdiutil attach \"$1\" | grep Volumes | awk '{print $3}'`\n");
|
||||
|
||||
QString folderName {"High Fidelity"};
|
||||
if (!_runLatest->isChecked()) {
|
||||
folderName += QString(" - ") + getPRNumberFromURL(_url->text());
|
||||
}
|
||||
|
||||
script.write((QString("cp -rf \"$VOLUME/") + folderName + "/interface.app\" \"" + _workingFolder + "/High_Fidelity/\"\n").toStdString().c_str());
|
||||
script.write((QString("cp -rf \"$VOLUME/") + folderName + "/Sandbox.app\" \"" + _workingFolder + "/High_Fidelity/\"\n").toStdString().c_str());
|
||||
|
||||
script.write("hdiutil detach \"$VOLUME\"\n");
|
||||
script.write("killall yes\n");
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
commandLine = "yes | " + _workingFolder + "/install_app.sh " + installerFullPath;
|
||||
#endif
|
||||
appendLog(commandLine);
|
||||
_installerWorker->setCommandLine(commandLine);
|
||||
emit startInstaller();
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::installationComplete() {
|
||||
verifyInstallationSucceeded();
|
||||
|
||||
createSnapshotFolder();
|
||||
|
||||
_statusLabel->setText("Running tests");
|
||||
|
||||
if (!_runServerless->isChecked()) {
|
||||
startLocalServerProcesses();
|
||||
}
|
||||
|
||||
runInterfaceWithTestScript();
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::verifyInstallationSucceeded() {
|
||||
// Exit if the executables are missing.
|
||||
// On Windows, the reason is probably that UAC has blocked the installation. This is treated as a critical error
|
||||
#ifdef Q_OS_WIN
|
||||
QFileInfo interfaceExe(QDir::toNativeSeparators(_installationFolder) + "\\interface.exe");
|
||||
QFileInfo assignmentClientExe(QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe");
|
||||
QFileInfo domainServerExe(QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe");
|
||||
|
||||
if (!interfaceExe.exists() || !assignmentClientExe.exists() || !domainServerExe.exists()) {
|
||||
QMessageBox::critical(0, "Installation of High Fidelity has failed", "Please verify that UAC has been disabled");
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::saveExistingHighFidelityAppDataFolder() {
|
||||
QString dataDirectory{ "NOT FOUND" };
|
||||
#ifdef Q_OS_WIN
|
||||
dataDirectory = qgetenv("USERPROFILE") + "\\AppData\\Roaming";
|
||||
#elif defined Q_OS_MAC
|
||||
dataDirectory = QDir::homePath() + "/Library/Application Support";
|
||||
#endif
|
||||
if (_runLatest->isChecked()) {
|
||||
_appDataFolder = dataDirectory + "/High Fidelity";
|
||||
} else {
|
||||
// We are running a PR build
|
||||
_appDataFolder = dataDirectory + "/High Fidelity - " + getPRNumberFromURL(_url->text());
|
||||
}
|
||||
|
||||
_savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME;
|
||||
if (QDir(_savedAppDataFolder).exists()) {
|
||||
_savedAppDataFolder.removeRecursively();
|
||||
}
|
||||
if (_appDataFolder.exists()) {
|
||||
// The original folder is saved in a unique name
|
||||
_appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path());
|
||||
}
|
||||
|
||||
// Copy an "empty" AppData folder (i.e. no entities)
|
||||
QDir canonicalAppDataFolder;
|
||||
#ifdef Q_OS_WIN
|
||||
canonicalAppDataFolder = QDir::currentPath() + "/AppDataHighFidelity";
|
||||
#elif defined Q_OS_MAC
|
||||
canonicalAppDataFolder = QCoreApplication::applicationDirPath() + "/AppDataHighFidelity";
|
||||
#endif
|
||||
if (canonicalAppDataFolder.exists()) {
|
||||
copyFolder(canonicalAppDataFolder.path(), _appDataFolder.path());
|
||||
} else {
|
||||
QMessageBox::critical(0, "Internal error", "The nitpick AppData folder cannot be found at:\n" + canonicalAppDataFolder.path());
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::createSnapshotFolder() {
|
||||
_snapshotFolder = _workingFolder + "/" + SNAPSHOT_FOLDER_NAME;
|
||||
|
||||
// Just delete all PNGs from the folder if it already exists
|
||||
if (QDir(_snapshotFolder).exists()) {
|
||||
// Note that we cannot use just a `png` filter, as the filenames include periods
|
||||
// Also, delete any `jpg` and `txt` files
|
||||
// The idea is to leave only previous zipped result folders
|
||||
QDirIterator it(_snapshotFolder);
|
||||
while (it.hasNext()) {
|
||||
QString filename = it.next();
|
||||
if (filename.right(4) == ".png" || filename.right(4) == ".jpg" || filename.right(4) == ".txt") {
|
||||
QFile::remove(filename);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
QDir().mkdir(_snapshotFolder);
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::killProcesses() {
|
||||
#ifdef Q_OS_WIN
|
||||
try {
|
||||
QStringList processesToKill = QStringList() << "interface.exe"
|
||||
<< "assignment-client.exe"
|
||||
<< "domain-server.exe"
|
||||
<< "server-console.exe";
|
||||
|
||||
// Loop until all pending processes to kill have actually died
|
||||
QStringList pendingProcessesToKill;
|
||||
do {
|
||||
pendingProcessesToKill.clear();
|
||||
|
||||
// Get list of running tasks
|
||||
HANDLE processSnapHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (processSnapHandle == INVALID_HANDLE_VALUE) {
|
||||
throw("Process snapshot creation failure");
|
||||
}
|
||||
|
||||
PROCESSENTRY32 processEntry32;
|
||||
processEntry32.dwSize = sizeof(PROCESSENTRY32);
|
||||
if (!Process32First(processSnapHandle, &processEntry32)) {
|
||||
CloseHandle(processSnapHandle);
|
||||
throw("Process32First failed");
|
||||
}
|
||||
|
||||
// Kill any task in the list
|
||||
do {
|
||||
foreach (QString process, processesToKill)
|
||||
if (QString(processEntry32.szExeFile) == process) {
|
||||
QString commandLine = "taskkill /im " + process + " /f >nul";
|
||||
system(commandLine.toStdString().c_str());
|
||||
pendingProcessesToKill << process;
|
||||
}
|
||||
} while (Process32Next(processSnapHandle, &processEntry32));
|
||||
|
||||
QThread::sleep(2);
|
||||
} while (!pendingProcessesToKill.isEmpty());
|
||||
|
||||
} catch (QString errorMessage) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage);
|
||||
exit(-1);
|
||||
} catch (...) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error");
|
||||
exit(-1);
|
||||
}
|
||||
#elif defined Q_OS_MAC
|
||||
QString commandLine;
|
||||
|
||||
commandLine = QString("killall interface") + "; " + _workingFolder +"/waitForFinish.sh interface";
|
||||
system(commandLine.toStdString().c_str());
|
||||
|
||||
commandLine = QString("killall Sandbox") + "; " + _workingFolder +"/waitForFinish.sh Sandbox";
|
||||
system(commandLine.toStdString().c_str());
|
||||
|
||||
commandLine = QString("killall Console") + "; " + _workingFolder +"/waitForFinish.sh Console";
|
||||
system(commandLine.toStdString().c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::startLocalServerProcesses() {
|
||||
QString commandLine;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
commandLine =
|
||||
"start \"domain-server.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\domain-server.exe\"";
|
||||
system(commandLine.toStdString().c_str());
|
||||
|
||||
commandLine =
|
||||
"start \"assignment-client.exe\" \"" + QDir::toNativeSeparators(_installationFolder) + "\\assignment-client.exe\" -n 6";
|
||||
system(commandLine.toStdString().c_str());
|
||||
|
||||
#elif defined Q_OS_MAC
|
||||
commandLine = "open \"" +_installationFolder + "/Sandbox.app\"";
|
||||
system(commandLine.toStdString().c_str());
|
||||
#endif
|
||||
|
||||
// Give server processes time to stabilize
|
||||
QThread::sleep(20);
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::runInterfaceWithTestScript() {
|
||||
QString url = QString("hifi://localhost");
|
||||
if (_runServerless->isChecked()) {
|
||||
// Move to an empty area
|
||||
url = "file:///~serverless/tutorial.json";
|
||||
} else {
|
||||
url = "hifi://localhost";
|
||||
}
|
||||
|
||||
QString deleteScript =
|
||||
QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/utils/deleteNearbyEntities.js";
|
||||
|
||||
QString testScript =
|
||||
QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js";
|
||||
|
||||
QString commandLine;
|
||||
#ifdef Q_OS_WIN
|
||||
QString exeFile;
|
||||
// First, run script to delete any entities in test area
|
||||
// Note that this will run to completion before continuing
|
||||
exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\"";
|
||||
commandLine = "start /wait \"\" " + exeFile +
|
||||
" --url " + url +
|
||||
" --no-updater" +
|
||||
" --no-login-suggestion"
|
||||
" --testScript " + deleteScript + " quitWhenFinished";
|
||||
|
||||
system(commandLine.toStdString().c_str());
|
||||
|
||||
// Now run the test suite
|
||||
exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\"";
|
||||
commandLine = exeFile +
|
||||
" --url " + url +
|
||||
" --no-updater" +
|
||||
" --no-login-suggestion"
|
||||
" --testScript " + testScript + " quitWhenFinished" +
|
||||
" --testResultsLocation " + _snapshotFolder;
|
||||
|
||||
_interfaceWorker->setCommandLine(commandLine);
|
||||
emit startInterface();
|
||||
#elif defined Q_OS_MAC
|
||||
QFile script;
|
||||
script.setFileName(_workingFolder + "/runInterfaceTests.sh");
|
||||
if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
|
||||
"Could not open 'runInterfaceTests.sh'");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
script.write("#!/bin/sh\n\n");
|
||||
|
||||
// First, run script to delete any entities in test area
|
||||
commandLine =
|
||||
"open -W \"" +_installationFolder + "/interface.app\" --args" +
|
||||
" --url " + url +
|
||||
" --no-updater" +
|
||||
" --no-login-suggestion"
|
||||
" --testScript " + deleteScript + " quitWhenFinished\n";
|
||||
|
||||
script.write(commandLine.toStdString().c_str());
|
||||
|
||||
// On The Mac, we need to resize Interface. The Interface window opens a few seconds after the process
|
||||
// has started.
|
||||
// Before starting interface, start a process that will resize interface 10s after it opens
|
||||
commandLine = _workingFolder +"/waitForStart.sh interface && sleep 10 && " + _workingFolder +"/setInterfaceSizeAndPosition.sh &\n";
|
||||
script.write(commandLine.toStdString().c_str());
|
||||
|
||||
commandLine =
|
||||
"open \"" +_installationFolder + "/interface.app\" --args" +
|
||||
" --url " + url +
|
||||
" --no-updater" +
|
||||
" --no-login-suggestion"
|
||||
" --testScript " + testScript + " quitWhenFinished" +
|
||||
" --testResultsLocation " + _snapshotFolder +
|
||||
" && " + _workingFolder +"/waitForFinish.sh interface\n";
|
||||
|
||||
script.write(commandLine.toStdString().c_str());
|
||||
|
||||
script.close();
|
||||
script.setPermissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner);
|
||||
|
||||
commandLine = _workingFolder + "/runInterfaceTests.sh";
|
||||
_interfaceWorker->setCommandLine(commandLine);
|
||||
|
||||
emit startInterface();
|
||||
#endif
|
||||
|
||||
// Helpful for debugging
|
||||
appendLog(commandLine);
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::interfaceExecutionComplete() {
|
||||
QFileInfo testCompleted(QDir::toNativeSeparators(_snapshotFolder) +"/tests_completed.txt");
|
||||
if (!testCompleted.exists()) {
|
||||
QMessageBox::critical(0, "Tests not completed", "Interface seems to have crashed before completion of the test scripts\nExisting images will be evaluated");
|
||||
}
|
||||
|
||||
evaluateResults();
|
||||
|
||||
killProcesses();
|
||||
|
||||
// The High Fidelity AppData folder will be restored after evaluation has completed
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::evaluateResults() {
|
||||
_statusLabel->setText("Evaluating results");
|
||||
nitpick->startTestsEvaluation(false, true, _snapshotFolder, _branch, _user);
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::automaticTestRunEvaluationComplete(QString zippedFolder, int numberOfFailures) {
|
||||
addBuildNumberToResults(zippedFolder);
|
||||
restoreHighFidelityAppDataFolder();
|
||||
|
||||
_statusLabel->setText("Testing complete");
|
||||
|
||||
QDateTime currentDateTime = QDateTime::currentDateTime();
|
||||
|
||||
QString completionText = QString("Tests completed at ") + QString::number(currentDateTime.time().hour()) + ":" +
|
||||
QString("%1").arg(currentDateTime.time().minute(), 2, 10, QChar('0')) + ", on " +
|
||||
currentDateTime.date().toString("ddd, MMM d, yyyy");
|
||||
|
||||
if (numberOfFailures == 0) {
|
||||
completionText += "; no failures";
|
||||
} else if (numberOfFailures == 1) {
|
||||
completionText += "; 1 failure";
|
||||
} else {
|
||||
completionText += QString("; ") + QString::number(numberOfFailures) + " failures";
|
||||
}
|
||||
appendLog(completionText);
|
||||
|
||||
_automatedTestIsRunning = false;
|
||||
|
||||
_runNow->setEnabled(true);
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::addBuildNumberToResults(QString zippedFolderName) {
|
||||
QString augmentedFilename;
|
||||
if (!_runLatest->isChecked()) {
|
||||
augmentedFilename = zippedFolderName.replace("local", getPRNumberFromURL(_url->text()));
|
||||
} else {
|
||||
augmentedFilename = zippedFolderName.replace("local", _buildInformation.build);
|
||||
}
|
||||
QFile::rename(zippedFolderName, augmentedFilename);
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::restoreHighFidelityAppDataFolder() {
|
||||
_appDataFolder.removeRecursively();
|
||||
|
||||
if (_savedAppDataFolder != QDir()) {
|
||||
_appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path());
|
||||
}
|
||||
}
|
||||
|
||||
// Copies a folder recursively
|
||||
void TestRunnerDesktop::copyFolder(const QString& source, const QString& destination) {
|
||||
try {
|
||||
if (!QFileInfo(source).isDir()) {
|
||||
// just a file copy
|
||||
QFile::copy(source, destination);
|
||||
} else {
|
||||
QDir destinationDir(destination);
|
||||
if (!destinationDir.cdUp()) {
|
||||
throw("'source '" + source + "'seems to be a root folder");
|
||||
}
|
||||
|
||||
if (!destinationDir.mkdir(QFileInfo(destination).fileName())) {
|
||||
throw("Could not create destination folder '" + destination + "'");
|
||||
}
|
||||
|
||||
QStringList fileNames =
|
||||
QDir(source).entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
|
||||
|
||||
foreach (const QString& fileName, fileNames) {
|
||||
copyFolder(QString(source + "/" + fileName), QString(destination + "/" + fileName));
|
||||
}
|
||||
}
|
||||
} catch (QString errorMessage) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage);
|
||||
exit(-1);
|
||||
} catch (...) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunnerDesktop::checkTime() {
|
||||
// No processing is done if a test is running
|
||||
if (_automatedTestIsRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
|
||||
// Check day of week
|
||||
if (!_dayCheckboxes.at(now.date().dayOfWeek() - 1)->isChecked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the time
|
||||
bool timeToRun{ false };
|
||||
|
||||
for (size_t i = 0; i < std::min(_timeEditCheckboxes.size(), _timeEdits.size()); ++i) {
|
||||
if (_timeEditCheckboxes[i]->isChecked() && (_timeEdits[i]->time().hour() == now.time().hour()) &&
|
||||
(_timeEdits[i]->time().minute() == now.time().minute())) {
|
||||
timeToRun = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (timeToRun) {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
QString TestRunnerDesktop::getPRNumberFromURL(const QString& url) {
|
||||
try {
|
||||
QStringList urlParts = url.split("/");
|
||||
QStringList filenameParts = urlParts[urlParts.size() - 1].split("-");
|
||||
if (filenameParts.size() <= 3) {
|
||||
#ifdef Q_OS_WIN
|
||||
throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`";
|
||||
#elif defined Q_OS_MAC
|
||||
throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.dmg`";
|
||||
#endif
|
||||
}
|
||||
return filenameParts[filenameParts.size() - 2];
|
||||
} catch (QString errorMessage) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage);
|
||||
exit(-1);
|
||||
} catch (...) {
|
||||
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "unknown error");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
124
tools/nitpick/src/TestRunnerDesktop.h
Normal file
124
tools/nitpick/src/TestRunnerDesktop.h
Normal file
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// TestRunnerDesktop.h
|
||||
//
|
||||
// Created by Nissim Hadar on 1 Sept 2018.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_testRunnerDesktop_h
|
||||
#define hifi_testRunnerDesktop_h
|
||||
|
||||
#include <QDir>
|
||||
#include <QLabel>
|
||||
#include <QObject>
|
||||
#include <QPushButton>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
|
||||
#include "TestRunner.h"
|
||||
|
||||
class InterfaceWorker;
|
||||
class InstallerWorker;
|
||||
|
||||
class TestRunnerDesktop : public QObject, public TestRunner {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TestRunnerDesktop(
|
||||
std::vector<QCheckBox*> dayCheckboxes,
|
||||
std::vector<QCheckBox*> timeEditCheckboxes,
|
||||
std::vector<QTimeEdit*> timeEdits,
|
||||
QLabel* workingFolderLabel,
|
||||
QCheckBox* runServerless,
|
||||
QCheckBox* runLatest,
|
||||
QLineEdit* url,
|
||||
QPushButton* runNow,
|
||||
QLabel* statusLabel,
|
||||
|
||||
QObject* parent = 0
|
||||
);
|
||||
|
||||
~TestRunnerDesktop();
|
||||
|
||||
void setWorkingFolderAndEnableControls();
|
||||
|
||||
void run();
|
||||
|
||||
void downloadComplete();
|
||||
void runInstaller();
|
||||
void verifyInstallationSucceeded();
|
||||
|
||||
void saveExistingHighFidelityAppDataFolder();
|
||||
void restoreHighFidelityAppDataFolder();
|
||||
|
||||
void createSnapshotFolder();
|
||||
|
||||
void killProcesses();
|
||||
void startLocalServerProcesses();
|
||||
|
||||
void runInterfaceWithTestScript();
|
||||
|
||||
void evaluateResults();
|
||||
void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures);
|
||||
void addBuildNumberToResults(QString zippedFolderName);
|
||||
|
||||
void copyFolder(const QString& source, const QString& destination);
|
||||
|
||||
QString getPRNumberFromURL(const QString& url);
|
||||
|
||||
private slots:
|
||||
void checkTime();
|
||||
void installationComplete();
|
||||
void interfaceExecutionComplete();
|
||||
|
||||
signals:
|
||||
void startInstaller();
|
||||
void startInterface();
|
||||
void startResize();
|
||||
|
||||
private:
|
||||
bool _automatedTestIsRunning{ false };
|
||||
|
||||
QString _installerURL;
|
||||
QString _installerFilename;
|
||||
|
||||
QDir _appDataFolder;
|
||||
QDir _savedAppDataFolder;
|
||||
|
||||
QString _installationFolder;
|
||||
QString _snapshotFolder;
|
||||
|
||||
const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" };
|
||||
const QString SNAPSHOT_FOLDER_NAME{ "snapshots" };
|
||||
|
||||
QString _branch;
|
||||
QString _user;
|
||||
|
||||
std::vector<QCheckBox*> _dayCheckboxes;
|
||||
std::vector<QCheckBox*> _timeEditCheckboxes;
|
||||
std::vector<QTimeEdit*> _timeEdits;
|
||||
QLabel* _workingFolderLabel;
|
||||
QCheckBox* _runServerless;
|
||||
QPushButton* _runNow;
|
||||
QTimer* _timer;
|
||||
QThread* _installerThread;
|
||||
QThread* _interfaceThread;
|
||||
|
||||
InstallerWorker* _installerWorker;
|
||||
InterfaceWorker* _interfaceWorker;
|
||||
};
|
||||
|
||||
class InstallerWorker : public Worker {
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void startInstaller();
|
||||
};
|
||||
|
||||
class InterfaceWorker : public Worker {
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void startInterface();
|
||||
};
|
||||
#endif
|
196
tools/nitpick/src/TestRunnerMobile.cpp
Normal file
196
tools/nitpick/src/TestRunnerMobile.cpp
Normal file
|
@ -0,0 +1,196 @@
|
|||
//
|
||||
// TestRunnerMobile.cpp
|
||||
//
|
||||
// Created by Nissim Hadar on 22 Jan 2019.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "TestRunnerMobile.h"
|
||||
|
||||
#include <QThread>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
|
||||
#include "Nitpick.h"
|
||||
extern Nitpick* nitpick;
|
||||
|
||||
TestRunnerMobile::TestRunnerMobile(
|
||||
QLabel* workingFolderLabel,
|
||||
QPushButton *connectDeviceButton,
|
||||
QPushButton *pullFolderButton,
|
||||
QLabel* detectedDeviceLabel,
|
||||
QLineEdit *folderLineEdit,
|
||||
QPushButton* downloadAPKPushbutton,
|
||||
QPushButton* installAPKPushbutton,
|
||||
QPushButton* runInterfacePushbutton,
|
||||
QCheckBox* runLatest,
|
||||
QLineEdit* url,
|
||||
QLabel* statusLabel,
|
||||
|
||||
QObject* parent
|
||||
) : QObject(parent)
|
||||
{
|
||||
_workingFolderLabel = workingFolderLabel;
|
||||
_connectDeviceButton = connectDeviceButton;
|
||||
_pullFolderButton = pullFolderButton;
|
||||
_detectedDeviceLabel = detectedDeviceLabel;
|
||||
_folderLineEdit = folderLineEdit;
|
||||
_downloadAPKPushbutton = downloadAPKPushbutton;
|
||||
_installAPKPushbutton = installAPKPushbutton;
|
||||
_runInterfacePushbutton = runInterfacePushbutton;
|
||||
_runLatest = runLatest;
|
||||
_url = url;
|
||||
_statusLabel = statusLabel;
|
||||
|
||||
folderLineEdit->setText("/sdcard/DCIM/TEST");
|
||||
|
||||
modelNames["SM_G955U1"] = "Samsung S8+ unlocked";
|
||||
|
||||
// Find ADB (Android Debugging Bridge)
|
||||
#ifdef Q_OS_WIN
|
||||
if (QProcessEnvironment::systemEnvironment().contains("ADB_PATH")) {
|
||||
QString adbExePath = QProcessEnvironment::systemEnvironment().value("ADB_PATH") + "/platform-tools";
|
||||
if (!QFile::exists(adbExePath + "/" + _adbExe)) {
|
||||
QMessageBox::critical(0, _adbExe, QString("ADB executable not found in ") + adbExePath);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
_adbCommand = adbExePath + "/" + _adbExe;
|
||||
} else {
|
||||
QMessageBox::critical(0, "ADB_PATH not defined",
|
||||
"Please set ADB_PATH to directory containing the `adb` executable");
|
||||
exit(-1);
|
||||
}
|
||||
#elif defined Q_OS_MAC
|
||||
_adbCommand = "/usr/local/bin/adb";
|
||||
if (!QFile::exists(_adbCommand)) {
|
||||
QMessageBox::critical(0, "adb not found",
|
||||
"python3 not found at " + _adbCommand);
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TestRunnerMobile::~TestRunnerMobile() {
|
||||
}
|
||||
|
||||
void TestRunnerMobile::setWorkingFolderAndEnableControls() {
|
||||
setWorkingFolder(_workingFolderLabel);
|
||||
|
||||
_connectDeviceButton->setEnabled(true);
|
||||
}
|
||||
|
||||
void TestRunnerMobile::connectDevice() {
|
||||
#if defined Q_OS_WIN || defined Q_OS_MAC
|
||||
QString devicesFullFilename{ _workingFolder + "/devices.txt" };
|
||||
QString command = _adbCommand + " devices -l > " + devicesFullFilename;
|
||||
system(command.toStdString().c_str());
|
||||
|
||||
if (!QFile::exists(devicesFullFilename)) {
|
||||
QMessageBox::critical(0, "Internal error", "devicesFullFilename not found");
|
||||
exit (-1);
|
||||
}
|
||||
|
||||
// Device should be in second line
|
||||
QFile devicesFile(devicesFullFilename);
|
||||
devicesFile.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
QString line1 = devicesFile.readLine();
|
||||
QString line2 = devicesFile.readLine();
|
||||
|
||||
const QString DEVICE{ "device" };
|
||||
if (line2.contains(DEVICE)) {
|
||||
// Make sure only 1 device
|
||||
QString line3 = devicesFile.readLine();
|
||||
if (line3.contains(DEVICE)) {
|
||||
QMessageBox::critical(0, "Too many devices detected", "Tests will run only if a single device is attached");
|
||||
|
||||
} else {
|
||||
// Line looks like this: 988a1b47335239434b device product:dream2qlteue model:SM_G955U1 device:dream2qlteue transport_id:2
|
||||
QStringList tokens = line2.split(QRegExp("[\r\n\t ]+"));
|
||||
QString deviceID = tokens[0];
|
||||
|
||||
QString modelID = tokens[3].split(':')[1];
|
||||
QString modelName = "UKNOWN";
|
||||
if (modelNames.count(modelID) == 1) {
|
||||
modelName = modelNames[modelID];
|
||||
}
|
||||
|
||||
_detectedDeviceLabel->setText(modelName + " [" + deviceID + "]");
|
||||
_pullFolderButton->setEnabled(true);
|
||||
_folderLineEdit->setEnabled(true);
|
||||
_downloadAPKPushbutton->setEnabled(true);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestRunnerMobile::downloadAPK() {
|
||||
downloadBuildXml((void*)this);
|
||||
}
|
||||
|
||||
|
||||
void TestRunnerMobile::downloadComplete() {
|
||||
if (!buildXMLDownloaded) {
|
||||
// Download of Build XML has completed
|
||||
buildXMLDownloaded = true;
|
||||
|
||||
// Download the High Fidelity installer
|
||||
QStringList urls;
|
||||
QStringList filenames;
|
||||
if (_runLatest->isChecked()) {
|
||||
parseBuildInformation();
|
||||
|
||||
_installerFilename = INSTALLER_FILENAME_LATEST;
|
||||
|
||||
|
||||
// Replace the `exe` extension with `apk`
|
||||
_installerFilename = _installerFilename.replace(_installerFilename.length() - 3, 3, "apk");
|
||||
_buildInformation.url = _buildInformation.url.replace(_buildInformation.url.length() - 3, 3, "apk");
|
||||
|
||||
urls << _buildInformation.url;
|
||||
filenames << _installerFilename;
|
||||
} else {
|
||||
QString urlText = _url->text();
|
||||
urls << urlText;
|
||||
_installerFilename = getInstallerNameFromURL(urlText);
|
||||
filenames << _installerFilename;
|
||||
}
|
||||
|
||||
_statusLabel->setText("Downloading installer");
|
||||
|
||||
nitpick->downloadFiles(urls, _workingFolder, filenames, (void*)this);
|
||||
} else {
|
||||
_statusLabel->setText("Installer download complete");
|
||||
_installAPKPushbutton->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void TestRunnerMobile::installAPK() {
|
||||
#if defined Q_OS_WIN || defined Q_OS_MAC
|
||||
_statusLabel->setText("Installing");
|
||||
QString command = _adbCommand + " install -r -d " + _workingFolder + "/" + _installerFilename + " >" + _workingFolder + "/installOutput.txt";
|
||||
system(command.toStdString().c_str());
|
||||
_statusLabel->setText("Installation complete");
|
||||
_runInterfacePushbutton->setEnabled(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestRunnerMobile::runInterface() {
|
||||
#if defined Q_OS_WIN || defined Q_OS_MAC
|
||||
_statusLabel->setText("Starting Interface");
|
||||
QString command = _adbCommand + " shell monkey -p io.highfidelity.hifiinterface -v 1";
|
||||
system(command.toStdString().c_str());
|
||||
_statusLabel->setText("Interface started");
|
||||
#endif
|
||||
}
|
||||
|
||||
void TestRunnerMobile::pullFolder() {
|
||||
#if defined Q_OS_WIN || defined Q_OS_MAC
|
||||
_statusLabel->setText("Pulling folder");
|
||||
QString command = _adbCommand + " pull " + _folderLineEdit->text() + " " + _workingFolder + _installerFilename;
|
||||
system(command.toStdString().c_str());
|
||||
_statusLabel->setText("Pull complete");
|
||||
#endif
|
||||
}
|
74
tools/nitpick/src/TestRunnerMobile.h
Normal file
74
tools/nitpick/src/TestRunnerMobile.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// TestRunnerMobile.h
|
||||
//
|
||||
// Created by Nissim Hadar on 22 Jan 2019.
|
||||
// Copyright 2013 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_testRunnerMobile_h
|
||||
#define hifi_testRunnerMobile_h
|
||||
|
||||
#include <QMap>
|
||||
#include <QLabel>
|
||||
#include <QObject>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "TestRunner.h"
|
||||
|
||||
class TestRunnerMobile : public QObject, public TestRunner {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TestRunnerMobile(
|
||||
QLabel* workingFolderLabel,
|
||||
QPushButton *connectDeviceButton,
|
||||
QPushButton *pullFolderButton,
|
||||
QLabel* detectedDeviceLabel,
|
||||
QLineEdit *folderLineEdit,
|
||||
QPushButton* downloadAPKPushbutton,
|
||||
QPushButton* installAPKPushbutton,
|
||||
QPushButton* runInterfacePushbutton,
|
||||
QCheckBox* runLatest,
|
||||
QLineEdit* url,
|
||||
QLabel* statusLabel,
|
||||
|
||||
QObject* parent = 0
|
||||
);
|
||||
~TestRunnerMobile();
|
||||
|
||||
void setWorkingFolderAndEnableControls();
|
||||
void connectDevice();
|
||||
|
||||
void downloadComplete();
|
||||
void downloadAPK();
|
||||
void runInterface();
|
||||
|
||||
void installAPK();
|
||||
|
||||
void pullFolder();
|
||||
|
||||
private:
|
||||
QPushButton* _connectDeviceButton;
|
||||
QPushButton* _pullFolderButton;
|
||||
QLabel* _detectedDeviceLabel;
|
||||
QLineEdit* _folderLineEdit;
|
||||
QPushButton* _downloadAPKPushbutton;
|
||||
QPushButton* _installAPKPushbutton;
|
||||
QPushButton* _runInterfacePushbutton;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
const QString _adbExe{ "adb.exe" };
|
||||
#else
|
||||
// Both Mac and Linux use "adb"
|
||||
const QString _adbExe{ "adb" };
|
||||
#endif
|
||||
|
||||
QString _installerFilename;
|
||||
|
||||
QString _adbCommand;
|
||||
|
||||
std::map<QString, QString> modelNames;
|
||||
};
|
||||
#endif
|
|
@ -8,7 +8,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include <QtWidgets/QApplication>
|
||||
#include "ui/Nitpick.h"
|
||||
#include "Nitpick.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<string>Nitpick</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralWidget">
|
||||
<widget class="QPushButton" name="closeButton">
|
||||
<widget class="QPushButton" name="closePushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>470</x>
|
||||
|
@ -43,16 +43,16 @@
|
|||
</rect>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>3</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab_1">
|
||||
<attribute name="title">
|
||||
<string>Create</string>
|
||||
</attribute>
|
||||
<widget class="QPushButton" name="createTestsButton">
|
||||
<widget class="QPushButton" name="createTestsPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>195</x>
|
||||
<x>210</x>
|
||||
<y>60</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
|
@ -62,7 +62,7 @@
|
|||
<string>Create Tests</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createMDFileButton">
|
||||
<widget class="QPushButton" name="createMDFilePushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
|
@ -75,7 +75,7 @@
|
|||
<string>Create MD file</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createAllMDFilesButton">
|
||||
<widget class="QPushButton" name="createAllMDFilesPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>320</x>
|
||||
|
@ -88,10 +88,10 @@
|
|||
<string>Create all MD files</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createTestsOutlineButton">
|
||||
<widget class="QPushButton" name="createTestsOutlinePushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>195</x>
|
||||
<x>210</x>
|
||||
<y>120</y>
|
||||
<width>220</width>
|
||||
<height>40</height>
|
||||
|
@ -101,7 +101,7 @@
|
|||
<string>Create Tests Outline</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createRecursiveScriptButton">
|
||||
<widget class="QPushButton" name="createRecursiveScriptPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
|
@ -114,7 +114,7 @@
|
|||
<string>Create Recursive Script</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createAllRecursiveScriptsButton">
|
||||
<widget class="QPushButton" name="createAllRecursiveScriptsPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>320</x>
|
||||
|
@ -127,7 +127,7 @@
|
|||
<string>Create all Recursive Scripts</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createTestAutoScriptButton">
|
||||
<widget class="QPushButton" name="createTestAutoScriptPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>70</x>
|
||||
|
@ -140,7 +140,7 @@
|
|||
<string>Create testAuto script</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createAllTestAutoScriptsButton">
|
||||
<widget class="QPushButton" name="createAllTestAutoScriptsPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>320</x>
|
||||
|
@ -158,7 +158,7 @@
|
|||
<attribute name="title">
|
||||
<string>Windows</string>
|
||||
</attribute>
|
||||
<widget class="QPushButton" name="hideTaskbarButton">
|
||||
<widget class="QPushButton" name="hideTaskbarPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
|
@ -171,7 +171,7 @@
|
|||
<string>Hide Windows Taskbar</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="showTaskbarButton">
|
||||
<widget class="QPushButton" name="showTaskbarPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>200</x>
|
||||
|
@ -187,9 +187,9 @@
|
|||
</widget>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Run</string>
|
||||
<string>Test on Desktop</string>
|
||||
</attribute>
|
||||
<widget class="QPushButton" name="runNowButton">
|
||||
<widget class="QPushButton" name="runNowPushbutton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
|
@ -420,26 +420,26 @@
|
|||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="setWorkingFolderButton">
|
||||
<widget class="QPushButton" name="setWorkingFolderRunOnDesktopPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>20</y>
|
||||
<width>161</width>
|
||||
<height>28</height>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set Working Folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="workingFolderLabel">
|
||||
<widget class="QLabel" name="workingFolderRunOnDesktopLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>190</x>
|
||||
<y>20</y>
|
||||
<width>321</width>
|
||||
<height>31</height>
|
||||
<width>320</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
|
@ -469,7 +469,7 @@
|
|||
<string>Status:</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="statusLabel">
|
||||
<widget class="QLabel" name="statusLabelOnDesktop">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>350</x>
|
||||
|
@ -501,7 +501,7 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="checkBoxRunLatest">
|
||||
<widget class="QCheckBox" name="runLatestOnDesktopCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
|
@ -533,7 +533,7 @@
|
|||
<string>URL</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="urlLineEdit">
|
||||
<widget class="QLineEdit" name="urlOnDesktopLineEdit">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
|
@ -547,6 +547,201 @@
|
|||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_5">
|
||||
<attribute name="title">
|
||||
<string>Test on Mobile</string>
|
||||
</attribute>
|
||||
<widget class="QPushButton" name="connectDevicePushbutton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>90</y>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Connect Device</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="detectedDeviceLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>190</x>
|
||||
<y>96</y>
|
||||
<width>320</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>(not detected)</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="setWorkingFolderRunOnMobilePushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>20</y>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set Working Folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="workingFolderRunOnMobileLabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>190</x>
|
||||
<y>20</y>
|
||||
<width>320</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>(not set...)</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="pullFolderPushbutton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>460</x>
|
||||
<y>410</y>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Pull folder</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="folderLineEdit">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>410</y>
|
||||
<width>440</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="urlOnMobileLineEdit">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>170</x>
|
||||
<y>170</y>
|
||||
<width>451</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QCheckBox" name="runLatestOnMobileCheckBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>170</y>
|
||||
<width>120</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Run Latest</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="downloadAPKPushbutton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>210</y>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Download APK</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="workingFolderLabel_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>290</x>
|
||||
<y>20</y>
|
||||
<width>41</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Status:</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="statusLabelOnMobile">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>340</x>
|
||||
<y>20</y>
|
||||
<width>271</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>#######</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="installAPKPushbutton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>250</y>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Install APK</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="runInterfacePushbutton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>300</y>
|
||||
<width>160</width>
|
||||
<height>30</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Run Interface</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Evaluate</string>
|
||||
|
@ -567,7 +762,7 @@
|
|||
<string>Interactive Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="evaluateTestsButton">
|
||||
<widget class="QPushButton" name="evaluateTestsPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>330</x>
|
||||
|
@ -585,7 +780,7 @@
|
|||
<attribute name="title">
|
||||
<string>Web Interface</string>
|
||||
</attribute>
|
||||
<widget class="QPushButton" name="updateTestRailRunResultsButton">
|
||||
<widget class="QPushButton" name="updateTestRailRunResultsPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
|
@ -614,7 +809,7 @@
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createTestRailRunButton">
|
||||
<widget class="QPushButton" name="createTestRailRunPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
|
@ -627,7 +822,7 @@
|
|||
<string>Create Run</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="createTestRailTestCasesButton">
|
||||
<widget class="QPushButton" name="createTestRailTestCasesPushbutton">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>240</x>
|
||||
|
@ -678,7 +873,7 @@
|
|||
<property name="title">
|
||||
<string>Amazon Web Services</string>
|
||||
</property>
|
||||
<widget class="QPushButton" name="createWebPagePushButton">
|
||||
<widget class="QPushButton" name="createWebPagePushbutton">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
|
@ -719,10 +914,10 @@
|
|||
</widget>
|
||||
</widget>
|
||||
<zorder>groupBox</zorder>
|
||||
<zorder>updateTestRailRunResultsButton</zorder>
|
||||
<zorder>updateTestRailRunResultsPushbutton</zorder>
|
||||
<zorder>createPythonScriptRadioButton</zorder>
|
||||
<zorder>createTestRailRunButton</zorder>
|
||||
<zorder>createTestRailTestCasesButton</zorder>
|
||||
<zorder>createTestRailRunPushbutton</zorder>
|
||||
<zorder>createTestRailTestCasesPushbutton</zorder>
|
||||
<zorder>createXMLScriptRadioButton</zorder>
|
||||
<zorder>groupBox_2</zorder>
|
||||
</widget>
|
||||
|
@ -851,17 +1046,17 @@
|
|||
<tabstops>
|
||||
<tabstop>userLineEdit</tabstop>
|
||||
<tabstop>branchLineEdit</tabstop>
|
||||
<tabstop>createTestsButton</tabstop>
|
||||
<tabstop>createMDFileButton</tabstop>
|
||||
<tabstop>createAllMDFilesButton</tabstop>
|
||||
<tabstop>createTestsOutlineButton</tabstop>
|
||||
<tabstop>createRecursiveScriptButton</tabstop>
|
||||
<tabstop>createAllRecursiveScriptsButton</tabstop>
|
||||
<tabstop>createTestAutoScriptButton</tabstop>
|
||||
<tabstop>createAllTestAutoScriptsButton</tabstop>
|
||||
<tabstop>hideTaskbarButton</tabstop>
|
||||
<tabstop>showTaskbarButton</tabstop>
|
||||
<tabstop>runNowButton</tabstop>
|
||||
<tabstop>createTestsPushbutton</tabstop>
|
||||
<tabstop>createMDFilePushbutton</tabstop>
|
||||
<tabstop>createAllMDFilesPushbutton</tabstop>
|
||||
<tabstop>createTestsOutlinePushbutton</tabstop>
|
||||
<tabstop>createRecursiveScriptPushbutton</tabstop>
|
||||
<tabstop>createAllRecursiveScriptsPushbutton</tabstop>
|
||||
<tabstop>createTestAutoScriptPushbutton</tabstop>
|
||||
<tabstop>createAllTestAutoScriptsPushbutton</tabstop>
|
||||
<tabstop>hideTaskbarPushbutton</tabstop>
|
||||
<tabstop>showTaskbarPushbutton</tabstop>
|
||||
<tabstop>runNowPushbutton</tabstop>
|
||||
<tabstop>sundayCheckBox</tabstop>
|
||||
<tabstop>wednesdayCheckBox</tabstop>
|
||||
<tabstop>tuesdayCheckBox</tabstop>
|
||||
|
@ -877,22 +1072,22 @@
|
|||
<tabstop>timeEdit2checkBox</tabstop>
|
||||
<tabstop>timeEdit3checkBox</tabstop>
|
||||
<tabstop>timeEdit4checkBox</tabstop>
|
||||
<tabstop>setWorkingFolderButton</tabstop>
|
||||
<tabstop>setWorkingFolderRunOnDesktopPushbutton</tabstop>
|
||||
<tabstop>plainTextEdit</tabstop>
|
||||
<tabstop>checkBoxServerless</tabstop>
|
||||
<tabstop>checkBoxRunLatest</tabstop>
|
||||
<tabstop>urlLineEdit</tabstop>
|
||||
<tabstop>runLatestOnDesktopCheckBox</tabstop>
|
||||
<tabstop>urlOnDesktopLineEdit</tabstop>
|
||||
<tabstop>checkBoxInteractiveMode</tabstop>
|
||||
<tabstop>evaluateTestsButton</tabstop>
|
||||
<tabstop>updateTestRailRunResultsButton</tabstop>
|
||||
<tabstop>evaluateTestsPushbutton</tabstop>
|
||||
<tabstop>updateTestRailRunResultsPushbutton</tabstop>
|
||||
<tabstop>createPythonScriptRadioButton</tabstop>
|
||||
<tabstop>createTestRailRunButton</tabstop>
|
||||
<tabstop>createTestRailTestCasesButton</tabstop>
|
||||
<tabstop>createTestRailRunPushbutton</tabstop>
|
||||
<tabstop>createTestRailTestCasesPushbutton</tabstop>
|
||||
<tabstop>createXMLScriptRadioButton</tabstop>
|
||||
<tabstop>createWebPagePushButton</tabstop>
|
||||
<tabstop>createWebPagePushbutton</tabstop>
|
||||
<tabstop>updateAWSCheckBox</tabstop>
|
||||
<tabstop>awsURLLineEdit</tabstop>
|
||||
<tabstop>closeButton</tabstop>
|
||||
<tabstop>closePushbutton</tabstop>
|
||||
<tabstop>tabWidget</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
Loading…
Reference in a new issue