diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d616e1f3a..1ae139e69b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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)) diff --git a/cmake/macros/FixupNitpick.cmake b/cmake/macros/FixupNitpick.cmake new file mode 100644 index 0000000000..8477b17823 --- /dev/null +++ b/cmake/macros/FixupNitpick.cmake @@ -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() diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 1b7243d4f2..3ebc44e931 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -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") diff --git a/interface/resources/icons/+android/backward.svg b/interface/resources/icons/+android_interface/backward.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/backward.svg rename to interface/resources/icons/+android_interface/backward.svg diff --git a/interface/resources/icons/+android/bubble-a.svg b/interface/resources/icons/+android_interface/bubble-a.svg similarity index 100% rename from interface/resources/icons/+android/bubble-a.svg rename to interface/resources/icons/+android_interface/bubble-a.svg diff --git a/interface/resources/icons/+android/bubble-i.svg b/interface/resources/icons/+android_interface/bubble-i.svg similarity index 100% rename from interface/resources/icons/+android/bubble-i.svg rename to interface/resources/icons/+android_interface/bubble-i.svg diff --git a/interface/resources/icons/+android/button-a.svg b/interface/resources/icons/+android_interface/button-a.svg similarity index 100% rename from interface/resources/icons/+android/button-a.svg rename to interface/resources/icons/+android_interface/button-a.svg diff --git a/interface/resources/icons/+android/button.svg b/interface/resources/icons/+android_interface/button.svg similarity index 100% rename from interface/resources/icons/+android/button.svg rename to interface/resources/icons/+android_interface/button.svg diff --git a/interface/resources/icons/+android/forward.svg b/interface/resources/icons/+android_interface/forward.svg similarity index 100% rename from interface/resources/icons/+android/forward.svg rename to interface/resources/icons/+android_interface/forward.svg diff --git a/interface/resources/icons/+android/go-a.svg b/interface/resources/icons/+android_interface/go-a.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/go-a.svg rename to interface/resources/icons/+android_interface/go-a.svg diff --git a/interface/resources/icons/+android/go-i.svg b/interface/resources/icons/+android_interface/go-i.svg similarity index 100% rename from interface/resources/icons/+android/go-i.svg rename to interface/resources/icons/+android_interface/go-i.svg diff --git a/interface/resources/icons/+android/hand.svg b/interface/resources/icons/+android_interface/hand.svg similarity index 100% rename from interface/resources/icons/+android/hand.svg rename to interface/resources/icons/+android_interface/hand.svg diff --git a/interface/resources/icons/+android/hide.svg b/interface/resources/icons/+android_interface/hide.svg similarity index 100% rename from interface/resources/icons/+android/hide.svg rename to interface/resources/icons/+android_interface/hide.svg diff --git a/interface/resources/icons/+android/mic-mute-a.svg b/interface/resources/icons/+android_interface/mic-mute-a.svg similarity index 100% rename from interface/resources/icons/+android/mic-mute-a.svg rename to interface/resources/icons/+android_interface/mic-mute-a.svg diff --git a/interface/resources/icons/+android/mic-mute-i.svg b/interface/resources/icons/+android_interface/mic-mute-i.svg similarity index 100% rename from interface/resources/icons/+android/mic-mute-i.svg rename to interface/resources/icons/+android_interface/mic-mute-i.svg diff --git a/interface/resources/icons/+android/mic-unmute-a.svg b/interface/resources/icons/+android_interface/mic-unmute-a.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/mic-unmute-a.svg rename to interface/resources/icons/+android_interface/mic-unmute-a.svg diff --git a/interface/resources/icons/+android/mic-unmute-i.svg b/interface/resources/icons/+android_interface/mic-unmute-i.svg similarity index 100% rename from interface/resources/icons/+android/mic-unmute-i.svg rename to interface/resources/icons/+android_interface/mic-unmute-i.svg diff --git a/interface/resources/icons/+android/myview-a.svg b/interface/resources/icons/+android_interface/myview-a.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/myview-a.svg rename to interface/resources/icons/+android_interface/myview-a.svg diff --git a/interface/resources/icons/+android/myview-hover.svg b/interface/resources/icons/+android_interface/myview-hover.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/myview-hover.svg rename to interface/resources/icons/+android_interface/myview-hover.svg diff --git a/interface/resources/icons/+android/myview-i.svg b/interface/resources/icons/+android_interface/myview-i.svg old mode 100755 new mode 100644 similarity index 100% rename from interface/resources/icons/+android/myview-i.svg rename to interface/resources/icons/+android_interface/myview-i.svg diff --git a/interface/resources/icons/+android/show-up.svg b/interface/resources/icons/+android_interface/show-up.svg similarity index 100% rename from interface/resources/icons/+android/show-up.svg rename to interface/resources/icons/+android_interface/show-up.svg diff --git a/interface/resources/icons/+android/stats.svg b/interface/resources/icons/+android_interface/stats.svg similarity index 100% rename from interface/resources/icons/+android/stats.svg rename to interface/resources/icons/+android_interface/stats.svg diff --git a/interface/resources/icons/+android/tick.svg b/interface/resources/icons/+android_interface/tick.svg similarity index 100% rename from interface/resources/icons/+android/tick.svg rename to interface/resources/icons/+android_interface/tick.svg diff --git a/interface/resources/qml/+android/StatText.qml b/interface/resources/qml/+android_interface/StatText.qml similarity index 100% rename from interface/resources/qml/+android/StatText.qml rename to interface/resources/qml/+android_interface/StatText.qml diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android_interface/Stats.qml similarity index 100% rename from interface/resources/qml/+android/Stats.qml rename to interface/resources/qml/+android_interface/Stats.qml diff --git a/interface/resources/qml/+android/Web3DSurface.qml b/interface/resources/qml/+android_interface/Web3DSurface.qml similarity index 100% rename from interface/resources/qml/+android/Web3DSurface.qml rename to interface/resources/qml/+android_interface/Web3DSurface.qml diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml similarity index 100% rename from interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml rename to interface/resources/qml/LoginDialog/+android_interface/LinkAccountBody.qml diff --git a/interface/resources/qml/LoginDialog/+android/SignUpBody.qml b/interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml similarity index 100% rename from interface/resources/qml/LoginDialog/+android/SignUpBody.qml rename to interface/resources/qml/LoginDialog/+android_interface/SignUpBody.qml diff --git a/interface/resources/qml/controlsUit/+android/ImageButton.qml b/interface/resources/qml/controlsUit/+android_interface/ImageButton.qml similarity index 100% rename from interface/resources/qml/controlsUit/+android/ImageButton.qml rename to interface/resources/qml/controlsUit/+android_interface/ImageButton.qml diff --git a/interface/resources/qml/desktop/+android/FocusHack.qml b/interface/resources/qml/desktop/+android_interface/FocusHack.qml similarity index 100% rename from interface/resources/qml/desktop/+android/FocusHack.qml rename to interface/resources/qml/desktop/+android_interface/FocusHack.qml diff --git a/interface/resources/qml/hifi/+android/ActionBar.qml b/interface/resources/qml/hifi/+android_interface/ActionBar.qml similarity index 100% rename from interface/resources/qml/hifi/+android/ActionBar.qml rename to interface/resources/qml/hifi/+android_interface/ActionBar.qml diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android_interface/AudioBar.qml similarity index 100% rename from interface/resources/qml/hifi/+android/AudioBar.qml rename to interface/resources/qml/hifi/+android_interface/AudioBar.qml diff --git a/interface/resources/qml/hifi/+android/AvatarOption.qml b/interface/resources/qml/hifi/+android_interface/AvatarOption.qml similarity index 100% rename from interface/resources/qml/hifi/+android/AvatarOption.qml rename to interface/resources/qml/hifi/+android_interface/AvatarOption.qml diff --git a/interface/resources/qml/hifi/+android/Desktop.qml b/interface/resources/qml/hifi/+android_interface/Desktop.qml similarity index 100% rename from interface/resources/qml/hifi/+android/Desktop.qml rename to interface/resources/qml/hifi/+android_interface/Desktop.qml diff --git a/interface/resources/qml/hifi/+android/HifiConstants.qml b/interface/resources/qml/hifi/+android_interface/HifiConstants.qml similarity index 100% rename from interface/resources/qml/hifi/+android/HifiConstants.qml rename to interface/resources/qml/hifi/+android_interface/HifiConstants.qml diff --git a/interface/resources/qml/hifi/+android/StatsBar.qml b/interface/resources/qml/hifi/+android_interface/StatsBar.qml similarity index 100% rename from interface/resources/qml/hifi/+android/StatsBar.qml rename to interface/resources/qml/hifi/+android_interface/StatsBar.qml diff --git a/interface/resources/qml/hifi/+android/WindowHeader.qml b/interface/resources/qml/hifi/+android_interface/WindowHeader.qml similarity index 100% rename from interface/resources/qml/hifi/+android/WindowHeader.qml rename to interface/resources/qml/hifi/+android_interface/WindowHeader.qml diff --git a/interface/resources/qml/hifi/+android/bottomHudOptions.qml b/interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml similarity index 100% rename from interface/resources/qml/hifi/+android/bottomHudOptions.qml rename to interface/resources/qml/hifi/+android_interface/bottomHudOptions.qml diff --git a/interface/resources/qml/hifi/+android/button.qml b/interface/resources/qml/hifi/+android_interface/button.qml similarity index 100% rename from interface/resources/qml/hifi/+android/button.qml rename to interface/resources/qml/hifi/+android_interface/button.qml diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android_interface/modesbar.qml similarity index 100% rename from interface/resources/qml/hifi/+android/modesbar.qml rename to interface/resources/qml/hifi/+android_interface/modesbar.qml diff --git a/interface/resources/qml/hifi/avatarapp/+android/TransparencyMask.qml b/interface/resources/qml/hifi/avatarapp/+android_interface/TransparencyMask.qml similarity index 100% rename from interface/resources/qml/hifi/avatarapp/+android/TransparencyMask.qml rename to interface/resources/qml/hifi/avatarapp/+android_interface/TransparencyMask.qml diff --git a/interface/resources/qml/stylesUit/+android/HifiConstants.qml b/interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml similarity index 100% rename from interface/resources/qml/stylesUit/+android/HifiConstants.qml rename to interface/resources/qml/stylesUit/+android_interface/HifiConstants.qml diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b198edc245..63c80ed27a 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -915,14 +915,9 @@ void MyAvatar::simulate(float deltaTime, bool inView) { auto entityTreeRenderer = qApp->getEntities(); EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr; if (entityTree) { - bool zoneAllowsFlying = true; - bool collisionlessAllowed = true; + std::pair zoneInteractionProperties; entityTree->withWriteLock([&] { - std::shared_ptr zone = entityTreeRenderer->myAvatarZone(); - if (zone) { - zoneAllowsFlying = zone->getFlyingAllowed(); - collisionlessAllowed = zone->getGhostingAllowed(); - } + zoneInteractionProperties = entityTreeRenderer->getZoneInteractionProperties(); EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); forEachDescendant([&](SpatiallyNestablePointer object) { // we need to update attached queryAACubes in our own local tree so point-select always works @@ -935,6 +930,8 @@ void MyAvatar::simulate(float deltaTime, bool inView) { }); }); bool isPhysicsEnabled = qApp->isPhysicsEnabled(); + bool zoneAllowsFlying = zoneInteractionProperties.first; + bool collisionlessAllowed = zoneInteractionProperties.second; _characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled); _characterController.setCollisionlessAllowed(collisionlessAllowed); } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 44025fc8f4..2b3a915235 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1289,6 +1289,17 @@ CalculateEntityLoadingPriority EntityTreeRenderer::_calculateEntityLoadingPriori return 0.0f; }; +std::pair EntityTreeRenderer::getZoneInteractionProperties() { + for (auto& zone : _layeredZones) { + // Only domain entities control flying allowed and ghosting allowed + if (zone.zone && zone.zone->isDomainEntity()) { + return { zone.zone->getFlyingAllowed(), zone.zone->getGhostingAllowed() }; + } + } + + return { true, true }; +} + bool EntityTreeRenderer::wantsKeyboardFocus(const EntityItemID& id) const { auto renderable = renderableForEntityId(id); if (!renderable) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index d9f594a20b..725416e2cc 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -105,7 +105,7 @@ public: // For Scene.shouldRenderEntities QList& getEntitiesLastInScene() { return _entityIDsLastInScene; } - std::shared_ptr myAvatarZone() { return _layeredZones.getZone(); } + std::pair getZoneInteractionProperties(); bool wantsKeyboardFocus(const EntityItemID& id) const; QObject* getEventHandler(const EntityItemID& id); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7e01af04dd..2e53fa4d57 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1264,7 +1264,7 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return false; } - if (_lastTextures != entity->getTextures()) { + if (_textures != entity->getTextures()) { return true; } @@ -1418,15 +1418,14 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce entity->_originalTexturesRead = true; } - if (_lastTextures != entity->getTextures()) { + if (_textures != entity->getTextures()) { + QVariantMap newTextures; withWriteLock([&] { _texturesLoaded = false; - _lastTextures = entity->getTextures(); + _textures = entity->getTextures(); + newTextures = parseTexturesToMap(_textures, entity->_originalTextures); }); - auto newTextures = parseTexturesToMap(_lastTextures, entity->_originalTextures); - if (newTextures != model->getTextures()) { - model->setTextures(newTextures); - } + model->setTextures(newTextures); } if (entity->_needsJointSimulation) { entity->copyAnimationJointDataToModel(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 16c3664f28..2a37e7107f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -181,7 +181,7 @@ private: bool _hasModel { false }; ModelPointer _model; - QString _lastTextures; + QString _textures; bool _texturesLoaded { false }; int _lastKnownCurrentFrame { -1 }; #ifdef MODEL_ENTITY_USE_FADE_EFFECT diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 7cafaece7a..c1488a5893 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1414,8 +1414,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.Bloom} bloom - The bloom properties of the zone. * * @property {boolean} flyingAllowed=true - If true then visitors can fly in the zone; otherwise they cannot. + * Only works on domain entities. * @property {boolean} ghostingAllowed=true - If true then visitors with avatar collisions turned off will not - * collide with content in the zone; otherwise visitors will always collide with content in the zone. + * collide with content in the zone; otherwise visitors will always collide with content in the zone. Only works on domain entities. * @property {string} filterURL="" - The URL of a JavaScript file that filters changes to properties of entities within the * zone. It is periodically executed for each entity in the zone. It can, for example, be used to not allow changes to diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index ddbb028b6e..e365d0a7b6 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -43,14 +43,15 @@ ModelEntityItem::ModelEntityItem(const EntityItemID& entityItemID) : EntityItem( } const QString ModelEntityItem::getTextures() const { - QReadLocker locker(&_texturesLock); - auto textures = _textures; - return textures; + return resultWithReadLock([&] { + return _textures; + }); } void ModelEntityItem::setTextures(const QString& textures) { - QWriteLocker locker(&_texturesLock); - _textures = textures; + withWriteLock([&] { + _textures = textures; + }); } EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 8c9fbdc45f..649a6cb50f 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -163,7 +163,6 @@ protected: AnimationPropertyGroup _animationProperties; - mutable QReadWriteLock _texturesLock; QString _textures; ShapeType _shapeType = SHAPE_TYPE_NONE; diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 1ebb488458..66ce5f32bf 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -683,7 +683,7 @@ void CharacterController::updateState() { return; } if (_pendingFlags & PENDING_FLAG_RECOMPUTE_FLYING) { - SET_STATE(CharacterController::State::Hover, "recomputeFlying"); + SET_STATE(CharacterController::State::Hover, "recomputeFlying"); _hasSupport = false; _stepHeight = _minStepHeight; // clears memory of last step obstacle _pendingFlags &= ~PENDING_FLAG_RECOMPUTE_FLYING; @@ -795,15 +795,13 @@ void CharacterController::updateState() { // Transition to hover if we are above the fall threshold SET_STATE(State::Hover, "above fall threshold"); } - } else if (!rayHasHit && !_hasSupport) { - SET_STATE(State::Hover, "no ground detected"); } break; } case State::Hover: btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length(); bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); - if (!_flyingAllowed && rayHasHit) { + if (!_flyingAllowed) { SET_STATE(State::InAir, "flying not allowed"); } else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { SET_STATE(State::InAir, "near ground"); diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 5394a0f448..ec1126c92f 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -1247,30 +1247,30 @@ void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector } } -QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures) { +QVariantMap parseTexturesToMap(QString newTextures, const QVariantMap& defaultTextures) { // If textures are unset, revert to original textures - if (textures.isEmpty()) { + if (newTextures.isEmpty()) { return defaultTextures; } // Legacy: a ,\n-delimited list of filename:"texturepath" - if (*textures.cbegin() != '{') { - textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; + if (*newTextures.cbegin() != '{') { + newTextures = "{\"" + newTextures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; } QJsonParseError error; - QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error); + QJsonDocument newTexturesJson = QJsonDocument::fromJson(newTextures.toUtf8(), &error); // If textures are invalid, revert to original textures if (error.error != QJsonParseError::NoError) { - qWarning() << "Could not evaluate textures property value:" << textures; + qWarning() << "Could not evaluate textures property value:" << newTextures; return defaultTextures; } - QVariantMap texturesMap = texturesJson.toVariant().toMap(); - // If textures are unset, revert to original textures - if (texturesMap.isEmpty()) { - return defaultTextures; + QVariantMap newTexturesMap = newTexturesJson.toVariant().toMap(); + QVariantMap toReturn = defaultTextures; + for (auto& texture : newTexturesMap.keys()) { + toReturn[texture] = newTexturesMap[texture]; } - return texturesJson.toVariant().toMap(); + return toReturn; } \ No newline at end of file diff --git a/libraries/shared/src/shared/FileUtils.cpp b/libraries/shared/src/shared/FileUtils.cpp index 9a58fc3e78..f2a4925351 100644 --- a/libraries/shared/src/shared/FileUtils.cpp +++ b/libraries/shared/src/shared/FileUtils.cpp @@ -32,7 +32,7 @@ const QStringList& FileUtils::getFileSelectors() { std::call_once(once, [] { #if defined(Q_OS_ANDROID) - //extraSelectors << "android_" HIFI_ANDROID_APP; + extraSelectors << "android_" HIFI_ANDROID_APP; #endif #if defined(USE_GLES) diff --git a/scripts/+android/defaultScripts.js b/scripts/+android_interface/defaultScripts.js similarity index 93% rename from scripts/+android/defaultScripts.js rename to scripts/+android_interface/defaultScripts.js index 8950af808d..e6971f5a6b 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android_interface/defaultScripts.js @@ -13,10 +13,10 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", - "system/+android/touchscreenvirtualpad.js", - "system/+android/actionbar.js", - "system/+android/audio.js" , - "system/+android/modes.js"/*, + "system/+android_interface/touchscreenvirtualpad.js", + "system/+android_interface/actionbar.js", + "system/+android_interface/audio.js" , + "system/+android_interface/modes.js"/*, "system/away.js", "system/controllers/controllerDisplayManager.js", "system/controllers/handControllerGrabAndroid.js", @@ -33,7 +33,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ ]; var DEBUG_SCRIPTS = [ - "system/+android/stats.js" + "system/+android_interface/stats.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ ]; diff --git a/scripts/+android_questInterface/defaultScripts.js b/scripts/+android_questInterface/defaultScripts.js index da50e4a182..d22716302c 100644 --- a/scripts/+android_questInterface/defaultScripts.js +++ b/scripts/+android_questInterface/defaultScripts.js @@ -25,6 +25,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/notifications.js", "system/commerce/wallet.js", "system/dialTone.js", + "system/quickGoto.js", "system/firstPersonHMD.js", "system/tablet-ui/tabletUI.js", "system/miniTablet.js" diff --git a/scripts/system/+android/actionbar.js b/scripts/system/+android_interface/actionbar.js similarity index 91% rename from scripts/system/+android/actionbar.js rename to scripts/system/+android_interface/actionbar.js index e7e2459e69..74b3896a62 100644 --- a/scripts/system/+android/actionbar.js +++ b/scripts/system/+android_interface/actionbar.js @@ -26,8 +26,8 @@ function init() { qml: "hifi/ActionBar.qml" }); backButton = actionbar.addButton({ - icon: "icons/+android/backward.svg", - activeIcon: "icons/+android/backward.svg", + icon: "icons/+android_interface/backward.svg", + activeIcon: "icons/+android_interface/backward.svg", text: "", bgOpacity: 0.0, hoverBgOpacity: 0.0, diff --git a/scripts/system/+android/audio.js b/scripts/system/+android_interface/audio.js similarity index 100% rename from scripts/system/+android/audio.js rename to scripts/system/+android_interface/audio.js diff --git a/scripts/system/+android/clickWeb.js b/scripts/system/+android_interface/clickWeb.js similarity index 100% rename from scripts/system/+android/clickWeb.js rename to scripts/system/+android_interface/clickWeb.js diff --git a/scripts/system/+android/displayNames.js b/scripts/system/+android_interface/displayNames.js similarity index 100% rename from scripts/system/+android/displayNames.js rename to scripts/system/+android_interface/displayNames.js diff --git a/scripts/system/+android/modes.js b/scripts/system/+android_interface/modes.js similarity index 100% rename from scripts/system/+android/modes.js rename to scripts/system/+android_interface/modes.js diff --git a/scripts/system/+android/radar.js b/scripts/system/+android_interface/radar.js similarity index 100% rename from scripts/system/+android/radar.js rename to scripts/system/+android_interface/radar.js diff --git a/scripts/system/+android/stats.js b/scripts/system/+android_interface/stats.js similarity index 100% rename from scripts/system/+android/stats.js rename to scripts/system/+android_interface/stats.js diff --git a/scripts/system/+android/touchscreenvirtualpad.js b/scripts/system/+android_interface/touchscreenvirtualpad.js similarity index 100% rename from scripts/system/+android/touchscreenvirtualpad.js rename to scripts/system/+android_interface/touchscreenvirtualpad.js diff --git a/scripts/system/+android/uniqueColor.js b/scripts/system/+android_interface/uniqueColor.js similarity index 100% rename from scripts/system/+android/uniqueColor.js rename to scripts/system/+android_interface/uniqueColor.js diff --git a/scripts/system/quickGoto.js b/scripts/system/quickGoto.js new file mode 100644 index 0000000000..c5560cce83 --- /dev/null +++ b/scripts/system/quickGoto.js @@ -0,0 +1,36 @@ +"use strict"; + +// +// quickGoto.js +// scripts/system/ +// +// Created by Dante Ruiz +// Copyright 2016 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 +// +/* globals Tablet, Toolbars, Script, HMD, DialogsManager */ + +(function() { // BEGIN LOCAL_SCOPE + + function addGotoButton(destination) { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + icon: "icons/tablet-icons/goto-i.svg", + activeIcon: "icons/tablet-icons/goto-a.svg", + text: destination + }); + var buttonDestination = destination; + button.clicked.connect(function() { + Window.location = "hifi://" + buttonDestination; + }); + Script.scriptEnding.connect(function () { + tablet.removeButton(button); + }); + } + + addGotoButton("dev-mobile"); + addGotoButton("quest-dev"); + +}()); // END LOCAL_SCOPE diff --git a/tools/nitpick/CMakeLists.txt b/tools/nitpick/CMakeLists.txt index efb5125f69..f825775879 100644 --- a/tools/nitpick/CMakeLists.txt +++ b/tools/nitpick/CMakeLists.txt @@ -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} $<$,$,$>:--release> \"$\"" - ) - - # 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" "$/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" "$" ) 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" "$/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 "$/" + 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() diff --git a/tools/nitpick/README.md b/tools/nitpick/README.md index 3a664a12e9..c5e3a5e21d 100644 --- a/tools/nitpick/README.md +++ b/tools/nitpick/README.md @@ -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]() -1. Double click on the installer and install to a convenient location -![](./setup_7z.PNG) - -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](). -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 ![](./Create.PNG) @@ -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) ![](./Windows.PNG) This tab is Windows-specific. It provides buttons to hide and show the task bar. diff --git a/tools/nitpick/compiledResources/resources.rcc b/tools/nitpick/compiledResources/resources.rcc new file mode 100644 index 0000000000..15f51ed7f4 Binary files /dev/null and b/tools/nitpick/compiledResources/resources.rcc differ diff --git a/tools/nitpick/icon/nitpick.icns b/tools/nitpick/icon/nitpick.icns new file mode 100644 index 0000000000..332539af2a Binary files /dev/null and b/tools/nitpick/icon/nitpick.icns differ diff --git a/tools/nitpick/icon/nitpick.ico b/tools/nitpick/icon/nitpick.ico new file mode 100644 index 0000000000..e3d852cb41 Binary files /dev/null and b/tools/nitpick/icon/nitpick.ico differ diff --git a/tools/nitpick/src/AWSInterface.h b/tools/nitpick/src/AWSInterface.h index fda250b115..63c48580f5 100644 --- a/tools/nitpick/src/AWSInterface.h +++ b/tools/nitpick/src/AWSInterface.h @@ -16,7 +16,7 @@ #include #include -#include "ui/BusyWindow.h" +#include "BusyWindow.h" #include "PythonInterface.h" diff --git a/tools/nitpick/src/ui/BusyWindow.cpp b/tools/nitpick/src/BusyWindow.cpp similarity index 100% rename from tools/nitpick/src/ui/BusyWindow.cpp rename to tools/nitpick/src/BusyWindow.cpp diff --git a/tools/nitpick/src/ui/BusyWindow.h b/tools/nitpick/src/BusyWindow.h similarity index 100% rename from tools/nitpick/src/ui/BusyWindow.h rename to tools/nitpick/src/BusyWindow.h diff --git a/tools/nitpick/src/ui/MismatchWindow.cpp b/tools/nitpick/src/MismatchWindow.cpp similarity index 100% rename from tools/nitpick/src/ui/MismatchWindow.cpp rename to tools/nitpick/src/MismatchWindow.cpp diff --git a/tools/nitpick/src/ui/MismatchWindow.h b/tools/nitpick/src/MismatchWindow.h similarity index 97% rename from tools/nitpick/src/ui/MismatchWindow.h rename to tools/nitpick/src/MismatchWindow.h index 30c29076b3..040e0b8bf1 100644 --- a/tools/nitpick/src/ui/MismatchWindow.h +++ b/tools/nitpick/src/MismatchWindow.h @@ -12,7 +12,7 @@ #include "ui_MismatchWindow.h" -#include "../common.h" +#include "common.h" class MismatchWindow : public QDialog, public Ui::MismatchWindow { Q_OBJECT diff --git a/tools/nitpick/src/ui/Nitpick.cpp b/tools/nitpick/src/Nitpick.cpp similarity index 65% rename from tools/nitpick/src/ui/Nitpick.cpp rename to tools/nitpick/src/Nitpick.cpp index 0bd397715b..78ed0ca0af 100644 --- a/tools/nitpick/src/ui/Nitpick.cpp +++ b/tools/nitpick/src/Nitpick.cpp @@ -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(); +} diff --git a/tools/nitpick/src/ui/Nitpick.h b/tools/nitpick/src/Nitpick.h similarity index 59% rename from tools/nitpick/src/ui/Nitpick.h rename to tools/nitpick/src/Nitpick.h index 08e41e0a90..29726be3bd 100644 --- a/tools/nitpick/src/ui/Nitpick.h +++ b/tools/nitpick/src/Nitpick.h @@ -15,11 +15,13 @@ #include #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; diff --git a/tools/nitpick/src/PythonInterface.cpp b/tools/nitpick/src/PythonInterface.cpp index 9832ac9f8d..9e2fec005f 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_PATH not defined", + QMessageBox::critical(0, "python not found", "python3 not found at " + _pythonCommand); exit(-1); } diff --git a/tools/nitpick/src/Test.cpp b/tools/nitpick/src/Test.cpp index 19c49eac42..2e62296146 100644 --- a/tools/nitpick/src/Test.cpp +++ b/tools/nitpick/src/Test.cpp @@ -19,7 +19,7 @@ #include #include -#include "ui/Nitpick.h" +#include "Nitpick.h" extern Nitpick* nitpick; #include diff --git a/tools/nitpick/src/Test.h b/tools/nitpick/src/Test.h index 9ef7c5627a..aafd2f5711 100644 --- a/tools/nitpick/src/Test.h +++ b/tools/nitpick/src/Test.h @@ -18,7 +18,7 @@ #include "AWSInterface.h" #include "ImageComparer.h" -#include "ui/MismatchWindow.h" +#include "MismatchWindow.h" #include "TestRailInterface.h" class Step { diff --git a/tools/nitpick/src/TestRailInterface.h b/tools/nitpick/src/TestRailInterface.h index 6843ca0142..566600e71a 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 "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 #include diff --git a/tools/nitpick/src/ui/TestRailResultsSelectorWindow.cpp b/tools/nitpick/src/TestRailResultsSelectorWindow.cpp similarity index 100% rename from tools/nitpick/src/ui/TestRailResultsSelectorWindow.cpp rename to tools/nitpick/src/TestRailResultsSelectorWindow.cpp diff --git a/tools/nitpick/src/ui/TestRailResultsSelectorWindow.h b/tools/nitpick/src/TestRailResultsSelectorWindow.h similarity index 100% rename from tools/nitpick/src/ui/TestRailResultsSelectorWindow.h rename to tools/nitpick/src/TestRailResultsSelectorWindow.h diff --git a/tools/nitpick/src/ui/TestRailRunSelectorWindow.cpp b/tools/nitpick/src/TestRailRunSelectorWindow.cpp similarity index 100% rename from tools/nitpick/src/ui/TestRailRunSelectorWindow.cpp rename to tools/nitpick/src/TestRailRunSelectorWindow.cpp diff --git a/tools/nitpick/src/ui/TestRailRunSelectorWindow.h b/tools/nitpick/src/TestRailRunSelectorWindow.h similarity index 100% rename from tools/nitpick/src/ui/TestRailRunSelectorWindow.h rename to tools/nitpick/src/TestRailRunSelectorWindow.h diff --git a/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.cpp b/tools/nitpick/src/TestRailTestCasesSelectorWindow.cpp similarity index 100% rename from tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.cpp rename to tools/nitpick/src/TestRailTestCasesSelectorWindow.cpp diff --git a/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.h b/tools/nitpick/src/TestRailTestCasesSelectorWindow.h similarity index 100% rename from tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.h rename to tools/nitpick/src/TestRailTestCasesSelectorWindow.h diff --git a/tools/nitpick/src/TestRunner.cpp b/tools/nitpick/src/TestRunner.cpp index 9aca2bf3e6..54246de80b 100644 --- a/tools/nitpick/src/TestRunner.cpp +++ b/tools/nitpick/src/TestRunner.cpp @@ -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 -#include -#include +#include -#include "ui/Nitpick.h" +#include "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() { +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; } diff --git a/tools/nitpick/src/TestRunner.h b/tools/nitpick/src/TestRunner.h index 00f0f66ecf..d2468ec2fa 100644 --- a/tools/nitpick/src/TestRunner.h +++ b/tools/nitpick/src/TestRunner.h @@ -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 #include #include -#include -#include #include -#include + +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 dayCheckboxes, - std::vector timeEditCheckboxes, - std::vector 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 _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; +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 diff --git a/tools/nitpick/src/TestRunnerDesktop.cpp b/tools/nitpick/src/TestRunnerDesktop.cpp new file mode 100644 index 0000000000..50cb6f9a07 --- /dev/null +++ b/tools/nitpick/src/TestRunnerDesktop.cpp @@ -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 +#include +#include + +#ifdef Q_OS_WIN +#include +#include +#endif + +#include "Nitpick.h" +extern Nitpick* nitpick; + +TestRunnerDesktop::TestRunnerDesktop( + std::vector dayCheckboxes, + std::vector timeEditCheckboxes, + std::vector 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); + } +} diff --git a/tools/nitpick/src/TestRunnerDesktop.h b/tools/nitpick/src/TestRunnerDesktop.h new file mode 100644 index 0000000000..a8f828b9d4 --- /dev/null +++ b/tools/nitpick/src/TestRunnerDesktop.h @@ -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 +#include +#include +#include +#include +#include + +#include "TestRunner.h" + +class InterfaceWorker; +class InstallerWorker; + +class TestRunnerDesktop : public QObject, public TestRunner { + Q_OBJECT +public: + explicit TestRunnerDesktop( + std::vector dayCheckboxes, + std::vector timeEditCheckboxes, + std::vector 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 _dayCheckboxes; + std::vector _timeEditCheckboxes; + std::vector _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 diff --git a/tools/nitpick/src/TestRunnerMobile.cpp b/tools/nitpick/src/TestRunnerMobile.cpp new file mode 100644 index 0000000000..e1c82854f4 --- /dev/null +++ b/tools/nitpick/src/TestRunnerMobile.cpp @@ -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 +#include +#include + +#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 +} diff --git a/tools/nitpick/src/TestRunnerMobile.h b/tools/nitpick/src/TestRunnerMobile.h new file mode 100644 index 0000000000..247f864976 --- /dev/null +++ b/tools/nitpick/src/TestRunnerMobile.h @@ -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 +#include +#include +#include + +#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 modelNames; +}; +#endif diff --git a/tools/nitpick/src/main.cpp b/tools/nitpick/src/main.cpp index 089a72e6ce..a2784a40b3 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 "ui/Nitpick.h" +#include "Nitpick.h" #include diff --git a/tools/nitpick/src/ui/BusyWindow.ui b/tools/nitpick/ui/BusyWindow.ui similarity index 100% rename from tools/nitpick/src/ui/BusyWindow.ui rename to tools/nitpick/ui/BusyWindow.ui diff --git a/tools/nitpick/src/ui/MismatchWindow.ui b/tools/nitpick/ui/MismatchWindow.ui similarity index 100% rename from tools/nitpick/src/ui/MismatchWindow.ui rename to tools/nitpick/ui/MismatchWindow.ui diff --git a/tools/nitpick/src/ui/Nitpick.ui b/tools/nitpick/ui/Nitpick.ui similarity index 73% rename from tools/nitpick/src/ui/Nitpick.ui rename to tools/nitpick/ui/Nitpick.ui index 78f7dcf2bf..319452233f 100644 --- a/tools/nitpick/src/ui/Nitpick.ui +++ b/tools/nitpick/ui/Nitpick.ui @@ -20,7 +20,7 @@ Nitpick - + 470 @@ -43,16 +43,16 @@ - 0 + 3 Create - + - 195 + 210 60 220 40 @@ -62,7 +62,7 @@ Create Tests - + 70 @@ -75,7 +75,7 @@ Create MD file - + 320 @@ -88,10 +88,10 @@ Create all MD files - + - 195 + 210 120 220 40 @@ -101,7 +101,7 @@ Create Tests Outline - + 70 @@ -114,7 +114,7 @@ Create Recursive Script - + 320 @@ -127,7 +127,7 @@ Create all Recursive Scripts - + 70 @@ -140,7 +140,7 @@ Create testAuto script - + 320 @@ -158,7 +158,7 @@ Windows - + 200 @@ -171,7 +171,7 @@ Hide Windows Taskbar - + 200 @@ -187,9 +187,9 @@ - Run + Test on Desktop - + false @@ -420,26 +420,26 @@ - + 10 20 - 161 - 28 + 160 + 30 Set Working Folder - + 190 20 - 321 - 31 + 320 + 30 @@ -469,7 +469,7 @@ Status: - + 350 @@ -501,7 +501,7 @@ false - + 20 @@ -533,7 +533,7 @@ URL - + false @@ -547,6 +547,201 @@ + + + Test on Mobile + + + + false + + + + 10 + 90 + 160 + 30 + + + + Connect Device + + + + + + 190 + 96 + 320 + 30 + + + + (not detected) + + + + + + 10 + 20 + 160 + 30 + + + + Set Working Folder + + + + + + 190 + 20 + 320 + 30 + + + + (not set...) + + + + + false + + + + 460 + 410 + 160 + 30 + + + + Pull folder + + + + + false + + + + 10 + 410 + 440 + 30 + + + + + + false + + + + 170 + 170 + 451 + 21 + + + + + + + 20 + 170 + 120 + 20 + + + + <html><head/><body><p>If unchecked, will not show results during evaluation</p></body></html> + + + Run Latest + + + true + + + + + false + + + + 10 + 210 + 160 + 30 + + + + Download APK + + + + + + 290 + 20 + 41 + 31 + + + + Status: + + + + + + 340 + 20 + 271 + 31 + + + + ####### + + + + + false + + + + 10 + 250 + 160 + 30 + + + + Install APK + + + + + false + + + + 10 + 300 + 160 + 30 + + + + Run Interface + + + Evaluate @@ -567,7 +762,7 @@ Interactive Mode - + 330 @@ -585,7 +780,7 @@ Web Interface - + 240 @@ -614,7 +809,7 @@ true - + 240 @@ -627,7 +822,7 @@ Create Run - + 240 @@ -678,7 +873,7 @@ Amazon Web Services - + true @@ -719,10 +914,10 @@ groupBox - updateTestRailRunResultsButton + updateTestRailRunResultsPushbutton createPythonScriptRadioButton - createTestRailRunButton - createTestRailTestCasesButton + createTestRailRunPushbutton + createTestRailTestCasesPushbutton createXMLScriptRadioButton groupBox_2 @@ -851,17 +1046,17 @@ userLineEdit branchLineEdit - createTestsButton - createMDFileButton - createAllMDFilesButton - createTestsOutlineButton - createRecursiveScriptButton - createAllRecursiveScriptsButton - createTestAutoScriptButton - createAllTestAutoScriptsButton - hideTaskbarButton - showTaskbarButton - runNowButton + createTestsPushbutton + createMDFilePushbutton + createAllMDFilesPushbutton + createTestsOutlinePushbutton + createRecursiveScriptPushbutton + createAllRecursiveScriptsPushbutton + createTestAutoScriptPushbutton + createAllTestAutoScriptsPushbutton + hideTaskbarPushbutton + showTaskbarPushbutton + runNowPushbutton sundayCheckBox wednesdayCheckBox tuesdayCheckBox @@ -877,22 +1072,22 @@ timeEdit2checkBox timeEdit3checkBox timeEdit4checkBox - setWorkingFolderButton + setWorkingFolderRunOnDesktopPushbutton plainTextEdit checkBoxServerless - checkBoxRunLatest - urlLineEdit + runLatestOnDesktopCheckBox + urlOnDesktopLineEdit checkBoxInteractiveMode - evaluateTestsButton - updateTestRailRunResultsButton + evaluateTestsPushbutton + updateTestRailRunResultsPushbutton createPythonScriptRadioButton - createTestRailRunButton - createTestRailTestCasesButton + createTestRailRunPushbutton + createTestRailTestCasesPushbutton createXMLScriptRadioButton - createWebPagePushButton + createWebPagePushbutton updateAWSCheckBox awsURLLineEdit - closeButton + closePushbutton tabWidget diff --git a/tools/nitpick/src/ui/TestRailResultsSelectorWindow.ui b/tools/nitpick/ui/TestRailResultsSelectorWindow.ui similarity index 100% rename from tools/nitpick/src/ui/TestRailResultsSelectorWindow.ui rename to tools/nitpick/ui/TestRailResultsSelectorWindow.ui diff --git a/tools/nitpick/src/ui/TestRailRunSelectorWindow.ui b/tools/nitpick/ui/TestRailRunSelectorWindow.ui similarity index 100% rename from tools/nitpick/src/ui/TestRailRunSelectorWindow.ui rename to tools/nitpick/ui/TestRailRunSelectorWindow.ui diff --git a/tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.ui b/tools/nitpick/ui/TestRailTestCasesSelectorWindow.ui similarity index 100% rename from tools/nitpick/src/ui/TestRailTestCasesSelectorWindow.ui rename to tools/nitpick/ui/TestRailTestCasesSelectorWindow.ui