Merge branch 'master' of https://github.com/highfidelity/hifi into cloneables

This commit is contained in:
David Back 2018-05-15 09:53:24 -07:00
commit 623410a0aa
10 changed files with 159 additions and 121 deletions

View file

@ -49,6 +49,7 @@ Item {
property string upgradeTitle; property string upgradeTitle;
property bool updateAvailable: root.upgradeUrl !== "" && !root.isShowingMyItems; property bool updateAvailable: root.upgradeUrl !== "" && !root.isShowingMyItems;
property bool isShowingMyItems; property bool isShowingMyItems;
property bool valid;
property string originalStatusText; property string originalStatusText;
property string originalStatusColor; property string originalStatusColor;
@ -239,6 +240,7 @@ Item {
width: 62; width: 62;
onLoaded: { onLoaded: {
item.enabled = root.valid;
item.buttonGlyphText = hifi.glyphs.gift; item.buttonGlyphText = hifi.glyphs.gift;
item.buttonText = "Gift"; item.buttonText = "Gift";
item.buttonClicked = function() { item.buttonClicked = function() {
@ -463,7 +465,7 @@ Item {
Item { Item {
id: statusContainer; id: statusContainer;
visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.numberSold > -1; visible: root.purchaseStatus === "pending" || !root.valid || root.numberSold > -1;
anchors.left: itemName.left; anchors.left: itemName.left;
anchors.right: itemName.right; anchors.right: itemName.right;
anchors.top: itemName.bottom; anchors.top: itemName.bottom;
@ -480,7 +482,7 @@ Item {
text: { text: {
if (root.purchaseStatus === "pending") { if (root.purchaseStatus === "pending") {
"PENDING..." "PENDING..."
} else if (root.purchaseStatus === "invalidated") { } else if (!root.valid) {
"INVALIDATED" "INVALIDATED"
} else if (root.numberSold > -1) { } else if (root.numberSold > -1) {
("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun)) ("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun))
@ -492,7 +494,7 @@ Item {
color: { color: {
if (root.purchaseStatus === "pending") { if (root.purchaseStatus === "pending") {
hifi.colors.blueAccent hifi.colors.blueAccent
} else if (root.purchaseStatus === "invalidated") { } else if (!root.valid) {
hifi.colors.redAccent hifi.colors.redAccent
} else { } else {
hifi.colors.baseGray hifi.colors.baseGray
@ -506,7 +508,7 @@ Item {
text: { text: {
if (root.purchaseStatus === "pending") { if (root.purchaseStatus === "pending") {
hifi.glyphs.question hifi.glyphs.question
} else if (root.purchaseStatus === "invalidated") { } else if (!root.valid) {
hifi.glyphs.question hifi.glyphs.question
} else { } else {
"" ""
@ -523,7 +525,7 @@ Item {
color: { color: {
if (root.purchaseStatus === "pending") { if (root.purchaseStatus === "pending") {
hifi.colors.blueAccent hifi.colors.blueAccent
} else if (root.purchaseStatus === "invalidated") { } else if (!root.valid) {
hifi.colors.redAccent hifi.colors.redAccent
} else { } else {
hifi.colors.baseGray hifi.colors.baseGray
@ -538,7 +540,7 @@ Item {
onClicked: { onClicked: {
if (root.purchaseStatus === "pending") { if (root.purchaseStatus === "pending") {
sendToPurchases({method: 'showPendingLightbox'}); sendToPurchases({method: 'showPendingLightbox'});
} else if (root.purchaseStatus === "invalidated") { } else if (!root.valid) {
sendToPurchases({method: 'showInvalidatedLightbox'}); sendToPurchases({method: 'showInvalidatedLightbox'});
} }
} }
@ -546,7 +548,7 @@ Item {
if (root.purchaseStatus === "pending") { if (root.purchaseStatus === "pending") {
statusText.color = hifi.colors.blueHighlight; statusText.color = hifi.colors.blueHighlight;
statusIcon.color = hifi.colors.blueHighlight; statusIcon.color = hifi.colors.blueHighlight;
} else if (root.purchaseStatus === "invalidated") { } else if (!root.valid) {
statusText.color = hifi.colors.redAccent; statusText.color = hifi.colors.redAccent;
statusIcon.color = hifi.colors.redAccent; statusIcon.color = hifi.colors.redAccent;
} }
@ -555,7 +557,7 @@ Item {
if (root.purchaseStatus === "pending") { if (root.purchaseStatus === "pending") {
statusText.color = hifi.colors.blueAccent; statusText.color = hifi.colors.blueAccent;
statusIcon.color = hifi.colors.blueAccent; statusIcon.color = hifi.colors.blueAccent;
} else if (root.purchaseStatus === "invalidated") { } else if (!root.valid) {
statusText.color = hifi.colors.redHighlight; statusText.color = hifi.colors.redHighlight;
statusIcon.color = hifi.colors.redHighlight; statusIcon.color = hifi.colors.redHighlight;
} }
@ -645,8 +647,8 @@ Item {
width: 160; width: 160;
height: 40; height: 40;
enabled: root.hasPermissionToRezThis && enabled: root.hasPermissionToRezThis &&
root.purchaseStatus !== "invalidated" && MyAvatar.skeletonModelURL !== root.itemHref &&
MyAvatar.skeletonModelURL !== root.itemHref; root.valid;
onHoveredChanged: { onHoveredChanged: {
if (hovered) { if (hovered) {

View file

@ -616,6 +616,7 @@ Rectangle {
upgradeTitle: model.upgrade_title; upgradeTitle: model.upgrade_title;
itemType: model.itemType; itemType: model.itemType;
isShowingMyItems: root.isShowingMyItems; isShowingMyItems: root.isShowingMyItems;
valid: model.valid;
anchors.topMargin: 10; anchors.topMargin: 10;
anchors.bottomMargin: 10; anchors.bottomMargin: 10;
@ -995,10 +996,6 @@ Rectangle {
for (var i = 0; i < purchasesModel.count; i++) { for (var i = 0; i < purchasesModel.count; i++) {
if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
if (!purchasesModel.get(i).valid) {
continue;
}
if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) { if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) {
tempPurchasesModel.insert(0, purchasesModel.get(i)); tempPurchasesModel.insert(0, purchasesModel.get(i));
} else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") || } else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") ||
@ -1055,10 +1052,6 @@ Rectangle {
var currentId; var currentId;
for (var i = 0; i < tempPurchasesModel.count; i++) { for (var i = 0; i < tempPurchasesModel.count; i++) {
currentId = tempPurchasesModel.get(i).id; currentId = tempPurchasesModel.get(i).id;
if (!purchasesModel.get(i).valid) {
continue;
}
filteredPurchasesModel.append(tempPurchasesModel.get(i)); filteredPurchasesModel.append(tempPurchasesModel.get(i));
filteredPurchasesModel.setProperty(i, 'cardBackVisible', false); filteredPurchasesModel.setProperty(i, 'cardBackVisible', false);
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1)); filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));

View file

@ -731,7 +731,9 @@ void EntityMotionState::measureBodyAcceleration() {
// hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt // hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt
glm::vec3 velocity = getBodyLinearVelocityGTSigma(); glm::vec3 velocity = getBodyLinearVelocityGTSigma();
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; const float MIN_DAMPING_FACTOR = 0.01f;
float invDampingAttenuationFactor = 1.0f / glm::max(powf(1.0f - _body->getLinearDamping(), dt), MIN_DAMPING_FACTOR);
_measuredAcceleration = (velocity * invDampingAttenuationFactor - _lastVelocity) * invDt;
_lastVelocity = velocity; _lastVelocity = velocity;
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) { if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) {
// we fall in here when _lastMeasureStep is old: the body has just become active // we fall in here when _lastMeasureStep is old: the body has just become active

View file

@ -149,9 +149,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con
batch.setStateScissorRect(viewport); batch.setStateScissorRect(viewport);
batch.setFramebuffer(fbo); batch.setFramebuffer(fbo);
batch.clearFramebuffer( batch.clearDepthFramebuffer(1.0, false);
gpu::Framebuffer::BUFFER_COLOR0 | gpu::Framebuffer::BUFFER_DEPTH,
vec4(vec3(1.0, 1.0, 1.0), 0.0), 1.0, 0, true);
glm::mat4 projMat; glm::mat4 projMat;
Transform viewMat; Transform viewMat;
@ -232,12 +230,11 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
const auto queryResolution = setupOutput.getN<RenderShadowSetup::Outputs>(2); const auto queryResolution = setupOutput.getN<RenderShadowSetup::Outputs>(2);
// Fetch and cull the items from the scene // Fetch and cull the items from the scene
// Enable models to not cast shadows (otherwise, models will always cast shadows) static const auto shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask);
static const auto shadowCasterFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask).withShadowCaster();
const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterFilter, queryResolution).asVarying(); const auto fetchInput = FetchSpatialTree::Inputs(shadowCasterReceiverFilter, queryResolution).asVarying();
const auto shadowSelection = task.addJob<FetchSpatialTree>("FetchShadowTree", fetchInput); const auto shadowSelection = task.addJob<FetchSpatialTree>("FetchShadowTree", fetchInput);
const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterFilter).asVarying(); const auto selectionInputs = FetchSpatialSelection::Inputs(shadowSelection, shadowCasterReceiverFilter).asVarying();
const auto shadowItems = task.addJob<FetchSpatialSelection>("FetchShadowSelection", selectionInputs); const auto shadowItems = task.addJob<FetchSpatialSelection>("FetchShadowSelection", selectionInputs);
// Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not // Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not
@ -261,21 +258,22 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende
char jobName[64]; char jobName[64];
sprintf(jobName, "ShadowCascadeSetup%d", i); sprintf(jobName, "ShadowCascadeSetup%d", i);
const auto cascadeSetupOutput = task.addJob<RenderShadowCascadeSetup>(jobName, i, _cullFunctor, tagBits, tagMask); const auto cascadeSetupOutput = task.addJob<RenderShadowCascadeSetup>(jobName, i, _cullFunctor, tagBits, tagMask);
const auto shadowFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(0); const auto shadowRenderFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(0);
const auto shadowBoundsFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(1);
auto antiFrustum = render::Varying(ViewFrustumPointer()); auto antiFrustum = render::Varying(ViewFrustumPointer());
cascadeFrustums[i] = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(1); cascadeFrustums[i] = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(2);
if (i > 1) { if (i > 1) {
antiFrustum = cascadeFrustums[i - 2]; antiFrustum = cascadeFrustums[i - 2];
} }
// CPU jobs: finer grained culling // CPU jobs: finer grained culling
const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowFilter, antiFrustum).asVarying(); const auto cullInputs = CullShapeBounds::Inputs(sortedShapes, shadowRenderFilter, shadowBoundsFilter, antiFrustum).asVarying();
const auto culledShadowItemsAndBounds = task.addJob<CullShapeBounds>("CullShadowCascade", cullInputs, shadowCullFunctor, RenderDetails::SHADOW); const auto culledShadowItemsAndBounds = task.addJob<CullShapeBounds>("CullShadowCascade", cullInputs, shadowCullFunctor, RenderDetails::SHADOW);
// GPU jobs: Render to shadow map // GPU jobs: Render to shadow map
sprintf(jobName, "RenderShadowMap%d", i); sprintf(jobName, "RenderShadowMap%d", i);
task.addJob<RenderShadowMap>(jobName, culledShadowItemsAndBounds, shapePlumber, i); task.addJob<RenderShadowMap>(jobName, culledShadowItemsAndBounds, shapePlumber, i);
task.addJob<RenderShadowCascadeTeardown>("ShadowCascadeTeardown", shadowFilter); task.addJob<RenderShadowCascadeTeardown>("ShadowCascadeTeardown", shadowRenderFilter);
} }
task.addJob<RenderShadowTeardown>("ShadowTeardown", setupOutput); task.addJob<RenderShadowTeardown>("ShadowTeardown", setupOutput);
@ -406,7 +404,11 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon
const auto globalShadow = lightStage->getCurrentKeyShadow(); const auto globalShadow = lightStage->getCurrentKeyShadow();
if (globalShadow && _cascadeIndex<globalShadow->getCascadeCount()) { if (globalShadow && _cascadeIndex<globalShadow->getCascadeCount()) {
output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask).withShadowCaster(); auto baseFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask);
// Second item filter is to filter items to keep in shadow frustum computation (here we need to keep shadow receivers)
output.edit1() = baseFilter;
// First item filter is to filter items to render in shadow map (so only keep casters)
output.edit0() = baseFilter.withShadowCaster();
// Set the keylight render args // Set the keylight render args
auto& cascade = globalShadow->getCascade(_cascadeIndex); auto& cascade = globalShadow->getCascade(_cascadeIndex);
@ -419,10 +421,11 @@ void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderCon
texelSize *= minTexelCount; texelSize *= minTexelCount;
_cullFunctor._minSquareSize = texelSize * texelSize; _cullFunctor._minSquareSize = texelSize * texelSize;
output.edit1() = cascadeFrustum; output.edit2() = cascadeFrustum;
} else { } else {
output.edit0() = ItemFilter::Builder::nothing(); output.edit0() = ItemFilter::Builder::nothing();
output.edit1() = ViewFrustumPointer(); output.edit1() = ItemFilter::Builder::nothing();
output.edit2() = ViewFrustumPointer();
} }
} }

View file

@ -118,7 +118,7 @@ private:
class RenderShadowCascadeSetup { class RenderShadowCascadeSetup {
public: public:
using Outputs = render::VaryingSet2<render::ItemFilter, ViewFrustumPointer>; using Outputs = render::VaryingSet3<render::ItemFilter, render::ItemFilter, ViewFrustumPointer>;
using JobModel = render::Job::ModelO<RenderShadowCascadeSetup, Outputs>; using JobModel = render::Job::ModelO<RenderShadowCascadeSetup, Outputs>;
RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) : RenderShadowCascadeSetup(unsigned int cascadeIndex, RenderShadowTask::CullFunctor& cullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00) :

View file

@ -368,17 +368,19 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input
RenderArgs* args = renderContext->args; RenderArgs* args = renderContext->args;
const auto& inShapes = inputs.get0(); const auto& inShapes = inputs.get0();
const auto& filter = inputs.get1(); const auto& cullFilter = inputs.get1();
const auto& antiFrustum = inputs.get2(); const auto& boundsFilter = inputs.get2();
const auto& antiFrustum = inputs.get3();
auto& outShapes = outputs.edit0(); auto& outShapes = outputs.edit0();
auto& outBounds = outputs.edit1(); auto& outBounds = outputs.edit1();
outShapes.clear(); outShapes.clear();
outBounds = AABox(); outBounds = AABox();
if (!filter.selectsNothing()) { if (!cullFilter.selectsNothing() || !boundsFilter.selectsNothing()) {
auto& details = args->_details.edit(_detailType); auto& details = args->_details.edit(_detailType);
Test test(_cullFunctor, args, details, antiFrustum); Test test(_cullFunctor, args, details, antiFrustum);
auto scene = args->_scene;
for (auto& inItems : inShapes) { for (auto& inItems : inShapes) {
auto key = inItems.first; auto key = inItems.first;
@ -393,16 +395,26 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input
if (antiFrustum == nullptr) { if (antiFrustum == nullptr) {
for (auto& item : inItems.second) { for (auto& item : inItems.second) {
if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) { if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound)) {
outItems->second.emplace_back(item); const auto shapeKey = scene->getItem(item.id).getKey();
outBounds += item.bound; if (cullFilter.test(shapeKey)) {
outItems->second.emplace_back(item);
}
if (boundsFilter.test(shapeKey)) {
outBounds += item.bound;
}
} }
} }
} else { } else {
for (auto& item : inItems.second) { for (auto& item : inItems.second) {
if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) { if (test.solidAngleTest(item.bound) && test.frustumTest(item.bound) && test.antiFrustumTest(item.bound)) {
outItems->second.emplace_back(item); const auto shapeKey = scene->getItem(item.id).getKey();
outBounds += item.bound; if (cullFilter.test(shapeKey)) {
} outItems->second.emplace_back(item);
}
if (boundsFilter.test(shapeKey)) {
outBounds += item.bound;
}
}
} }
} }
details._rendered += (int)outItems->second.size(); details._rendered += (int)outItems->second.size();

View file

@ -110,7 +110,7 @@ namespace render {
class CullShapeBounds { class CullShapeBounds {
public: public:
using Inputs = render::VaryingSet3<ShapeBounds, ItemFilter, ViewFrustumPointer>; using Inputs = render::VaryingSet4<ShapeBounds, ItemFilter, ItemFilter, ViewFrustumPointer>;
using Outputs = render::VaryingSet2<ShapeBounds, AABox>; using Outputs = render::VaryingSet2<ShapeBounds, AABox>;
using JobModel = Job::ModelIO<CullShapeBounds, Inputs, Outputs>; using JobModel = Job::ModelIO<CullShapeBounds, Inputs, Outputs>;

View file

@ -251,6 +251,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See
}); });
} }
break; break;
case 'refresh': // old name for refreshNearby
case 'refreshNearby': case 'refreshNearby':
data = {}; data = {};
ExtendedOverlay.some(function (overlay) { // capture the audio data ExtendedOverlay.some(function (overlay) { // capture the audio data
@ -743,10 +744,13 @@ function receiveMessage(channel, messageString, senderID) {
var message = JSON.parse(messageString); var message = JSON.parse(messageString);
switch (message.method) { switch (message.method) {
case 'select': case 'select':
sendToQml(message); // Accepts objects, not just strings. if (!onPalScreen) {
tablet.loadQMLSource(PAL_QML_SOURCE);
Script.setTimeout(function () { sendToQml(message); }, 1000);
} else {
sendToQml(message); // Accepts objects, not just strings.
}
break; break;
default:
print('Unrecognized PAL message', messageString);
} }
} }

View file

@ -175,40 +175,39 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai
} }
void Test::startTestsEvaluation(const QString& testFolder) { void Test::startTestsEvaluation(const QString& testFolder) {
QString pathToTestResultsDirectory; // Get list of JPEG images in folder, sorted by name
if (testFolder.isNull()) { QString previousSelection = snapshotDirectory;
// Get list of JPEG images in folder, sorted by name
pathToTestResultsDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly);
} else {
pathToTestResultsDirectory = testFolder;
}
if (pathToTestResultsDirectory == QString()) { snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images",
previousSelection, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (snapshotDirectory == "") {
snapshotDirectory = previousSelection;
return; return;
} }
// Quit if test results folder could not be created // Quit if test results folder could not be created
if (!createTestResultsFolderPath(pathToTestResultsDirectory)) { if (!createTestResultsFolderPath(snapshotDirectory)) {
return; return;
} }
// Before any processing - all images are converted to PNGs, as this is the format stored on GitHub // Before any processing - all images are converted to PNGs, as this is the format stored on GitHub
QStringList sortedSnapshotFilenames = createListOfAll_imagesInDirectory("jpg", pathToTestResultsDirectory); QStringList sortedSnapshotFilenames = createListOfAll_imagesInDirectory("jpg", snapshotDirectory);
foreach(QString filename, sortedSnapshotFilenames) { foreach(QString filename, sortedSnapshotFilenames) {
QStringList stringParts = filename.split("."); QStringList stringParts = filename.split(".");
copyJPGtoPNG( copyJPGtoPNG(snapshotDirectory + "/" + stringParts[0] + ".jpg",
pathToTestResultsDirectory + "/" + stringParts[0] + ".jpg", snapshotDirectory + "/" + stringParts[0] + ".png"
pathToTestResultsDirectory + "/" + stringParts[0] + ".png"
); );
QFile::remove(pathToTestResultsDirectory + "/" + stringParts[0] + ".jpg"); QFile::remove(snapshotDirectory + "/" + stringParts[0] + ".jpg");
} }
// Create two lists. The first is the test results, the second is the expected images // Create two lists. The first is the test results, the second is the expected images
// The expected images are represented as a URL to enable download from GitHub // The expected images are represented as a URL to enable download from GitHub
// Images that are in the wrong format are ignored. // Images that are in the wrong format are ignored.
QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", pathToTestResultsDirectory); QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", snapshotDirectory);
QStringList expectedImagesURLs; QStringList expectedImagesURLs;
resultImagesFullFilenames.clear(); resultImagesFullFilenames.clear();
@ -216,7 +215,7 @@ void Test::startTestsEvaluation(const QString& testFolder) {
expectedImagesFullFilenames.clear(); expectedImagesFullFilenames.clear();
foreach(QString currentFilename, sortedTestResultsFilenames) { foreach(QString currentFilename, sortedTestResultsFilenames) {
QString fullCurrentFilename = pathToTestResultsDirectory + "/" + currentFilename; QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename;
if (isInSnapshotFilenameFormat("png", currentFilename)) { if (isInSnapshotFilenameFormat("png", currentFilename)) {
resultImagesFullFilenames << fullCurrentFilename; resultImagesFullFilenames << fullCurrentFilename;
@ -236,11 +235,11 @@ void Test::startTestsEvaluation(const QString& testFolder) {
QString expectedImageFilename = currentFilename.replace("/", "_").replace(".", "_EI."); QString expectedImageFilename = currentFilename.replace("/", "_").replace(".", "_EI.");
expectedImagesFilenames << expectedImageFilename; expectedImagesFilenames << expectedImageFilename;
expectedImagesFullFilenames << pathToTestResultsDirectory + "/" + expectedImageFilename; expectedImagesFullFilenames << snapshotDirectory + "/" + expectedImageFilename;
} }
} }
autoTester->downloadImages(expectedImagesURLs, pathToTestResultsDirectory, expectedImagesFilenames); autoTester->downloadImages(expectedImagesURLs, snapshotDirectory, expectedImagesFilenames);
} }
void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar) { void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar) {
@ -303,25 +302,39 @@ void Test::importTest(QTextStream& textStream, const QString& testPathname) {
// This script will run all text.js scripts in every applicable sub-folder // This script will run all text.js scripts in every applicable sub-folder
void Test::createRecursiveScript() { void Test::createRecursiveScript() {
// Select folder to start recursing from // Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); QString previousSelection = testDirectory;
if (topLevelDirectory == "") {
testDirectory =
QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script",
previousSelection, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return; return;
} }
createRecursiveScript(topLevelDirectory, true); createRecursiveScript(testDirectory, true);
} }
// This method creates a `testRecursive.js` script in every sub-folder. // This method creates a `testRecursive.js` script in every sub-folder.
void Test::createAllRecursiveScripts() { void Test::createAllRecursiveScripts() {
// Select folder to start recursing from // Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts", ".", QFileDialog::ShowDirsOnly); QString previousSelection = testDirectory;
if (topLevelDirectory == "") {
testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts",
previousSelection,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return; return;
} }
createRecursiveScript(topLevelDirectory, false); createRecursiveScript(testDirectory, false);
QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); QDirIterator it(testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) { while (it.hasNext()) {
QString directory = it.next(); QString directory = it.next();
@ -427,29 +440,42 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact
void Test::createTest() { void Test::createTest() {
// Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on // Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on
// Any existing expected result images will be deleted // Any existing expected result images will be deleted
QString imageSourceDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); QString previousSelection = snapshotDirectory;
if (imageSourceDirectory == "") {
snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images",
previousSelection,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (snapshotDirectory == "") {
snapshotDirectory = previousSelection;
return; return;
} }
QString imageDestinationDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder to save the test images", ".", QFileDialog::ShowDirsOnly); previousSelection = testDirectory;
if (imageDestinationDirectory == "") {
QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder to save the test images",
previousSelection, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return; return;
} }
QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("jpg", imageSourceDirectory); QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("jpg", snapshotDirectory);
int i = 1; int i = 1;
const int maxImages = pow(10, NUM_DIGITS); const int maxImages = pow(10, NUM_DIGITS);
foreach (QString currentFilename, sortedImageFilenames) { foreach (QString currentFilename, sortedImageFilenames) {
QString fullCurrentFilename = imageSourceDirectory + "/" + currentFilename; QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename;
if (isInSnapshotFilenameFormat("jpg", currentFilename)) { if (isInSnapshotFilenameFormat("jpg", currentFilename)) {
if (i >= maxImages) { if (i >= maxImages) {
QMessageBox::critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported"); QMessageBox::critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported");
exit(-1); exit(-1);
} }
QString newFilename = "ExpectedImage_" + QString::number(i - 1).rightJustified(5, '0') + ".png"; QString newFilename = "ExpectedImage_" + QString::number(i - 1).rightJustified(5, '0') + ".png";
QString fullNewFileName = imageDestinationDirectory + "/" + newFilename; QString fullNewFileName = testDirectory + "/" + newFilename;
try { try {
copyJPGtoPNG(fullCurrentFilename, fullNewFileName); copyJPGtoPNG(fullCurrentFilename, fullNewFileName);
@ -489,31 +515,6 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString + "\\," + ws + ownPath + "\\," + ws + functionParameter + ws + "{" + ".*"); QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString + "\\," + ws + ownPath + "\\," + ws + functionParameter + ws + "{" + ".*");
QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle); QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle);
// Assert platform checks that test is running on the correct OS
const QString functionAssertPlatform(ws + "autoTester" + ws + "\\." + ws + "assertPlatform");
const QString regexAssertPlatform(ws + functionAssertPlatform + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertPlatform = QRegularExpression(regexAssertPlatform);
// Assert display checks that test is running on the correct display
const QString functionAssertDisplay(ws + "autoTester" + ws + "\\." + ws + "assertDisplay");
const QString regexAssertDisplay(ws + functionAssertDisplay + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertDisplay = QRegularExpression(regexAssertDisplay);
// Assert CPU checks that test is running on the correct type of CPU
const QString functionAssertCPU(ws + "autoTester" + ws + "\\." + ws + "assertCPU");
const QString regexAssertCPU(ws + functionAssertCPU + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertCPU = QRegularExpression(regexAssertCPU);
// Assert GPU checks that test is running on the correct type of GPU
const QString functionAssertGPU(ws + "autoTester" + ws + "\\." + ws + "assertGPU");
const QString regexAssertGPU(ws + functionAssertGPU + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertGPU = QRegularExpression(regexAssertGPU);
// Assert the correct amount of memory
const QString functionAssertPhysicalMemoryGB(ws + "autoTester" + ws + "\\." + ws + "assertPhysicalMemoryGB");
const QString regexAssertPhysicalMemoryGB(ws + functionAssertPhysicalMemoryGB + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineAssertPhysicalMemoryGB = QRegularExpression(regexAssertPhysicalMemoryGB);
// Each step is either of the following forms: // Each step is either of the following forms:
// autoTester.addStepSnapshot("Take snapshot"... // autoTester.addStepSnapshot("Take snapshot"...
@ -523,7 +524,7 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
const QRegularExpression lineStepSnapshot = QRegularExpression(regexStepSnapshot); const QRegularExpression lineStepSnapshot = QRegularExpression(regexStepSnapshot);
const QString functionAddStepName(ws + "autoTester" + ws + "\\." + ws + "addStep"); const QString functionAddStepName(ws + "autoTester" + ws + "\\." + ws + "addStep");
const QString regexStep(ws + functionAddStepName + ws + "\\(" + ws + quotedString + ws + "\\)" + ".*"); const QString regexStep(ws + functionAddStepName + ws + "\\(" + ws + quotedString + ".*");
const QRegularExpression lineStep = QRegularExpression(regexStep); const QRegularExpression lineStep = QRegularExpression(regexStep);
while (!line.isNull()) { while (!line.isNull()) {
@ -531,7 +532,6 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
if (lineContainingTitle.match(line).hasMatch()) { if (lineContainingTitle.match(line).hasMatch()) {
QStringList tokens = line.split('"'); QStringList tokens = line.split('"');
relevantTextFromTest.title = tokens[1]; relevantTextFromTest.title = tokens[1];
} else if (lineStepSnapshot.match(line).hasMatch()) { } else if (lineStepSnapshot.match(line).hasMatch()) {
QStringList tokens = line.split('"'); QStringList tokens = line.split('"');
QString nameOfStep = tokens[1]; QString nameOfStep = tokens[1];
@ -561,29 +561,43 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
// The folder selected must contain a script named "test.js", the file produced is named "test.md" // The folder selected must contain a script named "test.js", the file produced is named "test.md"
void Test::createMDFile() { void Test::createMDFile() {
// Folder selection // Folder selection
QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", ".", QFileDialog::ShowDirsOnly); QString previousSelection = testDirectory;
testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", previousSelection,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") { if (testDirectory == "") {
testDirectory = previousSelection;
return; return;
} }
createMDFile(testDirectory); createMDFile(testDirectory);
QMessageBox::information(0, "Success", "MD file has been created");
} }
void Test::createAllMDFiles() { void Test::createAllMDFiles() {
// Select folder to start recursing from // Select folder to start recursing from
QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", ".", QFileDialog::ShowDirsOnly); QString previousSelection = testDirectory;
if (topLevelDirectory == "") {
testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files",
previousSelection, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return; return;
} }
// First test if top-level folder has a test.js file // First test if top-level folder has a test.js file
const QString testPathname{ topLevelDirectory + "/" + TEST_FILENAME }; const QString testPathname { testDirectory + "/" + TEST_FILENAME };
QFileInfo fileInfo(testPathname); QFileInfo fileInfo(testPathname);
if (fileInfo.exists()) { if (fileInfo.exists()) {
createMDFile(topLevelDirectory); createMDFile(testDirectory);
} }
QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); QDirIterator it(testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) { while (it.hasNext()) {
QString directory = it.next(); QString directory = it.next();
@ -638,31 +652,33 @@ void Test::createMDFile(const QString& testDirectory) {
stream << "## Steps\n"; stream << "## Steps\n";
stream << "Press space bar to advance step by step\n\n"; stream << "Press space bar to advance step by step\n\n";
// Note that snapshots of step n are taken in step n+1
// (this implies that if the LAST step requests a snapshot then this will not work - caveat emptor)
int snapShotIndex { 0 }; int snapShotIndex { 0 };
for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) { for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) {
stream << "### Step " << QString::number(i + 1) << "\n"; stream << "### Step " << QString::number(i + 1) << "\n";
stream << "- " << testScriptLines.stepList[i]->text << "\n"; stream << "- " << testScriptLines.stepList[i]->text << "\n";
if ((i + 1 < testScriptLines.stepList.size()) && testScriptLines.stepList[i + 1]->takeSnapshot) { if ((i + 1 < testScriptLines.stepList.size()) && testScriptLines.stepList[i]->takeSnapshot) {
stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n"; stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n";
++snapShotIndex; ++snapShotIndex;
} }
} }
mdFile.close(); mdFile.close();
QMessageBox::information(0, "Success", "Test MD file " + mdFilename + " has been created");
} }
void Test::createTestsOutline() { void Test::createTestsOutline() {
QString testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", ".", QFileDialog::ShowDirsOnly); QString previousSelection = testDirectory;
if (testsRootDirectory == "") {
testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", previousSelection,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
return; return;
} }
const QString testsOutlineFilename { "testsOutline.md" }; const QString testsOutlineFilename { "testsOutline.md" };
QString mdFilename(testsRootDirectory + "/" + testsOutlineFilename); QString mdFilename(testDirectory + "/" + testsOutlineFilename);
QFile mdFile(mdFilename); QFile mdFile(mdFilename);
if (!mdFile.open(QIODevice::WriteOnly)) { if (!mdFile.open(QIODevice::WriteOnly)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
@ -676,10 +692,10 @@ void Test::createTestsOutline() {
stream << "Directories with an appended (*) have an automatic test\n\n"; stream << "Directories with an appended (*) have an automatic test\n\n";
// We need to know our current depth, as this isn't given by QDirIterator // We need to know our current depth, as this isn't given by QDirIterator
int rootDepth { testsRootDirectory.count('/') }; 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 // 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(testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories); QDirIterator it(testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) { while (it.hasNext()) {
QString directory = it.next(); QString directory = it.next();

View file

@ -89,6 +89,12 @@ private:
const int NUM_DIGITS { 5 }; const int NUM_DIGITS { 5 };
const QString EXPECTED_IMAGE_PREFIX { "ExpectedImage_" }; const QString EXPECTED_IMAGE_PREFIX { "ExpectedImage_" };
// We have two directories to work with.
// The first is the directory containing the test we are working with
// The second contains the snapshots taken for test runs that need to be evaluated
QString testDirectory;
QString snapshotDirectory;
QStringList expectedImagesFilenames; QStringList expectedImagesFilenames;
QStringList expectedImagesFullFilenames; QStringList expectedImagesFullFilenames;
QStringList resultImagesFullFilenames; QStringList resultImagesFullFilenames;