Merge branch '20116_nitpick_v1.2' of https://github.com/NissimHadar/hifi into 20116_nitpick_v1.2

This commit is contained in:
NissimHadar 2018-12-05 07:41:36 -08:00
commit 936119b246
35 changed files with 322 additions and 186 deletions

View file

@ -337,6 +337,13 @@ void AudioMixerClientData::removeAgentAvatarAudioStream() {
if (it != _audioStreams.end()) {
_audioStreams.erase(it);
// Clear mixing structures so that they get recreated with up to date
// data if the stream comes back
setHasReceivedFirstMix(false);
_streams.skipped.clear();
_streams.inactive.clear();
_streams.active.clear();
}
}

View file

@ -23,6 +23,8 @@ Item {
width: root.pane.width
property bool failAfterSignUp: false
onWidthChanged: d.resize();
function login() {
flavorText.visible = false
mainTextContainer.visible = false
@ -127,7 +129,7 @@ Item {
Column {
id: form
width: parent.width
onHeightChanged: d.resize(); onWidthChanged: d.resize();
onHeightChanged: d.resize();
anchors {
top: mainTextContainer.bottom

View file

@ -44,14 +44,14 @@ Rectangle {
onPasswordChanged: {
var use3DKeyboard = (typeof MenuInterface === "undefined") ? false : MenuInterface.isOptionChecked("Use 3D Keyboard");
var use3DKeyboard = (typeof KeyboardScriptingInterface === "undefined") ? false : KeyboardScriptingInterface.use3DKeyboard;
if (use3DKeyboard) {
KeyboardScriptingInterface.password = password;
}
}
onRaisedChanged: {
var use3DKeyboard = (typeof MenuInterface === "undefined") ? false : MenuInterface.isOptionChecked("Use 3D Keyboard");
var use3DKeyboard = (typeof KeyboardScriptingInterface === "undefined") ? false : KeyboardScriptingInterface.use3DKeyboard;
if (!use3DKeyboard) {
keyboardBase.height = raised ? raisedHeight : 0;
keyboardBase.visible = raised;

View file

@ -61,7 +61,7 @@ Item {
RalewaySemiBold {
text: Account.loggedIn ? qsTr("Log out") : qsTr("Log in")
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
Layout.alignment: Qt.AlignRight
font.pixelSize: 20
color: "#afafaf"
}
@ -71,7 +71,7 @@ Item {
height: Account.loggedIn ? parent.height/2 - parent.spacing/2 : 0
text: Account.loggedIn ? "[" + tabletRoot.usernameShort + "]" : ""
horizontalAlignment: Text.AlignRight
anchors.right: parent.right
Layout.alignment: Qt.AlignRight
font.pixelSize: 20
color: "#afafaf"
}

View file

@ -2572,6 +2572,8 @@ void Application::cleanupBeforeQuit() {
QString webengineRemoteDebugging = QProcessEnvironment::systemEnvironment().value("QTWEBENGINE_REMOTE_DEBUGGING", "false");
qCDebug(interfaceapp) << "QTWEBENGINE_REMOTE_DEBUGGING =" << webengineRemoteDebugging;
DependencyManager::prepareToExit();
if (tracing::enabled()) {
auto tracer = DependencyManager::get<tracing::Tracer>();
tracer->stopTracing();
@ -2684,6 +2686,7 @@ void Application::cleanupBeforeQuit() {
// destroy Audio so it and its threads have a chance to go down safely
// this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "stop");
DependencyManager::destroy<AudioClient>();
DependencyManager::destroy<AudioInjectorManager>();
DependencyManager::destroy<AudioScriptingInterface>();
@ -2775,7 +2778,7 @@ Application::~Application() {
// quit the thread used by the closure event sender
closeEventSender->thread()->quit();
// Can't log to file passed this point, FileLogger about to be deleted
// Can't log to file past this point, FileLogger about to be deleted
qInstallMessageHandler(LogHandler::verboseMessageHandler);
_renderEventHandler->deleteLater();
@ -6304,6 +6307,11 @@ void Application::update(float deltaTime) {
PerformanceTimer perfTimer("enqueueFrame");
getMain3DScene()->enqueueFrame();
}
// If the display plugin is inactive then the frames won't be processed so process them here.
if (!getActiveDisplayPlugin()->isActive()) {
getMain3DScene()->processTransactionQueue();
}
}
void Application::updateRenderArgs(float deltaTime) {

View file

@ -247,25 +247,35 @@ QVariantMap AvatarBookmarks::getAvatarDataToBookmark() {
bookmark.insert(ENTRY_AVATAR_URL, avatarUrl);
bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale);
QScriptEngine scriptEngine;
QVariantList wearableEntities;
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
auto avatarEntities = myAvatar->getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
auto entity = entityTree->findEntityByID(entityID);
if (!entity || !isWearableEntity(entity)) {
continue;
if (entityTree) {
QScriptEngine scriptEngine;
auto avatarEntities = myAvatar->getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
auto entity = entityTree->findEntityByID(entityID);
if (!entity || !isWearableEntity(entity)) {
continue;
}
QVariantMap avatarEntityData;
EncodeBitstreamParams params;
auto desiredProperties = entity->getEntityProperties(params);
desiredProperties += PROP_LOCAL_POSITION;
desiredProperties += PROP_LOCAL_ROTATION;
desiredProperties -= PROP_JOINT_ROTATIONS_SET;
desiredProperties -= PROP_JOINT_ROTATIONS;
desiredProperties -= PROP_JOINT_TRANSLATIONS_SET;
desiredProperties -= PROP_JOINT_TRANSLATIONS;
EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant();
wearableEntities.append(QVariant(avatarEntityData));
}
QVariantMap avatarEntityData;
EncodeBitstreamParams params;
auto desiredProperties = entity->getEntityProperties(params);
desiredProperties += PROP_LOCAL_POSITION;
desiredProperties += PROP_LOCAL_ROTATION;
EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant();
wearableEntities.append(QVariant(avatarEntityData));
}
bookmark.insert(ENTRY_AVATAR_ENTITIES, wearableEntities);
return bookmark;

View file

@ -364,8 +364,6 @@ Menu::Menu() {
qApp->setHmdTabletBecomesToolbarSetting(action->isChecked());
});
addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::Use3DKeyboard, 0, true);
// Developer > Render >>>
MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render");

View file

@ -213,7 +213,6 @@ namespace MenuOption {
const QString TurnWithHead = "Turn using Head";
const QString UseAudioForMouth = "Use Audio for Mouth";
const QString UseCamera = "Use Camera";
const QString Use3DKeyboard = "Use 3D Keyboard";
const QString VelocityFilter = "Velocity Filter";
const QString VisibleToEveryone = "Everyone";
const QString VisibleToFriends = "Friends";

View file

@ -19,7 +19,6 @@
#include <QStandardPaths>
static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head";
static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment";
static const QString ENTITY_MODEL_STRING = "Entity Model";
ModelSelector::ModelSelector() {

View file

@ -32,3 +32,7 @@ void KeyboardScriptingInterface::setPassword(bool password) {
void KeyboardScriptingInterface::loadKeyboardFile(const QString& keyboardFile) {
DependencyManager::get<Keyboard>()->loadKeyboardFile(keyboardFile);
}
bool KeyboardScriptingInterface::getUse3DKeyboard() {
return DependencyManager::get<Keyboard>()->getUse3DKeyboard();
}

View file

@ -30,6 +30,7 @@ class KeyboardScriptingInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(bool raised READ isRaised WRITE setRaised)
Q_PROPERTY(bool password READ isPassword WRITE setPassword)
Q_PROPERTY(bool use3DKeyboard READ getUse3DKeyboard);
public:
Q_INVOKABLE void loadKeyboardFile(const QString& string);
@ -39,5 +40,7 @@ private:
bool isPassword();
void setPassword(bool password);
bool getUse3DKeyboard();
};
#endif

View file

@ -241,6 +241,17 @@ void Keyboard::registerKeyboardHighlighting() {
selection->enableListToScene(KEY_PRESSED_HIGHLIGHT);
}
bool Keyboard::getUse3DKeyboard() const {
return _use3DKeyboardLock.resultWithReadLock<bool>([&] {
return _use3DKeyboard.get();
});
}
void Keyboard::setUse3DKeyboard(bool use) {
_use3DKeyboardLock.withWriteLock([&] {
_use3DKeyboard.set(use);
});
}
void Keyboard::createKeyboard() {
auto pointerManager = DependencyManager::get<PointerManager>();

View file

@ -23,6 +23,7 @@
#include <Sound.h>
#include <AudioInjector.h>
#include <shared/ReadWriteLockable.h>
#include <SettingHandle.h>
#include "ui/overlays/Overlay.h"
@ -97,6 +98,9 @@ public:
bool isPassword() const;
void setPassword(bool password);
bool getUse3DKeyboard() const;
void setUse3DKeyboard(bool use);
void loadKeyboardFile(const QString& keyboardFile);
QVector<OverlayID> getKeysID();
@ -143,6 +147,9 @@ private:
SharedSoundPointer _keySound { nullptr };
std::shared_ptr<QTimer> _layerSwitchTimer { std::make_shared<QTimer>() };
mutable ReadWriteLockable _use3DKeyboardLock;
Setting::Handle<bool> _use3DKeyboard { "use3DKeyboard", true };
QString _typedCharacters;
TextDisplay _textDisplay;
Anchor _anchor;

View file

@ -24,6 +24,7 @@
#include "Snapshot.h"
#include "SnapshotAnimated.h"
#include "UserActivityLogger.h"
#include "ui/Keyboard.h"
void setupPreferences() {
auto preferences = DependencyManager::get<Preferences>();
@ -119,6 +120,12 @@ void setupPreferences() {
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use reticle cursor instead of arrow", getter, setter));
}
{
auto getter = []()->bool { return DependencyManager::get<Keyboard>()->getUse3DKeyboard(); };
auto setter = [](bool value) { DependencyManager::get<Keyboard>()->setUse3DKeyboard(value); };
preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use Virtual Keyboard", getter, setter));
}
{
auto getter = []()->bool { return qApp->getMiniTabletEnabled(); };
auto setter = [](bool value) { qApp->setMiniTabletEnabled(value); };

View file

@ -302,7 +302,6 @@ void AudioClient::customDeleter() {
#if defined(Q_OS_ANDROID)
_shouldRestartInputSetup = false;
#endif
stop();
deleteLater();
}

View file

@ -74,6 +74,9 @@ public:
#endif
return hashCode;
}
static void prepareToExit() { manager()._exiting = true; }
private:
static DependencyManager& manager();
@ -84,6 +87,8 @@ private:
QHash<size_t, QSharedPointer<Dependency>> _instanceHash;
QHash<size_t, size_t> _inheritanceHash;
bool _exiting { false };
};
template <typename T>
@ -95,9 +100,17 @@ QSharedPointer<T> DependencyManager::get() {
instance = qSharedPointerCast<T>(manager().safeGet(hashCode));
#ifndef QT_NO_DEBUG
// debug builds...
if (instance.isNull()) {
qWarning() << "DependencyManager::get(): No instance available for" << typeid(T).name();
}
#else
// for non-debug builds, don't print "No instance available" during shutdown, because
// the act of printing this often causes crashes (because the LogHandler has-been/is-being
// deleted).
if (!manager()._exiting && instance.isNull()) {
qWarning() << "DependencyManager::get(): No instance available for" << typeid(T).name();
}
#endif
}

View file

@ -61,6 +61,8 @@ Script.include("/~/system/libraries/controllers.js");
this.reticleMinY = MARGIN;
this.reticleMaxY = 0;
this.lastUnexpectedChildrenCheckTime = 0;
this.endedGrab = 0;
this.MIN_HAPTIC_PULSE_INTERVAL = 500; // ms
var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX
@ -144,7 +146,12 @@ Script.include("/~/system/libraries/controllers.js");
// compute the mass for the purpose of energy and how quickly to move object
this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density);
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
// Debounce haptic pules. Can occur as near grab controller module vacillates between being ready or not due to
// changing positions and floating point rounding.
if (Date.now() - this.endedGrab > this.MIN_HAPTIC_PULSE_INTERVAL) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
}
unhighlightTargetEntity(this.targetEntityID);
var message = {
hand: this.hand,
@ -256,7 +263,7 @@ Script.include("/~/system/libraries/controllers.js");
};
this.endFarParentGrab = function (controllerData) {
this.hapticTargetID = null;
this.endedGrab = Date.now();
// var endProps = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
var endProps = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
if (this.thisFarGrabJointIsParent(endProps)) {
@ -410,11 +417,6 @@ Script.include("/~/system/libraries/controllers.js");
if (targetEntity) {
var gtProps = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES);
if (entityIsGrabbable(gtProps)) {
// give haptic feedback
if (gtProps.id !== this.hapticTargetID) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
this.hapticTargetID = gtProps.id;
}
// if we've attempted to grab a child, roll up to the root of the tree
var groupRootProps = findGroupParent(controllerData, gtProps);
if (entityIsGrabbable(groupRootProps)) {

View file

@ -24,7 +24,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
this.hand = hand;
this.targetEntityID = null;
this.actionID = null; // action this script created...
this.hapticTargetID = null;
this.parameters = makeDispatcherModuleParameters(
500,
@ -164,10 +163,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
break;
}
if (entityIsGrabbable(props) || entityIsCloneable(props)) {
if (props.id !== this.hapticTargetID) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
this.hapticTargetID = props.id;
}
if (!entityIsCloneable(props)) {
// if we've attempted to grab a non-cloneable child, roll up to the root of the tree
var groupRootProps = findGroupParent(controllerData, props);
@ -199,7 +194,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
return makeRunningValues(true, [this.targetEntityID], []);
}
} else {
this.hapticTargetID = null;
return makeRunningValues(false, [], []);
}
};
@ -209,7 +203,6 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE &&
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
this.endNearGrabAction();
this.hapticTargetID = null;
return makeRunningValues(false, [], []);
}

View file

@ -11,7 +11,7 @@
TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS,
findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH,
HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME,
TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid,
TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, NEAR_GRAB_DISTANCE,
distanceBetweenEntityLocalPositionAndBoundingBox, getGrabbableData, getGrabPointSphereOffset, DISPATCHER_PROPERTIES
*/
@ -24,15 +24,6 @@ Script.include("/~/system/libraries/controllers.js");
// XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
// XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC;
// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378
var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral
function getGrabOffset(handController) {
var offset = getGrabPointSphereOffset(handController, true);
offset.y = -offset.y;
return Vec3.multiply(MyAvatar.sensorToWorldScale, offset);
}
function NearParentingGrabEntity(hand) {
this.hand = hand;
this.targetEntityID = null;
@ -40,7 +31,6 @@ Script.include("/~/system/libraries/controllers.js");
this.previousParentID = {};
this.previousParentJointIndex = {};
this.previouslyUnhooked = {};
this.hapticTargetID = null;
this.lastUnequipCheckTime = 0;
this.autoUnequipCounter = 0;
this.lastUnexpectedChildrenCheckTime = 0;
@ -138,7 +128,6 @@ Script.include("/~/system/libraries/controllers.js");
};
this.endNearParentingGrabEntity = function (controllerData) {
this.hapticTargetID = null;
var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
if (this.thisHandIsParent(props) && !this.robbed) {
Entities.editEntity(this.targetEntityID, {
@ -169,8 +158,10 @@ Script.include("/~/system/libraries/controllers.js");
this.lastUnequipCheckTime = now;
if (props.parentID === MyAvatar.SELF_ID) {
var tearAwayDistance = TEAR_AWAY_DISTANCE * MyAvatar.sensorToWorldScale;
var controllerIndex = (this.hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand);
var controllerGrabOffset = getGrabOffset(controllerIndex);
var controllerIndex =
this.hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand;
var controllerGrabOffset = getGrabPointSphereOffset(controllerIndex, true);
controllerGrabOffset = Vec3.multiply(-MyAvatar.sensorToWorldScale, controllerGrabOffset);
var distance = distanceBetweenEntityLocalPositionAndBoundingBox(props, controllerGrabOffset);
if (distance > tearAwayDistance) {
this.autoUnequipCounter++;
@ -233,21 +224,18 @@ Script.include("/~/system/libraries/controllers.js");
// nearbyEntityProperties is already sorted by length from controller
var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand];
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
var nearGrabDistance = NEAR_GRAB_DISTANCE * sensorScaleFactor;
var nearGrabRadius = NEAR_GRAB_RADIUS * sensorScaleFactor;
for (var i = 0; i < nearbyEntityProperties.length; i++) {
var props = nearbyEntityProperties[i];
var handPosition = controllerData.controllerLocations[this.hand].position;
var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props);
var distance = Vec3.distance(handPosition, props.position);
if ((dist > TEAR_AWAY_DISTANCE) ||
(distance > NEAR_GRAB_RADIUS * sensorScaleFactor)) {
var grabPosition = controllerData.controllerLocations[this.hand].position; // Is offset from hand position.
var dist = distanceBetweenPointAndEntityBoundingBox(grabPosition, props);
var distance = Vec3.distance(grabPosition, props.position);
if ((dist > nearGrabDistance) ||
(distance > nearGrabRadius)) { // Only smallish entities can be near grabbed.
continue;
}
if (entityIsGrabbable(props) || entityIsCloneable(props)) {
// give haptic feedback
if (props.id !== this.hapticTargetID) {
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
this.hapticTargetID = props.id;
}
if (!entityIsCloneable(props)) {
// if we've attempted to grab a non-cloneable child, roll up to the root of the tree
var groupRootProps = findGroupParent(controllerData, props);
@ -284,7 +272,6 @@ Script.include("/~/system/libraries/controllers.js");
return makeRunningValues(true, [this.targetEntityID], []);
}
} else {
this.hapticTargetID = null;
this.robbed = false;
return makeRunningValues(false, [], []);
}
@ -303,7 +290,6 @@ Script.include("/~/system/libraries/controllers.js");
// entity was deleted
this.grabbing = false;
this.targetEntityID = null;
this.hapticTargetID = null;
this.robbed = false;
return makeRunningValues(false, [], []);
}

View file

@ -916,7 +916,7 @@ div.refresh input[type="button"] {
}
.draggable-number div {
height: 28px;
width: 92px;
width: 124px;
}
.draggable-number.text {
display: inline-block;
@ -929,6 +929,7 @@ div.refresh input[type="button"] {
height: 28px;
width: 100%;
line-height: 2;
box-sizing: border-box;
}
.draggable-number.text:hover {
cursor: ew-resize;
@ -944,12 +945,12 @@ div.refresh input[type="button"] {
cursor: default;
}
.draggable-number.left-arrow {
top: -5px;
top: 3px;
left: 0px;
transform: rotate(180deg);
}
.draggable-number.right-arrow {
top: -5px;
top: 3px;
right: 0px;
}
.draggable-number input[type=number] {
@ -971,14 +972,14 @@ div.refresh input[type="button"] {
left: 12px;
}
.draggable-number.fstuple + .draggable-number.fstuple {
padding-left: 28px;
margin-left: 28px;
}
.draggable-number.fstuple input {
right: -10px;
}
.draggable-number.fstuple .sublabel {
position: absolute;
top: 0;
top: 6px;
left: -16px;
font-family: FiraSans-SemiBold;
font-size: 15px;

View file

@ -309,6 +309,9 @@ For usage and examples: colpick.com/plugin
},
// Fix the values if the user enters a negative or high value
fixHSB = function (hsb) {
hsb.h = isNaN(hsb.h) ? 0 : hsb.h;
hsb.s = isNaN(hsb.s) ? 0 : hsb.s;
hsb.b = isNaN(hsb.b) ? 0 : hsb.b;
return {
h: Math.min(360, Math.max(0, hsb.h)),
s: Math.min(100, Math.max(0, hsb.s)),
@ -316,6 +319,9 @@ For usage and examples: colpick.com/plugin
};
},
fixRGB = function (rgb) {
rgb.r = isNaN(rgb.r) ? 0 : rgb.r;
rgb.g = isNaN(rgb.g) ? 0 : rgb.g;
rgb.b = isNaN(rgb.b) ? 0 : rgb.b;
return {
r: Math.min(255, Math.max(0, rgb.r)),
g: Math.min(255, Math.max(0, rgb.g)),

View file

@ -23,6 +23,20 @@ function DraggableNumber(min, max, step, decimals, dragStart, dragEnd) {
}
DraggableNumber.prototype = {
showInput: function() {
this.elText.style.visibility = "hidden";
this.elLeftArrow.style.visibility = "hidden";
this.elRightArrow.style.visibility = "hidden";
this.elInput.style.opacity = 1;
},
hideInput: function() {
this.elText.style.visibility = "visible";
this.elLeftArrow.style.visibility = "visible";
this.elRightArrow.style.visibility = "visible";
this.elInput.style.opacity = 0;
},
mouseDown: function(event) {
if (event.target === this.elText) {
this.initialMouseEvent = event;
@ -36,8 +50,8 @@ DraggableNumber.prototype = {
if (event.target === this.elText && this.initialMouseEvent) {
let dx = event.clientX - this.initialMouseEvent.clientX;
if (Math.abs(dx) <= DELTA_X_FOCUS_THRESHOLD) {
this.elInput.style.visibility = "visible";
this.elText.style.visibility = "hidden";
this.showInput();
this.elInput.focus();
}
this.initialMouseEvent = null;
}
@ -125,9 +139,8 @@ DraggableNumber.prototype = {
this.setValue(this.elInput.value);
},
inputBlur: function() {
this.elInput.style.visibility = "hidden";
this.elText.style.visibility = "visible";
inputBlur: function(ev) {
this.hideInput();
},
initialize: function() {
@ -171,13 +184,14 @@ DraggableNumber.prototype = {
if (this.step !== undefined) {
this.elInput.setAttribute("step", this.step);
}
this.elInput.style.visibility = "hidden";
this.elInput.style.opacity = 0;
this.elInput.addEventListener("change", this.onInputChange);
this.elInput.addEventListener("blur", this.onInputBlur);
this.elInput.addEventListener("focus", this.showInput.bind(this));
this.elText.appendChild(this.elLeftArrow);
this.elText.appendChild(this.elInput);
this.elText.appendChild(this.elRightArrow);
this.elDiv.appendChild(this.elLeftArrow);
this.elDiv.appendChild(this.elInput);
this.elDiv.appendChild(this.elRightArrow);
this.elDiv.appendChild(this.elText);
}
};

View file

@ -197,7 +197,7 @@ const GROUPS = [
multiplier: DEGREES_TO_RADIANS,
decimals: 2,
unit: "deg",
propertyID: "keyLight.direction.x",
propertyID: "keyLight.direction.y",
showPropertyRule: { "keyLightMode": "enabled" },
},
{
@ -206,7 +206,7 @@ const GROUPS = [
multiplier: DEGREES_TO_RADIANS,
decimals: 2,
unit: "deg",
propertyID: "keyLight.direction.y",
propertyID: "keyLight.direction.x",
showPropertyRule: { "keyLightMode": "enabled" },
},
{
@ -2149,7 +2149,7 @@ function createTupleNumberInput(property, subLabel) {
propertyData.decimals, dragStartFunction, dragEndFunction);
elDraggableNumber.elInput.setAttribute("id", elementID);
elDraggableNumber.elDiv.className += " fstuple";
elDraggableNumber.elText.insertBefore(elLabel, elDraggableNumber.elLeftArrow);
elDraggableNumber.elDiv.insertBefore(elLabel, elDraggableNumber.elLeftArrow);
return elDraggableNumber;
}

View file

@ -53,6 +53,7 @@
TEAR_AWAY_DISTANCE:true,
TEAR_AWAY_COUNT:true,
TEAR_AWAY_CHECK_TIME:true,
NEAR_GRAB_DISTANCE: true,
distanceBetweenPointAndEntityBoundingBox:true,
entityIsEquipped:true,
entityIsFarGrabbedByOther:true,
@ -99,6 +100,10 @@ NEAR_GRAB_RADIUS = 1.0;
TEAR_AWAY_DISTANCE = 0.15; // ungrab an entity if its bounding-box moves this far from the hand
TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away
TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks
NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance.
// Smaller than TEAR_AWAY_DISTANCE for hysteresis.
DISPATCHER_HOVERING_LIST = "dispactherHoveringList";
DISPATCHER_HOVERING_STYLE = {
isOutlineSmooth: true,

View file

@ -50,10 +50,12 @@ if (WIN32)
)
# 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
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$<TARGET_FILE_DIR:${TARGET_NAME}>/AppDataHighFidelity"
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "AppDataHighFidelity"
)
# add a custom command to copy the SSL DLLs
@ -62,5 +64,12 @@ if (WIN32)
POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory "$ENV{VCPKG_ROOT}/installed/x64-windows/bin" "$<TARGET_FILE_DIR:${TARGET_NAME}>"
)
elseif (APPLE)
# add a custom command to copy the empty Apps/Data High Fidelity folder (i.e. - a valid folder with no entities)
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/AppDataHighFidelity" "$<TARGET_FILE_DIR:${TARGET_NAME}>/AppDataHighFidelity"
)
endif ()
endif ()

View file

@ -1,6 +1,6 @@
# nitpick
Nitpick is a stand alone application that provides a mechanism for regression testing. The general idea is simple:
Nitpick is a stand alone application that provides a mechanism for regression testing. The general idea is simple:
* Each test folder has a script that produces a set of snapshots.
* The snapshots are compared to a 'canonical' set of images that have been produced beforehand.
* The result, if any test failed, is a zipped folder describing the failure.
@ -22,9 +22,9 @@ Nitpick is built as part of the High Fidelity build.
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-v1.0.exe`)
1. Enter installer name (i.e. `nitpick-installer-v1.1.exe`)
1. Click "OK"
1. Copy created installer to https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.0.exe: aws s3 cp nitpick-installer-v1.0.exe s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.0.exe
1. Copy created installer to https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.1.exe: aws s3 cp nitpick-installer-v1.1.exe s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.1.exe
#### Mac
These steps assume the hifi repository has been cloned to `~/hifi`.
1. (first time) Install brew
@ -37,12 +37,12 @@ These steps assume the hifi repository has been cloned to `~/hifi`.
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-v1.0 nitpick-installer-v1.0.dmg .`
1. Create installer (note final period).In a terminal: `create-dmg --volname nitpick-installer-v1.1 nitpick-installer-v1.1.dmg .`
Make sure to wait for completion.
1. Copy created installer to AWS: `~/Library/Python/3.7/bin/aws s3 cp nitpick-installer-v1.0.dmg s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.0.dmg`
1. Copy created installer to AWS: `~/Library/Python/3.7/bin/aws s3 cp nitpick-installer-v1.1.dmg s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.1.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-v1.0.exe)
1. (First time) download and install vc_redist.x64.exe (available at https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.1.exe)
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. 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/
@ -52,7 +52,7 @@ These steps assume the hifi repository has been cloned to `~/hifi`.
1. Leave region name and ouput format as default [None]
1. Install the latest release of Boto3 via pip: `pip install boto3`
1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.0.exe>)
1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.1.exe>)
1. Double click on the installer and install to a convenient location
![](./setup_7z.PNG)
@ -76,14 +76,14 @@ In a terminal: `python3 get-pip.py --user`
1. Enter the secret key
1. Leave region name and ouput format as default [None]
1. Install the latest release of Boto3 via pip: pip3 install boto3
1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Mac/nitpick-installer-v1.0.dmg>).
1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Mac/nitpick-installer-v1.1.dmg>).
1. Double-click on the downloaded image to mount it
1. Create a folder for the nitpick files (e.g. ~/nitpick)
If this folder exists then delete all it's contents.
1. Copy the downloaded files to the folder
In a terminal:
`cd ~/nitpick`
`cp -r /Volumes/nitpick-installer-v1.0/* .`
`cp -r /Volumes/nitpick-installer-v1.1/* .`
1. __To run nitpick, cd to the folder that you copied to and run `./nitpick`__
# Usage

View file

@ -22,12 +22,12 @@ AWSInterface::AWSInterface(QObject* parent) : QObject(parent) {
}
void AWSInterface::createWebPageFromResults(const QString& testResults,
const QString& snapshotDirectory,
const QString& workingDirectory,
QCheckBox* updateAWSCheckBox,
QLineEdit* urlLineEdit) {
_testResults = testResults;
_snapshotDirectory = snapshotDirectory;
_workingDirectory = workingDirectory;
_urlLineEdit = urlLineEdit;
_urlLineEdit->setEnabled(false);
@ -36,6 +36,9 @@ void AWSInterface::createWebPageFromResults(const QString& testResults,
if (updateAWSCheckBox->isChecked()) {
updateAWS();
QMessageBox::information(0, "Success", "HTML file has been created and copied to AWS");
} else {
QMessageBox::information(0, "Success", "HTML file has been created");
}
}
@ -43,14 +46,14 @@ void AWSInterface::extractTestFailuresFromZippedFolder() {
// For a test results zip file called `D:/tt/TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ].zip`
// the folder will be called `TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]`
// and, this folder will be in the working directory
QStringList parts =_testResults.split('/');
QString zipFolderName = _snapshotDirectory + "/" + parts[parts.length() - 1].split('.')[0];
QStringList parts = _testResults.split('/');
QString zipFolderName = _workingDirectory + "/" + parts[parts.length() - 1].split('.')[0];
if (QDir(zipFolderName).exists()) {
QDir dir = zipFolderName;
dir.removeRecursively();
}
JlCompress::extractDir(_testResults, _snapshotDirectory);
JlCompress::extractDir(_testResults, _workingDirectory);
}
void AWSInterface::createHTMLFile() {
@ -60,7 +63,7 @@ void AWSInterface::createHTMLFile() {
QString filename = pathComponents[pathComponents.length() - 1];
_resultsFolder = filename.left(filename.length() - 4);
QString resultsPath = _snapshotDirectory + "/" + _resultsFolder + "/";
QString resultsPath = _workingDirectory + "/" + _resultsFolder + "/";
QDir().mkdir(resultsPath);
_htmlFilename = resultsPath + HTML_FILENAME;
@ -116,7 +119,7 @@ void AWSInterface::writeTitle(QTextStream& stream) {
QString date_buildorPR_hostName = tokens[tokens.length() - 1].split("--")[1].split(".")[0];
QString buildorPR = date_buildorPR_hostName.split('(')[1].split(')')[0];
QString hostName = date_buildorPR_hostName.split('[')[1].split(']')[0];
QString hostName = date_buildorPR_hostName.split('[')[1].split(']')[0];
QStringList dateList = date_buildorPR_hostName.split('(')[0].split('_')[0].split('-');
QString year = dateList[0];
@ -156,7 +159,7 @@ void AWSInterface::writeTable(QTextStream& stream) {
// Note that failures are processed first, then successes
QStringList originalNamesFailures;
QStringList originalNamesSuccesses;
QDirIterator it1(_snapshotDirectory.toStdString().c_str());
QDirIterator it1(_workingDirectory);
while (it1.hasNext()) {
QString nextDirectory = it1.next();
@ -189,11 +192,11 @@ void AWSInterface::writeTable(QTextStream& stream) {
for (int i = 0; i < originalNamesSuccesses.length(); ++i) {
newNamesSuccesses.append(originalNamesSuccesses[i].split("--tests.")[1]);
}
_htmlFailuresFolder = _snapshotDirectory + "/" + _resultsFolder + "/" + FAILURES_FOLDER;
_htmlFailuresFolder = _workingDirectory + "/" + _resultsFolder + "/" + FAILURES_FOLDER;
QDir().mkdir(_htmlFailuresFolder);
_htmlSuccessesFolder = _snapshotDirectory + "/" + _resultsFolder + "/" + SUCCESSES_FOLDER;
_htmlSuccessesFolder = _workingDirectory + "/" + _resultsFolder + "/" + SUCCESSES_FOLDER;
QDir().mkdir(_htmlSuccessesFolder);
for (int i = 0; i < newNamesFailures.length(); ++i) {
@ -204,7 +207,11 @@ void AWSInterface::writeTable(QTextStream& stream) {
QDir().rename(originalNamesSuccesses[i], _htmlSuccessesFolder + "/" + newNamesSuccesses[i]);
}
QDirIterator it2((_htmlFailuresFolder).toStdString().c_str());
// Mac does not read folders in lexicographic order, so this step is divided into 2
// Each test consists of the test name and its index.
QDirIterator it2(_htmlFailuresFolder);
QStringList folderNames;
while (it2.hasNext()) {
QString nextDirectory = it2.next();
@ -214,10 +221,17 @@ void AWSInterface::writeTable(QTextStream& stream) {
}
QStringList pathComponents = nextDirectory.split('/');
QString filename = pathComponents[pathComponents.length() - 1];
int splitIndex = filename.lastIndexOf(".");
QString testName = filename.left(splitIndex).replace(".", " / ");
QString testNumber = filename.right(filename.length() - (splitIndex + 1));
QString folderName = pathComponents[pathComponents.length() - 1];
folderNames << folderName;
}
folderNames.sort();
for (const auto& folderName : folderNames) {
int splitIndex = folderName.lastIndexOf(".");
QString testName = folderName.left(splitIndex).replace('.', " / ");
int testNumber = folderName.right(folderName.length() - (splitIndex + 1)).toInt();
// The failures are ordered lexicographically, so we know that we can rely on the testName changing to create a new table
if (testName != previousTestName) {
@ -232,14 +246,14 @@ void AWSInterface::writeTable(QTextStream& stream) {
openTable(stream);
}
createEntry(testNumber.toInt(), filename, stream, true);
createEntry(testNumber, folderName, stream, true);
}
closeTable(stream);
stream << "\t" << "\t" << "<font color=\"blue\">\n";
stream << "\t" << "\t" << "<h1>The following tests passed:</h1>";
QDirIterator it3((_htmlSuccessesFolder).toStdString().c_str());
QDirIterator it3(_htmlSuccessesFolder);
while (it3.hasNext()) {
QString nextDirectory = it3.next();
@ -290,7 +304,7 @@ void AWSInterface::closeTable(QTextStream& stream) {
void AWSInterface::createEntry(int index, const QString& testResult, QTextStream& stream, const bool isFailure) {
stream << "\t\t\t<tr>\n";
stream << "\t\t\t\t<td><h1>" << QString::number(index) << "</h1></td>\n";
// For a test named `D:/t/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf/Failure_1--tests.engine.interaction.pick.collision.many.00000`
// we need `Failure_1--tests.engine.interaction.pick.collision.many.00000`
QStringList resultNameComponents = testResult.split('/');
@ -302,11 +316,11 @@ void AWSInterface::createEntry(int index, const QString& testResult, QTextStream
folder = FAILURES_FOLDER;
differenceFileFound = QFile::exists(_htmlFailuresFolder + "/" + resultName + "/Difference Image.png");
} else {
folder = SUCCESSES_FOLDER;
folder = SUCCESSES_FOLDER;
differenceFileFound = QFile::exists(_htmlSuccessesFolder + "/" + resultName + "/Difference Image.png");
}
stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/Actual Image.png\" width = \"576\" height = \"324\" ></td>\n";
stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/Expected Image.png\" width = \"576\" height = \"324\" ></td>\n";
@ -320,7 +334,7 @@ void AWSInterface::createEntry(int index, const QString& testResult, QTextStream
}
void AWSInterface::updateAWS() {
QString filename = _snapshotDirectory + "/updateAWS.py";
QString filename = _workingDirectory + "/updateAWS.py";
if (QFile::exists(filename)) {
QFile::remove(filename);
}
@ -337,7 +351,7 @@ void AWSInterface::updateAWS() {
stream << "import boto3\n";
stream << "s3 = boto3.resource('s3')\n\n";
QDirIterator it1(_htmlFailuresFolder.toStdString().c_str());
QDirIterator it1(_htmlFailuresFolder);
while (it1.hasNext()) {
QString nextDirectory = it1.next();
@ -345,26 +359,26 @@ void AWSInterface::updateAWS() {
if (nextDirectory.right(1) == ".") {
continue;
}
// nextDirectory looks like `D:/t/TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]/failures/engine.render.effect.bloom.00000`
// We need to concatenate the last 3 components, to get `TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]/failures/engine.render.effect.bloom.00000`
QStringList parts = nextDirectory.split('/');
QString filename = parts[parts.length() - 3] + "/" + parts[parts.length() - 2] + "/" + parts[parts.length() - 1];
stream << "data = open('" << _snapshotDirectory << "/" << filename << "/"
stream << "data = open('" << _workingDirectory << "/" << filename << "/"
<< "Actual Image.png"
<< "', 'rb')\n";
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Actual Image.png" << "', Body=data)\n\n";
stream << "data = open('" << _snapshotDirectory << "/" << filename << "/"
stream << "data = open('" << _workingDirectory << "/" << filename << "/"
<< "Expected Image.png"
<< "', 'rb')\n";
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n";
if (QFile::exists(_htmlFailuresFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) {
stream << "data = open('" << _snapshotDirectory << "/" << filename << "/"
stream << "data = open('" << _workingDirectory << "/" << filename << "/"
<< "Difference Image.png"
<< "', 'rb')\n";
@ -372,7 +386,7 @@ void AWSInterface::updateAWS() {
}
}
QDirIterator it2(_htmlSuccessesFolder.toStdString().c_str());
QDirIterator it2(_htmlSuccessesFolder);
while (it2.hasNext()) {
QString nextDirectory = it2.next();
@ -386,20 +400,20 @@ void AWSInterface::updateAWS() {
QStringList parts = nextDirectory.split('/');
QString filename = parts[parts.length() - 3] + "/" + parts[parts.length() - 2] + "/" + parts[parts.length() - 1];
stream << "data = open('" << _snapshotDirectory << "/" << filename << "/"
stream << "data = open('" << _workingDirectory << "/" << filename << "/"
<< "Actual Image.png"
<< "', 'rb')\n";
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Actual Image.png" << "', Body=data)\n\n";
stream << "data = open('" << _snapshotDirectory << "/" << filename << "/"
stream << "data = open('" << _workingDirectory << "/" << filename << "/"
<< "Expected Image.png"
<< "', 'rb')\n";
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n";
if (QFile::exists(_htmlSuccessesFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) {
stream << "data = open('" << _snapshotDirectory << "/" << filename << "/"
stream << "data = open('" << _workingDirectory << "/" << filename << "/"
<< "Difference Image.png"
<< "', 'rb')\n";
@ -407,7 +421,7 @@ void AWSInterface::updateAWS() {
}
}
stream << "data = open('" << _snapshotDirectory << "/" << _resultsFolder << "/" << HTML_FILENAME << "', 'rb')\n";
stream << "data = open('" << _workingDirectory << "/" << _resultsFolder << "/" << HTML_FILENAME << "', 'rb')\n";
stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << _resultsFolder << "/"
<< HTML_FILENAME << "', Body=data, ContentType='text/html')\n";
@ -426,10 +440,10 @@ void AWSInterface::updateAWS() {
[=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); });
#ifdef Q_OS_WIN
QStringList parameters = QStringList() << filename ;
QStringList parameters = QStringList() << filename;
process->start(_pythonCommand, parameters);
#elif defined Q_OS_MAC
QStringList parameters = QStringList() << "-c" << _pythonCommand + " " + filename;
QStringList parameters = QStringList() << "-c" << _pythonCommand + " " + filename;
process->start("sh", parameters);
#endif
}
}

View file

@ -26,7 +26,7 @@ public:
explicit AWSInterface(QObject* parent = 0);
void createWebPageFromResults(const QString& testResults,
const QString& snapshotDirectory,
const QString& workingDirectory,
QCheckBox* updateAWSCheckBox,
QLineEdit* urlLineEdit);
@ -49,7 +49,7 @@ public:
private:
QString _testResults;
QString _snapshotDirectory;
QString _workingDirectory;
QString _resultsFolder;
QString _htmlFailuresFolder;
QString _htmlSuccessesFolder;

View file

@ -12,12 +12,12 @@
#include <QtWidgets/QMessageBox>
Downloader::Downloader(QUrl fileURL, QObject *parent) : QObject(parent) {
_networkAccessManager.get(QNetworkRequest(fileURL));
connect(
&_networkAccessManager, SIGNAL (finished(QNetworkReply*)),
this, SLOT (fileDownloaded(QNetworkReply*))
);
_networkAccessManager.get(QNetworkRequest(fileURL));
}
void Downloader::fileDownloaded(QNetworkReply* reply) {

View file

@ -105,7 +105,7 @@ int Test::compareImageLists() {
++numberOfFailures;
if (!isInteractiveMode) {
appendTestResultsToFile(_testResultsFolderPath, testResult, _mismatchWindow.getComparisonImage(), true);
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), true);
} else {
_mismatchWindow.exec();
@ -113,7 +113,7 @@ int Test::compareImageLists() {
case USER_RESPONSE_PASS:
break;
case USE_RESPONSE_FAIL:
appendTestResultsToFile(_testResultsFolderPath, testResult, _mismatchWindow.getComparisonImage(), true);
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), true);
break;
case USER_RESPONSE_ABORT:
keepOn = false;
@ -124,7 +124,7 @@ int Test::compareImageLists() {
}
}
} else {
appendTestResultsToFile(_testResultsFolderPath, testResult, _mismatchWindow.getComparisonImage(), false);
appendTestResultsToFile(testResult, _mismatchWindow.getComparisonImage(), false);
}
_progressBar->setValue(i);
@ -134,12 +134,31 @@ int Test::compareImageLists() {
return numberOfFailures;
}
void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestResult testResult, QPixmap comparisonImage, bool hasFailed) {
int Test::checkTextResults() {
// Create lists of failed and passed tests
QStringList nameFilterFailed;
nameFilterFailed << "*.failed.txt";
QStringList testsFailed = QDir(_snapshotDirectory).entryList(nameFilterFailed, QDir::Files, QDir::Name);
QStringList nameFilterPassed;
nameFilterPassed << "*.passed.txt";
QStringList testsPassed = QDir(_snapshotDirectory).entryList(nameFilterPassed, QDir::Files, QDir::Name);
// Add results to Test Results folder
foreach(QString currentFilename, testsFailed) {
}
return testsFailed.length();
}
void Test::appendTestResultsToFile(TestResult testResult, QPixmap comparisonImage, bool hasFailed) {
// Critical error if Test Results folder does not exist
if (!QDir().exists(_testResultsFolderPath)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + _testResultsFolderPath + " not found");
exit(-1);
}
// There are separate subfolders for failures and passes
QString resultFolderPath;
if (hasFailed) {
resultFolderPath = _testResultsFolderPath + "/Failure_" + QString::number(_failureIndex) + "--" +
@ -195,6 +214,22 @@ void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestRe
comparisonImage.save(resultFolderPath + "/" + "Difference Image.png");
}
void::Test::appendTestResultsToFile(QString testResultFilename, bool hasFailed) {
QString resultFolderPath;
if (hasFailed) {
resultFolderPath = _testResultsFolderPath + "/Failure_";
++_failureIndex;
} else {
resultFolderPath = _testResultsFolderPath + "/Success_";
++_successIndex;
}
if (!QFile::copy(testResultFilename, resultFolderPath)) {
//// QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to copy " + sourceFile + " to " + destinationFile);
exit(-1);
}
}
void Test::startTestsEvaluation(const bool isRunningFromCommandLine,
const bool isRunningInAutomaticTestRun,
const QString& snapshotDirectory,
@ -270,9 +305,14 @@ void Test::startTestsEvaluation(const bool isRunningFromCommandLine,
nitpick->downloadFiles(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames, (void *)this);
}
void Test::finishTestsEvaluation() {
// First - compare the pairs of images
int numberOfFailures = compareImageLists();
// Next - check text results
numberOfFailures += checkTextResults();
if (!_isRunningFromCommandLine && !_isRunningInAutomaticTestRun) {
if (numberOfFailures == 0) {
QMessageBox::information(0, "Success", "All images are as expected");
@ -542,7 +582,7 @@ void Test::createAllMDFiles() {
createMDFile(_testsRootDirectory);
}
QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
@ -620,7 +660,7 @@ void Test::createTestAutoScript() {
}
if (createTestAutoScript(_testDirectory)) {
QMessageBox::information(0, "Success", "'nitpick.js` script has been created");
QMessageBox::information(0, "Success", "'testAuto.js` script has been created");
}
}
@ -636,7 +676,7 @@ void Test::createAllTestAutoScripts() {
createTestAutoScript(_testsRootDirectory);
}
QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
@ -653,7 +693,7 @@ void Test::createAllTestAutoScripts() {
}
}
QMessageBox::information(0, "Success", "'nitpick.js' scripts have been created");
QMessageBox::information(0, "Success", "All 'testAuto.js' scripts have been created");
}
bool Test::createTestAutoScript(const QString& directory) {
@ -677,7 +717,7 @@ bool Test::createTestAutoScript(const QString& directory) {
stream << "if (typeof PATH_TO_THE_REPO_PATH_UTILS_FILE === 'undefined') PATH_TO_THE_REPO_PATH_UTILS_FILE = 'https://raw.githubusercontent.com/highfidelity/hifi_tests/master/tests/utils/branchUtils.js';\n";
stream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);\n";
stream << "var nitpick = createAutoTester(Script.resolvePath('.'));\n\n";
stream << "var nitpick = createNitpick(Script.resolvePath('.'));\n\n";
stream << "nitpick.enableAuto();\n\n";
stream << "Script.include('./test.js?raw=true');\n";
@ -704,7 +744,7 @@ void Test::createAllRecursiveScripts() {
createRecursiveScript(_testsRootDirectory, false);
QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(_testsRootDirectory, QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
@ -716,7 +756,7 @@ void Test::createAllRecursiveScripts() {
// Only process directories that have sub-directories
bool hasNoSubDirectories{ true };
QDirIterator it2(directory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it2(directory, QDirIterator::Subdirectories);
while (it2.hasNext()) {
QString directory2 = it2.next();
@ -737,20 +777,21 @@ void Test::createAllRecursiveScripts() {
}
void Test::createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode) {
const QString recursiveTestsFilename("testRecursive.js");
QFile allTestsFilename(topLevelDirectory + "/" + recursiveTestsFilename);
if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) {
const QString recursiveTestsScriptName("testRecursive.js");
const QString recursiveTestsFilename(topLevelDirectory + "/" + recursiveTestsScriptName);
QFile recursiveTestsFile(recursiveTestsFilename);
if (!recursiveTestsFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Failed to create \"" + recursiveTestsFilename + "\" in directory \"" + topLevelDirectory + "\"");
"Failed to create \"" + recursiveTestsScriptName + "\" in directory \"" + topLevelDirectory + "\"");
exit(-1);
}
QTextStream textStream(&allTestsFilename);
QTextStream textStream(&recursiveTestsFile);
textStream << "// This is an automatically generated file, created by auto-tester" << endl;
textStream << "// This is an automatically generated file, created by nitpick" << endl;
// Include 'autoTest.js'
// Include 'nitpick.js'
QString branch = nitpick->getSelectedBranch();
QString user = nitpick->getSelectedUser();
@ -758,7 +799,7 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact
"/tests/utils/branchUtils.js\";"
<< endl;
textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl;
textStream << "var nitpick = createAutoTester(Script.resolvePath(\".\"));" << endl << endl;
textStream << "var nitpick = createNitpick(Script.resolvePath(\".\"));" << endl << endl;
textStream << "var testsRootPath = nitpick.getTestsRootPath();" << endl << endl;
@ -787,7 +828,7 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact
testFound = true;
}
QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(topLevelDirectory, QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
@ -809,7 +850,15 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact
if (interactiveMode && !testFound) {
QMessageBox::information(0, "Failure", "No \"" + TEST_FILENAME + "\" files found");
allTestsFilename.close();
recursiveTestsFile.close();
return;
}
// If 'directories' is empty, this means that this recursive script has no tests to call, so it is redundant
// The script will be closed and deleted
if (directories.length() == 0) {
recursiveTestsFile.close();
QFile::remove(recursiveTestsFilename);
return;
}
@ -821,7 +870,7 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact
textStream << endl;
textStream << "nitpick.runRecursive();" << endl;
allTestsFilename.close();
recursiveTestsFile.close();
}
void Test::createTestsOutline() {
@ -858,7 +907,7 @@ void Test::createTestsOutline() {
int rootDepth { _testDirectory.count('/') };
// Each test is shown as the folder name linking to the matching GitHub URL, and the path to the associated test.md file
QDirIterator it(_testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(_testDirectory, QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
@ -1052,11 +1101,11 @@ void Test::createWebPage(QCheckBox* updateAWSCheckBox, QLineEdit* urlLineEdit) {
return;
}
QString snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store temporary files in",
QString workingDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store temporary files in",
nullptr, QFileDialog::ShowDirsOnly);
if (snapshotDirectory.isNull()) {
if (workingDirectory.isNull()) {
return;
}
_awsInterface.createWebPageFromResults(testResults, snapshotDirectory, updateAWSCheckBox, urlLineEdit);
_awsInterface.createWebPageFromResults(testResults, workingDirectory, updateAWSCheckBox, urlLineEdit);
}

View file

@ -77,6 +77,7 @@ public:
void createRecursiveScript(const QString& topLevelDirectory, bool interactiveMode);
int compareImageLists();
int checkTextResults();
QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory);
@ -84,7 +85,8 @@ public:
void includeTest(QTextStream& textStream, const QString& testPathname);
void appendTestResultsToFile(const QString& testResultsFolderPath, TestResult testResult, QPixmap comparisonImage, bool hasFailed);
void appendTestResultsToFile(TestResult testResult, QPixmap comparisonImage, bool hasFailed);
void appendTestResultsToFile(QString testResultFilename, bool hasFailed);
bool createTestResultsFolderPath(const QString& directory);
QString zipAndDeleteTestResultsFolder();

View file

@ -275,7 +275,7 @@ void TestRailInterface::processDirectoryPython(const QString& directory,
const QString& userGitHub,
const QString& branchGitHub) {
// Loop over all entries in directory
QDirIterator it(directory.toStdString().c_str());
QDirIterator it(directory);
while (it.hasNext()) {
QString nextDirectory = it.next();
@ -855,7 +855,7 @@ QDomElement TestRailInterface::processDirectoryXML(const QString& directory,
QDomElement result = element;
// Loop over all entries in directory
QDirIterator it(directory.toStdString().c_str());
QDirIterator it(directory);
while (it.hasNext()) {
QString nextDirectory = it.next();

View file

@ -332,23 +332,23 @@ void TestRunner::verifyInstallationSucceeded() {
}
void TestRunner::saveExistingHighFidelityAppDataFolder() {
#ifdef Q_OS_WIN
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";
_appDataFolder = dataDirectory + "/High Fidelity";
} else {
// We are running a PR build
_appDataFolder = dataDirectory + "\\High Fidelity - " + getPRNumberFromURL(_url->text());
_appDataFolder = dataDirectory + "/High Fidelity - " + getPRNumberFromURL(_url->text());
}
_savedAppDataFolder = dataDirectory + "/" + UNIQUE_FOLDER_NAME;
if (_savedAppDataFolder.exists()) {
if (QDir(_savedAppDataFolder).exists()) {
_savedAppDataFolder.removeRecursively();
}
if (_appDataFolder.exists()) {
// The original folder is saved in a unique name
_appDataFolder.rename(_appDataFolder.path(), _savedAppDataFolder.path());
@ -356,9 +356,6 @@ void TestRunner::saveExistingHighFidelityAppDataFolder() {
// Copy an "empty" AppData folder (i.e. no entities)
copyFolder(QDir::currentPath() + "/AppDataHighFidelity", _appDataFolder.path());
#elif defined Q_OS_MAC
// TODO: find Mac equivalent of AppData
#endif
}
void TestRunner::createSnapshotFolder() {
@ -369,7 +366,7 @@ void TestRunner::createSnapshotFolder() {
// 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.toStdString().c_str());
QDirIterator it(_snapshotFolder);
while (it.hasNext()) {
QString filename = it.next();
if (filename.right(4) == ".png" || filename.right(4) == ".jpg" || filename.right(4) == ".txt") {
@ -469,12 +466,7 @@ void TestRunner::runInterfaceWithTestScript() {
// Move to an empty area
url = "file:///~serverless/tutorial.json";
} else {
#ifdef Q_OS_WIN
url = "hifi://localhost";
#elif defined Q_OS_MAC
// TODO: Find out Mac equivalent of AppData, then this won't be needed
url = "hifi://localhost/9999,9999,9999";
#endif
}
QString testScript =
@ -535,8 +527,6 @@ void TestRunner::runInterfaceWithTestScript() {
}
void TestRunner::interfaceExecutionComplete() {
killProcesses();
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");
@ -544,6 +534,8 @@ void TestRunner::interfaceExecutionComplete() {
evaluateResults();
killProcesses();
// The High Fidelity AppData folder will be restored after evaluation has completed
}
@ -589,15 +581,11 @@ void TestRunner::addBuildNumberToResults(QString zippedFolderName) {
}
void TestRunner::restoreHighFidelityAppDataFolder() {
#ifdef Q_OS_WIN
_appDataFolder.removeRecursively();
if (_savedAppDataFolder != QDir()) {
_appDataFolder.rename(_savedAppDataFolder.path(), _appDataFolder.path());
}
#elif defined Q_OS_MAC
// TODO: find Mac equivalent of AppData
#endif
}
// Copies a folder recursively

View file

@ -36,7 +36,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) {
_ui.statusLabel->setText("");
_ui.plainTextEdit->setReadOnly(true);
setWindowTitle("Nitpick - v1.0");
setWindowTitle("Nitpick - v1.2");
// Coming soon to a nitpick near you...
//// _helpWindow.textBrowser->setText()