diff --git a/CMakeLists.txt b/CMakeLists.txt index d3ac2bff50..d0a2e57dd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,7 +81,7 @@ if (ANDROID) set(GLES_OPTION ON) set(PLATFORM_QT_COMPONENTS AndroidExtras WebView) else () - set(PLATFORM_QT_COMPONENTS WebEngine Xml) + set(PLATFORM_QT_COMPONENTS WebEngine) endif () if (USE_GLES AND (NOT ANDROID)) diff --git a/INSTALL.md b/INSTALL.md index 985a76fe89..bcbf93eef3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -32,8 +32,8 @@ To produce an executable installer on Windows, the following are required: 1. [InetC Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0 1. Extract Zip - 1. Copy `Plugins\x86-ansi\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` - 1. Copy `Plugins\x86-unicode\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\` + 1. Copy `Plugin\x86-ansi\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-ansi\` + 1. Copy `Plugin\x86-unicode\InetC.dll` to `C:\Program Files (x86)\NSIS\Plugins\x86-unicode\` 1. [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0 1. Extract Zip diff --git a/cmake/macros/FixupNitpick.cmake b/cmake/macros/FixupNitpick.cmake deleted file mode 100644 index 8477b17823..0000000000 --- a/cmake/macros/FixupNitpick.cmake +++ /dev/null @@ -1,36 +0,0 @@ -# -# 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() diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 3ebc44e931..1b7243d4f2 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -77,9 +77,6 @@ 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 @@ -143,9 +140,8 @@ 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") @@ -163,14 +159,11 @@ 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 ".") @@ -180,7 +173,6 @@ 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") diff --git a/tools/nitpick/compiledResources/resources.rcc b/tools/nitpick/compiledResources/resources.rcc deleted file mode 100644 index 15f51ed7f4..0000000000 Binary files a/tools/nitpick/compiledResources/resources.rcc and /dev/null differ diff --git a/tools/nitpick/icon/nitpick.icns b/tools/nitpick/icon/nitpick.icns deleted file mode 100644 index 332539af2a..0000000000 Binary files a/tools/nitpick/icon/nitpick.icns and /dev/null differ diff --git a/tools/nitpick/icon/nitpick.ico b/tools/nitpick/icon/nitpick.ico deleted file mode 100644 index e3d852cb41..0000000000 Binary files a/tools/nitpick/icon/nitpick.ico and /dev/null differ diff --git a/tools/nitpick/src/AWSInterface.h b/tools/nitpick/src/AWSInterface.h index 63c48580f5..fda250b115 100644 --- a/tools/nitpick/src/AWSInterface.h +++ b/tools/nitpick/src/AWSInterface.h @@ -16,7 +16,7 @@ #include #include -#include "BusyWindow.h" +#include "ui/BusyWindow.h" #include "PythonInterface.h" diff --git a/tools/nitpick/src/PythonInterface.cpp b/tools/nitpick/src/PythonInterface.cpp index 9e2fec005f..9832ac9f8d 100644 --- a/tools/nitpick/src/PythonInterface.cpp +++ b/tools/nitpick/src/PythonInterface.cpp @@ -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 not found", + QMessageBox::critical(0, "PYTHON_PATH not defined", "python3 not found at " + _pythonCommand); exit(-1); } diff --git a/tools/nitpick/src/Test.cpp b/tools/nitpick/src/Test.cpp index 2e62296146..19c49eac42 100644 --- a/tools/nitpick/src/Test.cpp +++ b/tools/nitpick/src/Test.cpp @@ -19,7 +19,7 @@ #include #include -#include "Nitpick.h" +#include "ui/Nitpick.h" extern Nitpick* nitpick; #include diff --git a/tools/nitpick/src/Test.h b/tools/nitpick/src/Test.h index aafd2f5711..9ef7c5627a 100644 --- a/tools/nitpick/src/Test.h +++ b/tools/nitpick/src/Test.h @@ -18,7 +18,7 @@ #include "AWSInterface.h" #include "ImageComparer.h" -#include "MismatchWindow.h" +#include "ui/MismatchWindow.h" #include "TestRailInterface.h" class Step { diff --git a/tools/nitpick/src/TestRailInterface.h b/tools/nitpick/src/TestRailInterface.h index 566600e71a..6843ca0142 100644 --- a/tools/nitpick/src/TestRailInterface.h +++ b/tools/nitpick/src/TestRailInterface.h @@ -11,10 +11,10 @@ #ifndef hifi_test_testrail_interface_h #define hifi_test_testrail_interface_h -#include "BusyWindow.h" -#include "TestRailTestCasesSelectorWindow.h" -#include "TestRailRunSelectorWindow.h" -#include "TestRailResultsSelectorWindow.h" +#include "ui/BusyWindow.h" +#include "ui/TestRailTestCasesSelectorWindow.h" +#include "ui/TestRailRunSelectorWindow.h" +#include "ui/TestRailResultsSelectorWindow.h" #include #include diff --git a/tools/nitpick/src/TestRunner.cpp b/tools/nitpick/src/TestRunner.cpp index 491e211e2c..9aca2bf3e6 100644 --- a/tools/nitpick/src/TestRunner.cpp +++ b/tools/nitpick/src/TestRunner.cpp @@ -1,7 +1,7 @@ // // TestRunner.cpp // -// Created by Nissim Hadar on 23 Jan 2019. +// Created by Nissim Hadar on 1 Sept 2018. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -9,9 +9,70 @@ // #include "TestRunner.h" -#include +#include +#include +#include -void TestRunner::setWorkingFolder(QLabel* workingFolderLabel) { +#include "ui/Nitpick.h" +extern Nitpick* nitpick; + +#ifdef Q_OS_WIN +#include +#include +#endif + +// TODO: for debug +#include + +TestRunner::TestRunner(std::vector dayCheckboxes, + std::vector timeEditCheckboxes, + std::vector 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() { // Everything will be written to this folder QString previousSelection = _workingFolder; QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); @@ -19,8 +80,8 @@ void TestRunner::setWorkingFolder(QLabel* workingFolderLabel) { parent += "/"; } - _workingFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a working folder for temporary files", parent, - QFileDialog::ShowDirsOnly); + _workingFolder = QFileDialog::getExistingDirectory(nullptr, "Please select a temporary folder for installation", parent, + QFileDialog::ShowDirsOnly); // If user canceled then restore previous selection and return if (_workingFolder == "") { @@ -28,7 +89,726 @@ void TestRunner::setWorkingFolder(QLabel* workingFolderLabel) { return; } - workingFolderLabel->setText(QDir::toNativeSeparators(_workingFolder)); +#ifdef Q_OS_WIN + _installationFolder = _workingFolder + "/High Fidelity"; +#elif defined Q_OS_MAC + _installationFolder = _workingFolder + "/High_Fidelity"; +#endif + + _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(); + + // 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 + 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); + } +} + +void TestRunner::parseBuildInformation() { + try { + QDomDocument domDocument; + QString filename{ _workingFolder + "/" + DEV_BUILD_XML_FILENAME }; + QFile file(filename); + if (!file.open(QIODevice::ReadOnly) || !domDocument.setContent(&file)) { + throw QString("Could not open " + filename); + } + + QString platformOfInterest; +#ifdef Q_OS_WIN + platformOfInterest = "windows"; +#elif defined(Q_OS_MAC) + platformOfInterest = "mac"; +#endif + + QDomElement element = domDocument.documentElement(); + + // Verify first element is "projects" + if (element.tagName() != "projects") { + throw("File seems to be in wrong format"); + } + + element = element.firstChild().toElement(); + if (element.tagName() != "project") { + throw("File seems to be in wrong format"); + } + + if (element.attribute("name") != "interface") { + throw("File is not from 'interface' build"); + } + + // Now loop over the platforms, looking for ours + bool platformFound{ false }; + element = element.firstChild().toElement(); + while (!element.isNull()) { + if (element.attribute("name") == platformOfInterest) { + platformFound = true; + break; + } + element = element.nextSibling().toElement(); + } + + if (!platformFound) { + throw("File seems to be in wrong format - platform " + platformOfInterest + " not found"); + } + + element = element.firstChild().toElement(); + if (element.tagName() != "build") { + throw("File seems to be in wrong format"); + } + + // Next element should be the version + element = element.firstChild().toElement(); + if (element.tagName() != "version") { + throw("File seems to be in wrong format"); + } + + // Add the build number to the end of the filename + _buildInformation.build = element.text(); + + // First sibling should be stable_version + element = element.nextSibling().toElement(); + if (element.tagName() != "stable_version") { + throw("File seems to be in wrong format"); + } + + // Next sibling should be url + element = element.nextSibling().toElement(); + if (element.tagName() != "url") { + throw("File seems to be in wrong format"); + } + _buildInformation.url = element.text(); + + } 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 Worker::setCommandLine(const QString& commandLine) { @@ -40,4 +820,3 @@ int Worker::runCommand() { emit commandComplete(); return result; } - diff --git a/tools/nitpick/src/TestRunner.h b/tools/nitpick/src/TestRunner.h index 338cb5524e..00f0f66ecf 100644 --- a/tools/nitpick/src/TestRunner.h +++ b/tools/nitpick/src/TestRunner.h @@ -1,7 +1,7 @@ // // TestRunner.h // -// Created by Nissim Hadar on 23 Jan 2019. +// Created by Nissim Hadar on 1 Sept 2018. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -11,17 +11,133 @@ #ifndef hifi_testRunner_h #define hifi_testRunner_h +#include +#include #include +#include #include +#include +#include +#include +#include + +class BuildInformation { +public: + QString build; + QString url; +}; class Worker; -class TestRunner { +class TestRunner : public QObject { + Q_OBJECT public: - void setWorkingFolder(QLabel* workingFolderLabel); + explicit TestRunner(std::vector dayCheckboxes, + std::vector timeEditCheckboxes, + std::vector timeEdits, + QLabel* workingFolderLabel, + QCheckBox* runServerless, + QCheckBox* runLatest, + QLineEdit* url, + QPushButton* runNow, + QObject* parent = 0); + + ~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); + + void parseBuildInformation(); + +private slots: + void checkTime(); + void installationComplete(); + void interfaceExecutionComplete(); + +signals: + void startInstaller(); + void startInterface(); + void startResize(); + +private: + bool _automatedTestIsRunning{ false }; + +#ifdef Q_OS_WIN + const QString INSTALLER_FILENAME_LATEST{ "HighFidelity-Beta-latest-dev.exe" }; +#elif defined(Q_OS_MAC) + const QString INSTALLER_FILENAME_LATEST{ "HighFidelity-Beta-latest-dev.dmg" }; +#else + 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; -protected: QString _workingFolder; + QString _installationFolder; + QString _snapshotFolder; + + const QString UNIQUE_FOLDER_NAME{ "fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf" }; + const QString SNAPSHOT_FOLDER_NAME{ "snapshots" }; + + QString _branch; + QString _user; + + std::vector _dayCheckboxes; + std::vector _timeEditCheckboxes; + std::vector _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; }; class Worker : public QObject { @@ -34,9 +150,10 @@ public slots: signals: void commandComplete(); - + void startInstaller(); + void startInterface(); + private: QString _commandLine; }; - -#endif +#endif // hifi_testRunner_h diff --git a/tools/nitpick/src/main.cpp b/tools/nitpick/src/main.cpp index a2784a40b3..089a72e6ce 100644 --- a/tools/nitpick/src/main.cpp +++ b/tools/nitpick/src/main.cpp @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include -#include "Nitpick.h" +#include "ui/Nitpick.h" #include diff --git a/tools/nitpick/src/ui/BusyWindow.cpp b/tools/nitpick/src/ui/BusyWindow.cpp new file mode 100644 index 0000000000..8066d576f8 --- /dev/null +++ b/tools/nitpick/src/ui/BusyWindow.cpp @@ -0,0 +1,14 @@ +// +// BusyWindow.cpp +// +// Created by Nissim Hadar on 26 Jul 2017. +// 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 "BusyWindow.h" + +BusyWindow::BusyWindow(QWidget *parent) { + setupUi(this); +} diff --git a/tools/nitpick/src/ui/BusyWindow.h b/tools/nitpick/src/ui/BusyWindow.h new file mode 100644 index 0000000000..62f2df7e04 --- /dev/null +++ b/tools/nitpick/src/ui/BusyWindow.h @@ -0,0 +1,22 @@ +// +// BusyWindow.h +// +// Created by Nissim Hadar on 29 Jul 2017. +// 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_BusyWindow_h +#define hifi_BusyWindow_h + +#include "ui_BusyWindow.h" + +class BusyWindow : public QDialog, public Ui::BusyWindow { + Q_OBJECT + +public: + BusyWindow(QWidget* parent = Q_NULLPTR); +}; + +#endif \ No newline at end of file diff --git a/tools/nitpick/src/ui/BusyWindow.ui b/tools/nitpick/src/ui/BusyWindow.ui new file mode 100644 index 0000000000..c237566a5e --- /dev/null +++ b/tools/nitpick/src/ui/BusyWindow.ui @@ -0,0 +1,75 @@ + + + BusyWindow + + + Qt::ApplicationModal + + + + 0 + 0 + 542 + 189 + + + + Updating TestRail - please wait + + + + + 30 + 850 + 500 + 28 + + + + + 12 + + + + similarity + + + + + + 40 + 40 + 481 + 101 + + + + 0 + + + 0 + + + + + + 50 + 60 + 431 + 61 + + + + + 20 + + + + Please wait for this window to close + + + + + + + diff --git a/tools/nitpick/src/ui/MismatchWindow.cpp b/tools/nitpick/src/ui/MismatchWindow.cpp new file mode 100644 index 0000000000..58189b4795 --- /dev/null +++ b/tools/nitpick/src/ui/MismatchWindow.cpp @@ -0,0 +1,101 @@ +// +// MismatchWindow.cpp +// +// Created by Nissim Hadar on 9 Nov 2017. +// 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 "MismatchWindow.h" + +#include + +#include + +MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { + setupUi(this); + + expectedImage->setScaledContents(true); + resultImage->setScaledContents(true); + diffImage->setScaledContents(true); +} + +QPixmap MismatchWindow::computeDiffPixmap(QImage expectedImage, QImage resultImage) { + // Create an empty difference image if the images differ in size + if (expectedImage.height() != resultImage.height() || expectedImage.width() != resultImage.width()) { + return QPixmap(); + } + + // This is an optimization, as QImage.setPixel() is embarrassingly slow + unsigned char* buffer = new unsigned char[expectedImage.height() * expectedImage.width() * 3]; + + // loop over each pixel + for (int y = 0; y < expectedImage.height(); ++y) { + for (int x = 0; x < expectedImage.width(); ++x) { + QRgb pixelP = expectedImage.pixel(QPoint(x, y)); + QRgb pixelQ = resultImage.pixel(QPoint(x, y)); + + // Convert to luminance + double p = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); + double q = R_Y * qRed(pixelQ) + G_Y * qGreen(pixelQ) + B_Y * qBlue(pixelQ); + + // The intensity value is modified to increase the brightness of the displayed image + double absoluteDifference = fabs(p - q) / 255.0; + double modifiedDifference = sqrt(absoluteDifference); + + int difference = (int)(modifiedDifference * 255.0); + + buffer[3 * (x + y * expectedImage.width()) + 0] = difference; + buffer[3 * (x + y * expectedImage.width()) + 1] = difference; + buffer[3 * (x + y * expectedImage.width()) + 2] = difference; + } + } + + QImage diffImage(buffer, expectedImage.width(), expectedImage.height(), QImage::Format_RGB888); + QPixmap resultPixmap = QPixmap::fromImage(diffImage); + + delete[] buffer; + + return resultPixmap; +} + +void MismatchWindow::setTestResult(TestResult testResult) { + errorLabel->setText("Similarity: " + QString::number(testResult._error)); + + imagePath->setText("Path to test: " + testResult._pathname); + + expectedFilename->setText(testResult._expectedImageFilename); + resultFilename->setText(testResult._actualImageFilename); + + QPixmap expectedPixmap = QPixmap(testResult._pathname + testResult._expectedImageFilename); + QPixmap actualPixmap = QPixmap(testResult._pathname + testResult._actualImageFilename); + + _diffPixmap = computeDiffPixmap( + QImage(testResult._pathname + testResult._expectedImageFilename), + QImage(testResult._pathname + testResult._actualImageFilename) + ); + + expectedImage->setPixmap(expectedPixmap); + resultImage->setPixmap(actualPixmap); + diffImage->setPixmap(_diffPixmap); +} + +void MismatchWindow::on_passTestButton_clicked() { + _userResponse = USER_RESPONSE_PASS; + close(); +} + +void MismatchWindow::on_failTestButton_clicked() { + _userResponse = USE_RESPONSE_FAIL; + close(); +} + +void MismatchWindow::on_abortTestsButton_clicked() { + _userResponse = USER_RESPONSE_ABORT; + close(); +} + +QPixmap MismatchWindow::getComparisonImage() { + return _diffPixmap; +} diff --git a/tools/nitpick/src/ui/MismatchWindow.h b/tools/nitpick/src/ui/MismatchWindow.h new file mode 100644 index 0000000000..30c29076b3 --- /dev/null +++ b/tools/nitpick/src/ui/MismatchWindow.h @@ -0,0 +1,42 @@ +// +// MismatchWindow.h +// +// Created by Nissim Hadar on 9 Nov 2017. +// 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_MismatchWindow_h +#define hifi_MismatchWindow_h + +#include "ui_MismatchWindow.h" + +#include "../common.h" + +class MismatchWindow : public QDialog, public Ui::MismatchWindow { + Q_OBJECT + +public: + MismatchWindow(QWidget *parent = Q_NULLPTR); + + void setTestResult(TestResult testResult); + + UserResponse getUserResponse() { return _userResponse; } + + QPixmap computeDiffPixmap(QImage expectedImage, QImage resultImage); + QPixmap getComparisonImage(); + +private slots: + void on_passTestButton_clicked(); + void on_failTestButton_clicked(); + void on_abortTestsButton_clicked(); + +private: + UserResponse _userResponse{ USER_RESPONSE_INVALID }; + + QPixmap _diffPixmap; +}; + + +#endif // hifi_MismatchWindow_h \ No newline at end of file diff --git a/tools/nitpick/src/ui/MismatchWindow.ui b/tools/nitpick/src/ui/MismatchWindow.ui new file mode 100644 index 0000000000..8a174989d4 --- /dev/null +++ b/tools/nitpick/src/ui/MismatchWindow.ui @@ -0,0 +1,199 @@ + + + MismatchWindow + + + Qt::ApplicationModal + + + + 0 + 0 + 1782 + 942 + + + + MismatchWindow + + + + + 10 + 25 + 800 + 450 + + + + expected image + + + + + + 900 + 25 + 800 + 450 + + + + result image + + + + + + 540 + 480 + 800 + 450 + + + + diff image + + + + + + 60 + 660 + 480 + 28 + + + + + 12 + + + + result image filename + + + + + + 60 + 630 + 480 + 28 + + + + + 12 + + + + expected image filename + + + + + + 20 + 600 + 1200 + 28 + + + + + 12 + + + + image path + + + + + + 30 + 790 + 75 + 23 + + + + Pass + + + + + + 120 + 790 + 75 + 23 + + + + Fail + + + + + + 210 + 790 + 121 + 23 + + + + Abort current test + + + + + + 30 + 850 + 500 + 28 + + + + + 12 + + + + similarity + + + + + + 30 + 5 + 151 + 16 + + + + Expected Image + + + + + + 930 + 5 + 151 + 16 + + + + Actual Image + + + + + + + diff --git a/tools/nitpick/src/ui/Nitpick.cpp b/tools/nitpick/src/ui/Nitpick.cpp new file mode 100644 index 0000000000..0bd397715b --- /dev/null +++ b/tools/nitpick/src/ui/Nitpick.cpp @@ -0,0 +1,314 @@ +// +// Nitpick.cpp +// zone/ambientLightInheritence +// +// Created by Nissim Hadar on 2 Nov 2017. +// 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 "Nitpick.h" + +#ifdef Q_OS_WIN +#include +#include +#endif + +#include + +Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { + _ui.setupUi(this); + + _ui.checkBoxInteractiveMode->setChecked(true); + _ui.progressBar->setVisible(false); + _ui.tabWidget->setCurrentIndex(0); + + _signalMapper = new QSignalMapper(); + + connect(_ui.actionClose, &QAction::triggered, this, &Nitpick::on_closeButton_clicked); + connect(_ui.actionAbout, &QAction::triggered, this, &Nitpick::about); + connect(_ui.actionContent, &QAction::triggered, this, &Nitpick::content); + + // The second tab hides and shows the Windows task bar +#ifndef Q_OS_WIN + _ui.tabWidget->removeTab(1); +#endif + + _ui.statusLabel->setText(""); + _ui.plainTextEdit->setReadOnly(true); + + setWindowTitle("Nitpick - v1.3.2"); +} + +Nitpick::~Nitpick() { + delete _signalMapper; + + if (_test) { + delete _test; + } + + if (_testRunner) { + delete _testRunner; + } +} + +void Nitpick::setup() { + if (_test) { + delete _test; + } + _test = new Test(_ui.progressBar, _ui.checkBoxInteractiveMode); + + std::vector dayCheckboxes; + dayCheckboxes.emplace_back(_ui.mondayCheckBox); + dayCheckboxes.emplace_back(_ui.tuesdayCheckBox); + dayCheckboxes.emplace_back(_ui.wednesdayCheckBox); + dayCheckboxes.emplace_back(_ui.thursdayCheckBox); + dayCheckboxes.emplace_back(_ui.fridayCheckBox); + dayCheckboxes.emplace_back(_ui.saturdayCheckBox); + dayCheckboxes.emplace_back(_ui.sundayCheckBox); + + std::vector timeEditCheckboxes; + timeEditCheckboxes.emplace_back(_ui.timeEdit1checkBox); + timeEditCheckboxes.emplace_back(_ui.timeEdit2checkBox); + timeEditCheckboxes.emplace_back(_ui.timeEdit3checkBox); + timeEditCheckboxes.emplace_back(_ui.timeEdit4checkBox); + + std::vector timeEdits; + timeEdits.emplace_back(_ui.timeEdit1); + timeEdits.emplace_back(_ui.timeEdit2); + timeEdits.emplace_back(_ui.timeEdit3); + timeEdits.emplace_back(_ui.timeEdit4); + + if (_testRunner) { + delete _testRunner; + } + _testRunner = new TestRunner(dayCheckboxes, timeEditCheckboxes, timeEdits, _ui.workingFolderLabel, _ui.checkBoxServerless, _ui.checkBoxRunLatest, _ui.urlLineEdit, _ui.runNowButton); +} + +void Nitpick::startTestsEvaluation(const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory, + const QString& branch, + const QString& user +) { + _test->startTestsEvaluation(isRunningFromCommandLine, isRunningInAutomaticTestRun, snapshotDirectory, branch, user); +} + +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) { +#else + if (index == 0 || index == 1 || index == 2) { +#endif + _ui.userLineEdit->setDisabled(false); + _ui.branchLineEdit->setDisabled(false); + } else { + _ui.userLineEdit->setDisabled(true); + _ui.branchLineEdit->setDisabled(true); + } +} + +void Nitpick::on_evaluateTestsButton_clicked() { + _test->startTestsEvaluation(false, false); +} + +void Nitpick::on_createRecursiveScriptButton_clicked() { + _test->createRecursiveScript(); +} + +void Nitpick::on_createAllRecursiveScriptsButton_clicked() { + _test->createAllRecursiveScripts(); +} + +void Nitpick::on_createTestsButton_clicked() { + _test->createTests(); +} + +void Nitpick::on_createMDFileButton_clicked() { + _test->createMDFile(); +} + +void Nitpick::on_createAllMDFilesButton_clicked() { + _test->createAllMDFiles(); +} + +void Nitpick::on_createTestAutoScriptButton_clicked() { + _test->createTestAutoScript(); +} + +void Nitpick::on_createAllTestAutoScriptsButton_clicked() { + _test->createAllTestAutoScripts(); +} + +void Nitpick::on_createTestsOutlineButton_clicked() { + _test->createTestsOutline(); +} + +void Nitpick::on_createTestRailTestCasesButton_clicked() { + _test->createTestRailTestCases(); +} + +void Nitpick::on_createTestRailRunButton_clicked() { + _test->createTestRailRun(); +} + +void Nitpick::on_setWorkingFolderButton_clicked() { + _testRunner->setWorkingFolder(); +} + +void Nitpick::enableRunTabControls() { + _ui.runNowButton->setEnabled(true); + _ui.daysGroupBox->setEnabled(true); + _ui.timesGroupBox->setEnabled(true); +} + +void Nitpick::on_runNowButton_clicked() { + _testRunner->run(); +} + +void Nitpick::on_checkBoxRunLatest_clicked() { + _ui.urlLineEdit->setEnabled(!_ui.checkBoxRunLatest->isChecked()); +} + +void Nitpick::automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures) { + _testRunner->automaticTestRunEvaluationComplete(zippedFolderName, numberOfFailures); +} + +void Nitpick::on_updateTestRailRunResultsButton_clicked() { + _test->updateTestRailRunResult(); +} + +// To toggle between show and hide +// if (uState & ABS_AUTOHIDE) on_showTaskbarButton_clicked(); +// else on_hideTaskbarButton_clicked(); +// +void Nitpick::on_hideTaskbarButton_clicked() { +#ifdef Q_OS_WIN + APPBARDATA abd = { sizeof abd }; + UINT uState = (UINT)SHAppBarMessage(ABM_GETSTATE, &abd); + LPARAM param = uState & ABS_ALWAYSONTOP; + abd.lParam = ABS_AUTOHIDE | param; + SHAppBarMessage(ABM_SETSTATE, &abd); +#endif +} + +void Nitpick::on_showTaskbarButton_clicked() { +#ifdef Q_OS_WIN + APPBARDATA abd = { sizeof abd }; + UINT uState = (UINT)SHAppBarMessage(ABM_GETSTATE, &abd); + LPARAM param = uState & ABS_ALWAYSONTOP; + abd.lParam = param; + SHAppBarMessage(ABM_SETSTATE, &abd); +#endif +} + +void Nitpick::on_closeButton_clicked() { + exit(0); +} + +void Nitpick::on_createPythonScriptRadioButton_clicked() { + _test->setTestRailCreateMode(PYTHON); +} + +void Nitpick::on_createXMLScriptRadioButton_clicked() { + _test->setTestRailCreateMode(XML); +} + +void Nitpick::on_createWebPagePushButton_clicked() { + _test->createWebPage(_ui.updateAWSCheckBox, _ui.awsURLLineEdit); +} + +void Nitpick::downloadFile(const QUrl& url) { + _downloaders.emplace_back(new Downloader(url, this)); + connect(_downloaders[_index], SIGNAL(downloaded()), _signalMapper, SLOT(map())); + + _signalMapper->setMapping(_downloaders[_index], _index); + + ++_index; +} + +void Nitpick::downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void *caller) { + connect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); + + _directoryName = directoryName; + _filenames = filenames; + _caller = caller; + + _numberOfFilesToDownload = URLs.size(); + _numberOfFilesDownloaded = 0; + _index = 0; + + _ui.progressBar->setMinimum(0); + _ui.progressBar->setMaximum(_numberOfFilesToDownload - 1); + _ui.progressBar->setValue(0); + _ui.progressBar->setVisible(true); + + foreach (auto downloader, _downloaders) { + delete downloader; + } + + _downloaders.clear(); + for (int i = 0; i < _numberOfFilesToDownload; ++i) { + downloadFile(URLs[i]); + } +} + +void Nitpick::saveFile(int index) { + try { + QFile file(_directoryName + "/" + _filenames[index]); + file.open(QIODevice::WriteOnly); + file.write(_downloaders[index]->downloadedData()); + file.close(); + } catch (...) { + QMessageBox::information(0, "Test Aborted", "Failed to save file: " + _filenames[index]); + _ui.progressBar->setVisible(false); + return; + } + + ++_numberOfFilesDownloaded; + + if (_numberOfFilesDownloaded == _numberOfFilesToDownload) { + disconnect(_signalMapper, SIGNAL(mapped(int)), this, SLOT(saveFile(int))); + if (_caller == _test) { + _test->finishTestsEvaluation(); + } else if (_caller == _testRunner) { + _testRunner->downloadComplete(); + } + } else { + _ui.progressBar->setValue(_numberOfFilesDownloaded); + } +} + +void Nitpick::about() { + QMessageBox::information(0, "About", QString("Built ") + __DATE__ + ", " + __TIME__); +} + +void Nitpick::content() { + QDesktopServices::openUrl(QUrl("https://github.com/highfidelity/hifi/blob/master/tools/nitpick/README.md")); +} + +void Nitpick::setUserText(const QString& user) { + _ui.userLineEdit->setText(user); +} + +QString Nitpick::getSelectedUser() { + return _ui.userLineEdit->text(); +} + +void Nitpick::setBranchText(const QString& branch) { + _ui.branchLineEdit->setText(branch); +} + +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); +} diff --git a/tools/nitpick/src/ui/Nitpick.h b/tools/nitpick/src/ui/Nitpick.h new file mode 100644 index 0000000000..08e41e0a90 --- /dev/null +++ b/tools/nitpick/src/ui/Nitpick.h @@ -0,0 +1,121 @@ +// +// Nitpick.h +// +// Created by Nissim Hadar on 2 Nov 2017. +// 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_Nitpick_h +#define hifi_Nitpick_h + +#include +#include +#include +#include "ui_Nitpick.h" + +#include "../Downloader.h" +#include "../Test.h" + +#include "../TestRunner.h" +#include "../AWSInterface.h" + +class Nitpick : public QMainWindow { + Q_OBJECT + +public: + Nitpick(QWidget* parent = Q_NULLPTR); + ~Nitpick(); + + void setup(); + + void startTestsEvaluation(const bool isRunningFromCommandLine, + const bool isRunningInAutomaticTestRun, + const QString& snapshotDirectory, + const QString& branch, + const QString& user); + + void automaticTestRunEvaluationComplete(QString zippedFolderName, int numberOfFailures); + + void downloadFile(const QUrl& url); + void downloadFiles(const QStringList& URLs, const QString& directoryName, const QStringList& filenames, void* caller); + + void setUserText(const QString& user); + QString getSelectedUser(); + + void setBranchText(const QString& branch); + QString getSelectedBranch(); + + void enableRunTabControls(); + + void updateStatusLabel(const QString& status); + void appendLogWindow(const QString& message); + +private slots: + void on_tabWidget_currentChanged(int index); + + void on_evaluateTestsButton_clicked(); + void on_createRecursiveScriptButton_clicked(); + void on_createAllRecursiveScriptsButton_clicked(); + void on_createTestsButton_clicked(); + + void on_createMDFileButton_clicked(); + void on_createAllMDFilesButton_clicked(); + + void on_createTestAutoScriptButton_clicked(); + void on_createAllTestAutoScriptsButton_clicked(); + + void on_createTestsOutlineButton_clicked(); + + void on_createTestRailTestCasesButton_clicked(); + void on_createTestRailRunButton_clicked(); + + void on_setWorkingFolderButton_clicked(); + void on_runNowButton_clicked(); + + void on_checkBoxRunLatest_clicked(); + + void on_updateTestRailRunResultsButton_clicked(); + + void on_hideTaskbarButton_clicked(); + void on_showTaskbarButton_clicked(); + + void on_createPythonScriptRadioButton_clicked(); + void on_createXMLScriptRadioButton_clicked(); + + void on_createWebPagePushButton_clicked(); + + void on_closeButton_clicked(); + + void saveFile(int index); + + void about(); + void content(); + +private: + Ui::NitpickClass _ui; + Test* _test{ nullptr }; + TestRunner* _testRunner{ nullptr }; + + AWSInterface _awsInterface; + + std::vector _downloaders; + + // local storage for parameters - folder to store downloaded files in, and a list of their names + QString _directoryName; + QStringList _filenames; + + // Used to enable passing a parameter to slots + QSignalMapper* _signalMapper; + + int _numberOfFilesToDownload{ 0 }; + int _numberOfFilesDownloaded{ 0 }; + int _index{ 0 }; + + bool _isRunningFromCommandline{ false }; + + void* _caller; +}; + +#endif // hifi_Nitpick_h \ No newline at end of file diff --git a/tools/nitpick/src/ui/Nitpick.ui b/tools/nitpick/src/ui/Nitpick.ui new file mode 100644 index 0000000000..78f7dcf2bf --- /dev/null +++ b/tools/nitpick/src/ui/Nitpick.ui @@ -0,0 +1,900 @@ + + + NitpickClass + + + + 0 + 0 + 720 + 870 + + + + + 0 + 0 + + + + Nitpick + + + + + + 470 + 750 + 100 + 40 + + + + Close + + + + + + 45 + 140 + 630 + 580 + + + + 0 + + + + Create + + + + + 195 + 60 + 220 + 40 + + + + Create Tests + + + + + + 70 + 180 + 220 + 40 + + + + Create MD file + + + + + + 320 + 180 + 220 + 40 + + + + Create all MD files + + + + + + 195 + 120 + 220 + 40 + + + + Create Tests Outline + + + + + + 70 + 300 + 220 + 40 + + + + Create Recursive Script + + + + + + 320 + 300 + 220 + 40 + + + + Create all Recursive Scripts + + + + + + 70 + 240 + 220 + 40 + + + + Create testAuto script + + + + + + 320 + 240 + 220 + 40 + + + + Create all testAuto scripts + + + + + + Windows + + + + + 200 + 130 + 211 + 40 + + + + Hide Windows Taskbar + + + + + + 200 + 200 + 211 + 40 + + + + Show Windows Taskbar + + + + + + Run + + + + false + + + + 10 + 160 + 161 + 51 + + + + Run now + + + + + false + + + + 20 + 240 + 91 + 241 + + + + Days + + + + + 10 + 210 + 80 + 17 + + + + Sunday + + + + + + 10 + 90 + 80 + 17 + + + + Wednesday + + + + + + 10 + 60 + 80 + 17 + + + + Tuesday + + + + + + 10 + 120 + 80 + 17 + + + + Thursday + + + + + + 10 + 150 + 80 + 17 + + + + Friday + + + + + + 10 + 180 + 80 + 17 + + + + Saturday + + + + + + 10 + 30 + 80 + 17 + + + + Monday + + + + + + false + + + + 130 + 240 + 161 + 191 + + + + Times + + + + + 30 + 20 + 118 + 22 + + + + + + + 30 + 60 + 118 + 22 + + + + + + + 30 + 100 + 118 + 22 + + + + + + + 30 + 140 + 118 + 22 + + + + + + + 10 + 23 + 21 + 17 + + + + + + + + + + 10 + 63 + 21 + 17 + + + + + + + + + + 10 + 103 + 21 + 17 + + + + + + + + + + 10 + 143 + 21 + 17 + + + + + + + + + + + 10 + 20 + 161 + 28 + + + + Set Working Folder + + + + + + 190 + 20 + 321 + 31 + + + + (not set...) + + + + + + 300 + 210 + 311 + 331 + + + + + + + 300 + 170 + 41 + 31 + + + + Status: + + + + + + 350 + 170 + 271 + 31 + + + + ####### + + + + + + 20 + 70 + 120 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Server-less + + + false + + + + + + 20 + 100 + 120 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Run Latest + + + true + + + + + + 128 + 95 + 31 + 31 + + + + URL + + + + + false + + + + 170 + 100 + 451 + 21 + + + + + + + Evaluate + + + + + 190 + 180 + 131 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Interactive Mode + + + + + + 330 + 170 + 181 + 51 + + + + Evaluate Test + + + + + + Web Interface + + + + + 240 + 220 + 160 + 40 + + + + Update Run Results + + + + + + 170 + 100 + 95 + 20 + + + + Python + + + true + + + + + + 240 + 160 + 160 + 40 + + + + Create Run + + + + + + 240 + 100 + 160 + 40 + + + + Create Test Cases + + + + + + 170 + 120 + 95 + 20 + + + + XML + + + + + + 10 + 30 + 601 + 300 + + + + TestRail + + + + + + 10 + 350 + 601 + 151 + + + + Amazon Web Services + + + + true + + + + 270 + 30 + 160 + 51 + + + + Create Web Page + + + + + + 150 + 42 + 111 + 17 + + + + Update AWS + + + + + + 20 + 90 + 561 + 21 + + + + + groupBox + updateTestRailRunResultsButton + createPythonScriptRadioButton + createTestRailRunButton + createTestRailTestCasesButton + createXMLScriptRadioButton + groupBox_2 + + + + + + 120 + 80 + 110 + 16 + + + + + 10 + + + + GitHub Branch + + + + + + 120 + 40 + 110 + 16 + + + + + 10 + + + + GitHub User + + + + + + 80 + 760 + 255 + 23 + + + + 24 + + + + + + 220 + 40 + 161 + 21 + + + + + + + 220 + 80 + 161 + 21 + + + + + + + + 0 + 0 + 720 + 21 + + + + + File + + + + + + Help + + + + + + + + + + TopToolBarArea + + + false + + + + + + Close + + + + + About + + + + + Online readme + + + + + + userLineEdit + branchLineEdit + createTestsButton + createMDFileButton + createAllMDFilesButton + createTestsOutlineButton + createRecursiveScriptButton + createAllRecursiveScriptsButton + createTestAutoScriptButton + createAllTestAutoScriptsButton + hideTaskbarButton + showTaskbarButton + runNowButton + sundayCheckBox + wednesdayCheckBox + tuesdayCheckBox + thursdayCheckBox + fridayCheckBox + saturdayCheckBox + mondayCheckBox + timeEdit1 + timeEdit2 + timeEdit3 + timeEdit4 + timeEdit1checkBox + timeEdit2checkBox + timeEdit3checkBox + timeEdit4checkBox + setWorkingFolderButton + plainTextEdit + checkBoxServerless + checkBoxRunLatest + urlLineEdit + checkBoxInteractiveMode + evaluateTestsButton + updateTestRailRunResultsButton + createPythonScriptRadioButton + createTestRailRunButton + createTestRailTestCasesButton + createXMLScriptRadioButton + createWebPagePushButton + updateAWSCheckBox + awsURLLineEdit + closeButton + tabWidget + + + + diff --git a/tools/nitpick/src/ui/TestRailResultsSelectorWindow.cpp b/tools/nitpick/src/ui/TestRailResultsSelectorWindow.cpp new file mode 100644 index 0000000000..505e04b33e --- /dev/null +++ b/tools/nitpick/src/ui/TestRailResultsSelectorWindow.cpp @@ -0,0 +1,106 @@ +// +// TestRailResultsSelectorWindow.cpp +// +// Created by Nissim Hadar on 2 Aug 2017. +// 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 "TestRailResultsSelectorWindow.h" + +#include + +#include + +TestRailResultsSelectorWindow::TestRailResultsSelectorWindow(QWidget *parent) { + setupUi(this); + + projectIDLineEdit->setValidator(new QIntValidator(1, 999, this)); +} + + +void TestRailResultsSelectorWindow::reset() { + urlLineEdit->setDisabled(false); + userLineEdit->setDisabled(false); + passwordLineEdit->setDisabled(false); + projectIDLineEdit->setDisabled(false); + suiteIDLineEdit->setDisabled(false); + + OKButton->setDisabled(true); + + runsLabel->setDisabled(true); + runsComboBox->setDisabled(true); +} + +void TestRailResultsSelectorWindow::on_acceptButton_clicked() { + urlLineEdit->setDisabled(true); + userLineEdit->setDisabled(true); + passwordLineEdit->setDisabled(true); + projectIDLineEdit->setDisabled(true); + suiteIDLineEdit->setDisabled(true); + + OKButton->setDisabled(false); + + runsLabel->setDisabled(false); + runsComboBox->setDisabled(false); + close(); +} + +void TestRailResultsSelectorWindow::on_OKButton_clicked() { + userCancelled = false; + close(); +} + +void TestRailResultsSelectorWindow::on_cancelButton_clicked() { + userCancelled = true; + close(); +} + +bool TestRailResultsSelectorWindow::getUserCancelled() { + return userCancelled; +} + +void TestRailResultsSelectorWindow::setURL(const QString& user) { + urlLineEdit->setText(user); +} + +QString TestRailResultsSelectorWindow::getURL() { + return urlLineEdit->text(); +} + +void TestRailResultsSelectorWindow::setUser(const QString& user) { + userLineEdit->setText(user); +} + +QString TestRailResultsSelectorWindow::getUser() { + return userLineEdit->text(); +} + +QString TestRailResultsSelectorWindow::getPassword() { + return passwordLineEdit->text(); +} + +void TestRailResultsSelectorWindow::setProjectID(const int project) { + projectIDLineEdit->setText(QString::number(project)); +} + +int TestRailResultsSelectorWindow::getProjectID() { + return projectIDLineEdit->text().toInt(); +} + +void TestRailResultsSelectorWindow::setSuiteID(const int project) { + suiteIDLineEdit->setText(QString::number(project)); +} + +int TestRailResultsSelectorWindow::getSuiteID() { + return suiteIDLineEdit->text().toInt(); +} + +void TestRailResultsSelectorWindow::updateRunsComboBoxData(QStringList data) { + runsComboBox->insertItems(0, data); +} + +int TestRailResultsSelectorWindow::getRunID() { + return runsComboBox->currentIndex(); +} \ No newline at end of file diff --git a/tools/nitpick/src/ui/TestRailResultsSelectorWindow.h b/tools/nitpick/src/ui/TestRailResultsSelectorWindow.h new file mode 100644 index 0000000000..51059d6127 --- /dev/null +++ b/tools/nitpick/src/ui/TestRailResultsSelectorWindow.h @@ -0,0 +1,50 @@ +// +// TestRailResultsSelectorWindow.h +// +// Created by Nissim Hadar on 2 Aug 2017. +// 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_TestRailResultsSelectorWindow_h +#define hifi_TestRailResultsSelectorWindow_h + +#include "ui_TestRailResultsSelectorWindow.h" + +class TestRailResultsSelectorWindow : public QDialog, public Ui::TestRailResultsSelectorWindow { + Q_OBJECT + +public: + TestRailResultsSelectorWindow(QWidget* parent = Q_NULLPTR); + + void reset(); + + bool getUserCancelled(); + + void setURL(const QString& user); + QString getURL(); + + void setUser(const QString& user); + QString getUser(); + + QString getPassword(); + + void setProjectID(const int project); + int getProjectID(); + + void setSuiteID(const int project); + int getSuiteID(); + + bool userCancelled{ false }; + + void updateRunsComboBoxData(QStringList data); + int getRunID(); + +private slots: + void on_acceptButton_clicked(); + void on_OKButton_clicked(); + void on_cancelButton_clicked(); +}; + +#endif \ No newline at end of file diff --git a/tools/nitpick/src/ui/TestRailResultsSelectorWindow.ui b/tools/nitpick/src/ui/TestRailResultsSelectorWindow.ui new file mode 100644 index 0000000000..983b95ee79 --- /dev/null +++ b/tools/nitpick/src/ui/TestRailResultsSelectorWindow.ui @@ -0,0 +1,280 @@ + + + TestRailResultsSelectorWindow + + + + 0 + 0 + 533 + 474 + + + + TestRail Test Case Selector Window + + + + + 30 + 850 + 500 + 28 + + + + + 12 + + + + similarity + + + + + + 70 + 125 + 121 + 20 + + + + + 10 + + + + TestRail Password + + + + + + 70 + 25 + 121 + 20 + + + + + 10 + + + + TestRail URL + + + + + false + + + + 120 + 420 + 93 + 28 + + + + OK + + + + + + 280 + 420 + 93 + 28 + + + + Cancel + + + + + + 200 + 120 + 231 + 24 + + + + QLineEdit::Password + + + + + + 70 + 75 + 121 + 20 + + + + + 10 + + + + TestRail User + + + + + + 200 + 170 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 175 + 121 + 20 + + + + + 10 + + + + TestRail Project ID + + + + + + 200 + 270 + 231 + 28 + + + + Accept + + + + + false + + + + 160 + 350 + 271 + 22 + + + + + + true + + + + 80 + 350 + 71 + 20 + + + + + 10 + + + + TestRail Run + + + + + + 200 + 20 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 70 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 215 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 220 + 121 + 20 + + + + + 10 + + + + TestRail Suite ID + + + + + + urlLineEdit + userLineEdit + passwordLineEdit + projectIDLineEdit + suiteIDLineEdit + acceptButton + runsComboBox + OKButton + cancelButton + + + + diff --git a/tools/nitpick/src/ui/TestRailRunSelectorWindow.cpp b/tools/nitpick/src/ui/TestRailRunSelectorWindow.cpp new file mode 100644 index 0000000000..ac3419d46f --- /dev/null +++ b/tools/nitpick/src/ui/TestRailRunSelectorWindow.cpp @@ -0,0 +1,101 @@ +// +// TestRailRunSelectorWindow.cpp +// +// Created by Nissim Hadar on 31 Jul 2017. +// 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 "TestRailRunSelectorWindow.h" + +#include + +#include + +TestRailRunSelectorWindow::TestRailRunSelectorWindow(QWidget *parent) { + setupUi(this); + + projectIDLineEdit->setValidator(new QIntValidator(1, 999, this)); +} + +void TestRailRunSelectorWindow::reset() { + urlLineEdit->setDisabled(false); + userLineEdit->setDisabled(false); + passwordLineEdit->setDisabled(false); + projectIDLineEdit->setDisabled(false); + suiteIDLineEdit->setDisabled(false); + + OKButton->setDisabled(true); + sectionsComboBox->setDisabled(true); +} + +void TestRailRunSelectorWindow::on_acceptButton_clicked() { + urlLineEdit->setDisabled(true); + userLineEdit->setDisabled(true); + passwordLineEdit->setDisabled(true); + projectIDLineEdit->setDisabled(true); + suiteIDLineEdit->setDisabled(true); + + OKButton->setDisabled(false); + sectionsComboBox->setDisabled(false); + close(); +} + +void TestRailRunSelectorWindow::on_OKButton_clicked() { + userCancelled = false; + close(); +} + +void TestRailRunSelectorWindow::on_cancelButton_clicked() { + userCancelled = true; + close(); +} + +bool TestRailRunSelectorWindow::getUserCancelled() { + return userCancelled; +} + +void TestRailRunSelectorWindow::setURL(const QString& user) { + urlLineEdit->setText(user); +} + +QString TestRailRunSelectorWindow::getURL() { + return urlLineEdit->text(); +} + +void TestRailRunSelectorWindow::setUser(const QString& user) { + userLineEdit->setText(user); +} + +QString TestRailRunSelectorWindow::getUser() { + return userLineEdit->text(); +} + +QString TestRailRunSelectorWindow::getPassword() { + return passwordLineEdit->text(); +} + +void TestRailRunSelectorWindow::setProjectID(const int project) { + projectIDLineEdit->setText(QString::number(project)); +} + +int TestRailRunSelectorWindow::getProjectID() { + return projectIDLineEdit->text().toInt(); +} + +void TestRailRunSelectorWindow::setSuiteID(const int project) { + suiteIDLineEdit->setText(QString::number(project)); +} + +int TestRailRunSelectorWindow::getSuiteID() { + return suiteIDLineEdit->text().toInt(); +} + +void TestRailRunSelectorWindow::updateSectionsComboBoxData(QStringList data) { + sectionsComboBox->insertItems(0, data); +} + +int TestRailRunSelectorWindow::getSectionID() { + return sectionsComboBox->currentIndex(); +} \ No newline at end of file diff --git a/tools/nitpick/src/ui/TestRailRunSelectorWindow.h b/tools/nitpick/src/ui/TestRailRunSelectorWindow.h new file mode 100644 index 0000000000..d6428bb476 --- /dev/null +++ b/tools/nitpick/src/ui/TestRailRunSelectorWindow.h @@ -0,0 +1,50 @@ +// +// TestRailRunSelectorWindow.h +// +// Created by Nissim Hadar on 31 Jul 2017. +// 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_TestRailRunSelectorWindow_h +#define hifi_TestRailRunSelectorWindow_h + +#include "ui_TestRailRunSelectorWindow.h" + +class TestRailRunSelectorWindow : public QDialog, public Ui::TestRailRunSelectorWindow { + Q_OBJECT + +public: + TestRailRunSelectorWindow(QWidget* parent = Q_NULLPTR); + + void reset(); + + bool getUserCancelled(); + + void setURL(const QString& user); + QString getURL(); + + void setUser(const QString& user); + QString getUser(); + + QString getPassword(); + + void setProjectID(const int project); + int getProjectID(); + + void setSuiteID(const int project); + int getSuiteID(); + + bool userCancelled{ false }; + + void updateSectionsComboBoxData(QStringList data); + int getSectionID(); + +private slots: + void on_acceptButton_clicked(); + void on_OKButton_clicked(); + void on_cancelButton_clicked(); +}; + +#endif \ No newline at end of file diff --git a/tools/nitpick/src/ui/TestRailRunSelectorWindow.ui b/tools/nitpick/src/ui/TestRailRunSelectorWindow.ui new file mode 100644 index 0000000000..ad39b5cc64 --- /dev/null +++ b/tools/nitpick/src/ui/TestRailRunSelectorWindow.ui @@ -0,0 +1,283 @@ + + + TestRailRunSelectorWindow + + + Qt::ApplicationModal + + + + 0 + 0 + 489 + 474 + + + + TestRail Run Selector Window + + + + + 30 + 850 + 500 + 28 + + + + + 12 + + + + similarity + + + + + + 70 + 125 + 121 + 20 + + + + + 10 + + + + TestRail Password + + + + + + 70 + 25 + 121 + 20 + + + + + 10 + + + + TestRail URL + + + + + false + + + + 120 + 420 + 93 + 28 + + + + OK + + + + + + 280 + 420 + 93 + 28 + + + + Cancel + + + + + + 200 + 120 + 231 + 24 + + + + QLineEdit::Password + + + + + + 70 + 75 + 121 + 20 + + + + + 10 + + + + TestRail User + + + + + + 200 + 170 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 175 + 121 + 20 + + + + + 10 + + + + TestRail Project ID + + + + + + 200 + 270 + 231 + 28 + + + + Accept + + + + + false + + + + 140 + 350 + 311 + 22 + + + + + + true + + + + 20 + 350 + 121 + 20 + + + + + 10 + + + + TestRail Sections + + + + + + 200 + 20 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 70 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 215 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 220 + 121 + 20 + + + + + 10 + + + + TestRail Suite ID + + + + + + urlLineEdit + userLineEdit + passwordLineEdit + projectIDLineEdit + suiteIDLineEdit + acceptButton + sectionsComboBox + OKButton + cancelButton + + + + diff --git a/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.cpp b/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.cpp new file mode 100644 index 0000000000..638fe71819 --- /dev/null +++ b/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.cpp @@ -0,0 +1,106 @@ +// +// TestRailTestCasesSelectorWindow.cpp +// +// Created by Nissim Hadar on 26 Jul 2017. +// 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 "TestRailTestCasesSelectorWindow.h" + +#include + +#include + +TestRailTestCasesSelectorWindow::TestRailTestCasesSelectorWindow(QWidget *parent) { + setupUi(this); + + projectIDLineEdit->setValidator(new QIntValidator(1, 999, this)); +} + + +void TestRailTestCasesSelectorWindow::reset() { + urlLineEdit->setDisabled(false); + userLineEdit->setDisabled(false); + passwordLineEdit->setDisabled(false); + projectIDLineEdit->setDisabled(false); + suiteIDLineEdit->setDisabled(false); + + OKButton->setDisabled(true); + + releasesLabel->setDisabled(true); + releasesComboBox->setDisabled(true); +} + +void TestRailTestCasesSelectorWindow::on_acceptButton_clicked() { + urlLineEdit->setDisabled(true); + userLineEdit->setDisabled(true); + passwordLineEdit->setDisabled(true); + projectIDLineEdit->setDisabled(true); + suiteIDLineEdit->setDisabled(true); + + OKButton->setDisabled(false); + + releasesLabel->setDisabled(false); + releasesComboBox->setDisabled(false); + close(); +} + +void TestRailTestCasesSelectorWindow::on_OKButton_clicked() { + userCancelled = false; + close(); +} + +void TestRailTestCasesSelectorWindow::on_cancelButton_clicked() { + userCancelled = true; + close(); +} + +bool TestRailTestCasesSelectorWindow::getUserCancelled() { + return userCancelled; +} + +void TestRailTestCasesSelectorWindow::setURL(const QString& user) { + urlLineEdit->setText(user); +} + +QString TestRailTestCasesSelectorWindow::getURL() { + return urlLineEdit->text(); +} + +void TestRailTestCasesSelectorWindow::setUser(const QString& user) { + userLineEdit->setText(user); +} + +QString TestRailTestCasesSelectorWindow::getUser() { + return userLineEdit->text(); +} + +QString TestRailTestCasesSelectorWindow::getPassword() { + return passwordLineEdit->text(); +} + +void TestRailTestCasesSelectorWindow::setProjectID(const int project) { + projectIDLineEdit->setText(QString::number(project)); +} + +int TestRailTestCasesSelectorWindow::getProjectID() { + return projectIDLineEdit->text().toInt(); +} + +void TestRailTestCasesSelectorWindow::setSuiteID(const int project) { + suiteIDLineEdit->setText(QString::number(project)); +} + +int TestRailTestCasesSelectorWindow::getSuiteID() { + return suiteIDLineEdit->text().toInt(); +} + +void TestRailTestCasesSelectorWindow::updateReleasesComboBoxData(QStringList data) { + releasesComboBox->insertItems(0, data); +} + +int TestRailTestCasesSelectorWindow::getReleaseID() { + return releasesComboBox->currentIndex(); +} \ No newline at end of file diff --git a/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.h b/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.h new file mode 100644 index 0000000000..9153b003fa --- /dev/null +++ b/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.h @@ -0,0 +1,50 @@ +// +// TestRailTestCasesSelectorWindow.h +// +// Created by Nissim Hadar on 26 Jul 2017. +// 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_TestRailTestCasesSelectorWindow_h +#define hifi_TestRailTestCasesSelectorWindow_h + +#include "ui_TestRailTestCasesSelectorWindow.h" + +class TestRailTestCasesSelectorWindow : public QDialog, public Ui::TestRailTestCasesSelectorWindow { + Q_OBJECT + +public: + TestRailTestCasesSelectorWindow(QWidget* parent = Q_NULLPTR); + + void reset(); + + bool getUserCancelled(); + + void setURL(const QString& user); + QString getURL(); + + void setUser(const QString& user); + QString getUser(); + + QString getPassword(); + + void setProjectID(const int project); + int getProjectID(); + + void setSuiteID(const int project); + int getSuiteID(); + + bool userCancelled{ false }; + + void updateReleasesComboBoxData(QStringList data); + int getReleaseID(); + +private slots: + void on_acceptButton_clicked(); + void on_OKButton_clicked(); + void on_cancelButton_clicked(); +}; + +#endif \ No newline at end of file diff --git a/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.ui b/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.ui new file mode 100644 index 0000000000..41ff2943d5 --- /dev/null +++ b/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.ui @@ -0,0 +1,280 @@ + + + TestRailTestCasesSelectorWindow + + + + 0 + 0 + 489 + 474 + + + + TestRail Test Case Selector Window + + + + + 30 + 850 + 500 + 28 + + + + + 12 + + + + similarity + + + + + + 70 + 125 + 121 + 20 + + + + + 10 + + + + TestRail Password + + + + + + 70 + 25 + 121 + 20 + + + + + 10 + + + + TestRail URL + + + + + false + + + + 120 + 420 + 93 + 28 + + + + OK + + + + + + 280 + 420 + 93 + 28 + + + + Cancel + + + + + + 200 + 120 + 231 + 24 + + + + QLineEdit::Password + + + + + + 70 + 75 + 121 + 20 + + + + + 10 + + + + TestRail User + + + + + + 200 + 170 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 175 + 121 + 20 + + + + + 10 + + + + TestRail Project ID + + + + + + 200 + 270 + 231 + 28 + + + + Accept + + + + + false + + + + 270 + 350 + 161 + 22 + + + + + + true + + + + 80 + 350 + 181 + 20 + + + + + 10 + + + + TestRail Added for Release + + + + + + 200 + 20 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 70 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 200 + 215 + 231 + 24 + + + + QLineEdit::Normal + + + + + + 70 + 220 + 121 + 20 + + + + + 10 + + + + TestRail Suite ID + + + + + + urlLineEdit + userLineEdit + passwordLineEdit + projectIDLineEdit + suiteIDLineEdit + acceptButton + releasesComboBox + OKButton + cancelButton + + + +