Merge remote-tracking branch 'upstream/master' into bloom

This commit is contained in:
SamGondelman 2018-08-08 11:35:49 -07:00
commit a0c3c09b05
72 changed files with 2659 additions and 678 deletions

View file

@ -368,7 +368,6 @@ void Agent::executeScript() {
// give scripts access to the Users object
_scriptEngine->registerGlobalObject("Users", DependencyManager::get<UsersScriptingInterface>().data());
auto player = DependencyManager::get<recording::Deck>();
connect(player.data(), &recording::Deck::playbackStateChanged, [=] {
if (player->isPlaying()) {

View file

@ -11,6 +11,8 @@
#include "AssignmentClientApp.h"
#include <iostream>
#include <QtCore/QCommandLineParser>
#include <QtCore/QDir>
#include <QtCore/QStandardPaths>
@ -42,9 +44,8 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
// parse command-line
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity Assignment Client");
parser.addHelpOption();
const QCommandLineOption helpOption = parser.addHelpOption();
const QCommandLineOption versionOption = parser.addVersionOption();
QString typeDescription = "run single assignment client of given type\n# | Type\n============================";
for (Assignment::Type type = Assignment::FirstType;
@ -97,11 +98,16 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
parser.addOption(parentPIDOption);
if (!parser.parse(QCoreApplication::arguments())) {
qCritical() << parser.errorText() << endl;
std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam
parser.showHelp();
Q_UNREACHABLE();
}
if (parser.isSet(versionOption)) {
parser.showVersion();
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
parser.showHelp();
Q_UNREACHABLE();

View file

@ -13,6 +13,7 @@
#include <memory>
#include <random>
#include <iostream>
#include <QDir>
#include <QJsonDocument>
@ -69,6 +70,14 @@ const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com";
const QString ICE_SERVER_DEFAULT_HOSTNAME = "dev-ice.highfidelity.com";
#endif
QString DomainServer::_iceServerAddr { ICE_SERVER_DEFAULT_HOSTNAME };
int DomainServer::_iceServerPort { ICE_SERVER_DEFAULT_PORT };
bool DomainServer::_overrideDomainID { false };
QUuid DomainServer::_overridingDomainID;
bool DomainServer::_getTempName { false };
QString DomainServer::_userConfigFilename;
int DomainServer::_parentPID { -1 };
bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
const QString& metaversePath,
const QString& requestSubobjectKey,
@ -148,24 +157,13 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_gatekeeper(this),
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
_allAssignments(),
_unfulfilledAssignments(),
_isUsingDTLS(false),
_oauthProviderURL(),
_oauthClientID(),
_hostname(),
_ephemeralACScripts(),
_webAuthenticationStateSet(),
_cookieSessionHash(),
_automaticNetworkingSetting(),
_settingsManager(),
_iceServerAddr(ICE_SERVER_DEFAULT_HOSTNAME),
_iceServerPort(ICE_SERVER_DEFAULT_PORT)
_httpManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this)
{
PathUtils::removeTemporaryApplicationDirs();
if (_parentPID != -1) {
watchParentProcess(_parentPID);
}
parseCommandLine();
PathUtils::removeTemporaryApplicationDirs();
DependencyManager::set<tracing::Tracer>();
DependencyManager::set<StatTracker>();
@ -185,9 +183,16 @@ DomainServer::DomainServer(int argc, char* argv[]) :
// (need this since domain-server can restart itself and maintain static variables)
DependencyManager::set<AccountManager>();
auto args = arguments();
_settingsManager.setupConfigMap(args);
// load the user config
QString userConfigFilename;
if (!_userConfigFilename.isEmpty()) {
userConfigFilename = _userConfigFilename;
} else {
// we weren't passed a user config path
static const QString USER_CONFIG_FILE_NAME = "config.json";
userConfigFilename = PathUtils::getAppDataFilePath(USER_CONFIG_FILE_NAME);
}
_settingsManager.setupConfigMap(userConfigFilename);
// setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us
#ifdef _WIN32
@ -246,8 +251,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
}
// check for the temporary name parameter
const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name";
if (args.contains(GET_TEMPORARY_NAME_SWITCH)) {
if (_getTempName) {
getTemporaryName();
}
@ -316,28 +320,45 @@ DomainServer::DomainServer(int argc, char* argv[]) :
connect(_contentManager.get(), &DomainContentBackupManager::recoveryCompleted, this, &DomainServer::restart);
}
void DomainServer::parseCommandLine() {
void DomainServer::parseCommandLine(int argc, char* argv[]) {
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity Domain Server");
parser.addHelpOption();
const QCommandLineOption versionOption = parser.addVersionOption();
const QCommandLineOption helpOption = parser.addHelpOption();
const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT");
parser.addOption(iceServerAddressOption);
const QCommandLineOption domainIDOption("d", "domain-server uuid");
const QCommandLineOption domainIDOption("d", "domain-server uuid", "uuid");
parser.addOption(domainIDOption);
const QCommandLineOption getTempNameOption("get-temp-name", "Request a temporary domain-name");
parser.addOption(getTempNameOption);
const QCommandLineOption masterConfigOption("master-config", "Deprecated config-file option");
parser.addOption(masterConfigOption);
const QCommandLineOption userConfigOption("user-config", "Pass user config file pass", "path");
parser.addOption(userConfigOption);
const QCommandLineOption parentPIDOption(PARENT_PID_OPTION, "PID of the parent process", "parent-pid");
parser.addOption(parentPIDOption);
if (!parser.parse(QCoreApplication::arguments())) {
qWarning() << parser.errorText() << endl;
QStringList arguments;
for (int i = 0; i < argc; ++i) {
arguments << argv[i];
}
if (!parser.parse(arguments)) {
std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam
QCoreApplication mockApp(argc, argv); // required for call to showHelp()
parser.showHelp();
Q_UNREACHABLE();
}
if (parser.isSet(versionOption)) {
parser.showVersion();
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
QCoreApplication mockApp(argc, argv); // required for call to showHelp()
parser.showHelp();
Q_UNREACHABLE();
}
@ -354,7 +375,7 @@ void DomainServer::parseCommandLine() {
if (_iceServerAddr.isEmpty()) {
qWarning() << "Could not parse an IP address and port combination from" << hostnamePortString;
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
::exit(0);
}
}
@ -364,14 +385,21 @@ void DomainServer::parseCommandLine() {
qDebug() << "domain-server ID is" << _overridingDomainID;
}
if (parser.isSet(getTempNameOption)) {
_getTempName = true;
}
if (parser.isSet(userConfigOption)) {
_userConfigFilename = parser.value(userConfigOption);
}
if (parser.isSet(parentPIDOption)) {
bool ok = false;
int parentPID = parser.value(parentPIDOption).toInt(&ok);
if (ok) {
qDebug() << "Parent process PID is" << parentPID;
watchParentProcess(parentPID);
_parentPID = parentPID;
qDebug() << "Parent process PID is" << _parentPID;
}
}
}

View file

@ -59,6 +59,8 @@ public:
DomainServer(int argc, char* argv[]);
~DomainServer();
static void parseCommandLine(int argc, char* argv[]);
enum DomainType {
NonMetaverse,
MetaverseDomain,
@ -138,7 +140,6 @@ signals:
private:
QUuid getID();
void parseCommandLine();
QString getContentBackupDir();
QString getEntitiesDirPath();
@ -228,7 +229,7 @@ private:
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
TransactionHash _pendingAssignmentCredits;
bool _isUsingDTLS;
bool _isUsingDTLS { false };
QUrl _oauthProviderURL;
QString _oauthClientID;
@ -265,10 +266,13 @@ private:
friend class DomainGatekeeper;
friend class DomainMetadata;
QString _iceServerAddr;
int _iceServerPort;
bool _overrideDomainID { false }; // should we override the domain-id from settings?
QUuid _overridingDomainID { QUuid() }; // what should we override it with?
static QString _iceServerAddr;
static int _iceServerPort;
static bool _overrideDomainID; // should we override the domain-id from settings?
static QUuid _overridingDomainID; // what should we override it with?
static bool _getTempName;
static QString _userConfigFilename;
static int _parentPID;
bool _sendICEServerAddressToMetaverseAPIInProgress { false };
bool _sendICEServerAddressToMetaverseAPIRedo { false };

View file

@ -191,13 +191,12 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
nodeList->sendPacketList(std::move(packetList), message->getSenderSockAddr());
}
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilename) {
// since we're called from the DomainServerSettingsManager constructor, we don't take a write lock here
// even though we change the underlying config map
_argumentList = argumentList;
_configMap.loadConfig(_argumentList);
_configMap.setUserConfigFilename(userConfigFilename);
_configMap.loadConfig();
static const auto VERSION_SETTINGS_KEYPATH = "version";
QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH);
@ -1736,7 +1735,7 @@ void DomainServerSettingsManager::persistToFile() {
// failed to write, reload whatever the current config state is
// with a write lock since we're about to overwrite the config map
QWriteLocker locker(&_settingsLock);
_configMap.loadConfig(_argumentList);
_configMap.loadConfig();
}
}

View file

@ -49,7 +49,7 @@ public:
DomainServerSettingsManager();
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
void setupConfigMap(const QStringList& argumentList);
void setupConfigMap(const QString& userConfigFilename);
// each of the three methods in this group takes a read lock of _settingsLock
// and cannot be called when the a write lock is held by the same thread
@ -144,8 +144,6 @@ private slots:
void processUsernameFromIDRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
private:
QStringList _argumentList;
QJsonArray filteredDescriptionArray(bool isContentSettings);
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
const QJsonObject& settingDescription);

View file

@ -24,6 +24,8 @@
int main(int argc, char* argv[]) {
setupHifiApplication(BuildInfo::DOMAIN_SERVER_NAME);
DomainServer::parseCommandLine(argc, argv);
Setting::init();
int currentExitCode = 0;

View file

@ -42,6 +42,48 @@ extern "C" {
int main(int argc, const char* argv[]) {
setupHifiApplication(BuildInfo::INTERFACE_NAME);
QStringList arguments;
for (int i = 0; i < argc; ++i) {
arguments << argv[i];
}
QCommandLineParser parser;
parser.setApplicationDescription("High Fidelity Interface");
QCommandLineOption versionOption = parser.addVersionOption();
QCommandLineOption helpOption = parser.addHelpOption();
QCommandLineOption urlOption("url", "", "value");
QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater");
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
QCommandLineOption runServerOption("runServer", "Whether to run the server");
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache <dir>", "dir");
QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts <path>", "path");
parser.addOption(urlOption);
parser.addOption(noUpdaterOption);
parser.addOption(checkMinSpecOption);
parser.addOption(runServerOption);
parser.addOption(serverContentPathOption);
parser.addOption(overrideAppLocalDataPathOption);
parser.addOption(overrideScriptsPathOption);
parser.addOption(allowMultipleInstancesOption);
if (!parser.parse(arguments)) {
std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam
}
if (parser.isSet(versionOption)) {
parser.showVersion();
Q_UNREACHABLE();
}
if (parser.isSet(helpOption)) {
QCoreApplication mockApp(argc, const_cast<char**>(argv)); // required for call to showHelp()
parser.showHelp();
Q_UNREACHABLE();
}
// Early check for --traceFile argument
auto tracer = DependencyManager::set<tracing::Tracer>();
const char * traceFile = nullptr;
@ -95,30 +137,6 @@ int main(int argc, const char* argv[]) {
qDebug() << "Crash handler started:" << crashHandlerStarted;
}
QStringList arguments;
for (int i = 0; i < argc; ++i) {
arguments << argv[i];
}
QCommandLineParser parser;
QCommandLineOption urlOption("url", "", "value");
QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater");
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
QCommandLineOption runServerOption("runServer", "Whether to run the server");
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
QCommandLineOption overrideAppLocalDataPathOption("cache", "set test cache <dir>", "dir");
QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts <path>", "path");
parser.addOption(urlOption);
parser.addOption(noUpdaterOption);
parser.addOption(checkMinSpecOption);
parser.addOption(runServerOption);
parser.addOption(serverContentPathOption);
parser.addOption(overrideAppLocalDataPathOption);
parser.addOption(overrideScriptsPathOption);
parser.addOption(allowMultipleInstancesOption);
parser.parse(arguments);
const QString& applicationName = getInterfaceSharedMemoryName();
bool instanceMightBeRunning = true;

View file

@ -112,11 +112,14 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) {
}
batch.setModelTransform(renderTransform);
drawMaterial->setTextureTransforms(textureTransform);
// bind the material
RenderPipelines::bindMaterial(drawMaterial, batch, args->_enableTexturing);
args->_details._materialSwitches++;
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
drawMaterial->setTextureTransforms(textureTransform);
// bind the material
RenderPipelines::bindMaterial(drawMaterial, batch, args->_enableTexturing);
args->_details._materialSwitches++;
}
// Draw!
DependencyManager::get<GeometryCache>()->renderSphere(batch);

View file

@ -35,9 +35,7 @@ ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Pare
_procedural._vertexSource = gpu::Shader::getVertexShaderSource(shader::render_utils::vertex::simple);
// FIXME: Setup proper uniform slots and use correct pipelines for forward rendering
_procedural._opaquefragmentSource = gpu::Shader::getFragmentShaderSource(shader::render_utils::fragment::simple);
// FIXME: Transparent procedural entities only seem to work if they use the opaque pipelines
//_procedural._transparentfragmentSource = simple_transparent_frag::getSource();
_procedural._transparentfragmentSource = _procedural._opaquefragmentSource;
_procedural._transparentfragmentSource = gpu::Shader::getFragmentShaderSource(shader::render_utils::fragment::simple_transparent);
_procedural._opaqueState->setCullMode(gpu::State::CULL_NONE);
_procedural._opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL);
PrepareStencil::testMaskDrawShape(*_procedural._opaqueState);
@ -268,8 +266,10 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline);
}
} else {
RenderPipelines::bindMaterial(mat, batch, args->_enableTexturing);
args->_details._materialSwitches++;
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
RenderPipelines::bindMaterial(mat, batch, args->_enableTexturing);
args->_details._materialSwitches++;
}
geometryCache->renderShape(batch, geometryShape);
}

View file

@ -212,8 +212,9 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) {
_hazeStage->_currentFrame.pushHaze(_hazeIndex);
}
// Bloom only if the mode is not inherit, as the model deals with on/off
if (_bloomMode != COMPONENT_MODE_INHERIT) {
if (_bloomMode == COMPONENT_MODE_DISABLED) {
_bloomStage->_currentFrame.pushBloom(INVALID_INDEX);
} else if (_bloomMode == COMPONENT_MODE_ENABLED) {
_bloomStage->_currentFrame.pushBloom(_bloomIndex);
}
}
@ -426,8 +427,6 @@ void ZoneEntityRenderer::updateBloomFromEntity(const TypedEntityPointer& entity)
const auto& bloom = editBloom();
const uint32_t bloomMode = entity->getBloomMode();
bloom->setBloomActive(bloomMode == COMPONENT_MODE_ENABLED);
bloom->setBloomIntensity(_bloomProperties.getBloomIntensity());
bloom->setBloomThreshold(_bloomProperties.getBloomThreshold());
bloom->setBloomSize(_bloomProperties.getBloomSize());

View file

@ -149,7 +149,7 @@ void main(void) {
vec3 UP = vec3(0, 1, 0);
vec3 modelUpWorld;
<$transformModelToWorldDir(cam, obj, UP, modelUpWorld)$>
vec3 upWorld = mix(UP, normalize(modelUpWorld), particle.rotateWithEntity);
vec3 upWorld = mix(UP, normalize(modelUpWorld), float(particle.rotateWithEntity));
vec3 upEye = normalize(view3 * upWorld);
vec3 FORWARD = vec3(0, 0, -1);
vec3 particleRight = normalize(cross(FORWARD, upEye));

View file

@ -43,14 +43,6 @@ bool recommendedSparseTextures = (QThread::idealThreadCount() >= MIN_CORES_FOR_I
std::atomic<bool> Texture::_enableSparseTextures { recommendedSparseTextures };
struct ReportTextureState {
ReportTextureState() {
qCDebug(gpulogging) << "[TEXTURE TRANSFER SUPPORT]"
<< "\n\tidealThreadCount:" << QThread::idealThreadCount()
<< "\n\tRECOMMENDED enableSparseTextures:" << recommendedSparseTextures;
}
} report;
void Texture::setEnableSparseTextures(bool enabled) {
#ifdef Q_OS_WIN
qCDebug(gpulogging) << "[TEXTURE TRANSFER SUPPORT] SETTING - Enable Sparse Textures and Dynamic Texture Management:" << enabled;

View file

@ -26,15 +26,12 @@ namespace graphics {
void setBloomIntensity(const float bloomIntensity) { _bloomIntensity = bloomIntensity; }
void setBloomThreshold(const float bloomThreshold) { _bloomThreshold = bloomThreshold; }
void setBloomSize(const float bloomSize) { _bloomSize = bloomSize; }
void setBloomActive(const bool isBloomActive) { _isBloomActive = isBloomActive; }
float getBloomIntensity() { return _bloomIntensity; }
float getBloomThreshold() { return _bloomThreshold; }
float getBloomSize() { return _bloomSize; }
bool getBloomActive() { return _isBloomActive; }
private:
bool _isBloomActive { false };
float _bloomIntensity { INITIAL_BLOOM_INTENSITY };
float _bloomThreshold {INITIAL_BLOOM_THRESHOLD };
float _bloomSize { INITIAL_BLOOM_SIZE };

View file

@ -269,7 +269,14 @@ vec3 fetchLightmapMap(vec2 uv) {
<@func discardTransparent(opacity)@>
{
if (<$opacity$> < 1e-6) {
if (<$opacity$> < 1.0) {
discard;
}
}
<@endfunc@>
<@func discardInvisible(opacity)@>
{
if (<$opacity$> < 1.e-6) {
discard;
}
}

View file

@ -39,7 +39,4 @@ void main(void) {
// Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline
color = pow(color, vec3(2.2));
_fragColor = vec4(color, 0.0);
// FIXME: scribe does not yet scrub out else statements
return;
}

View file

@ -65,12 +65,13 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons
glm::ivec4 viewport{ 0, 0, bufferSize.x, bufferSize.y };
if (!bloom || !bloom->getBloomActive()) {
if (!bloom) {
outputs = _outputBuffer;
return;
}
_parameters.edit()._threshold = bloom->getBloomThreshold();
qDebug() << "boop1" << bloom->getBloomIntensity() << bloom->getBloomThreshold() << bloom->getBloomSize();
//{
// std::string blurName { "BloomBlurN" };
@ -136,10 +137,12 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In
const auto bloom = inputs.get4();
const glm::ivec4 viewport{ 0, 0, framebufferSize.x, framebufferSize.y };
if (!bloom || !bloom->getBloomActive()) {
if (!bloom) {
return;
}
qDebug() << "boop2" << bloom->getBloomIntensity() << bloom->getBloomThreshold() << bloom->getBloomSize();
const auto newIntensity = bloom->getBloomIntensity() / 3.0f;
auto& parameters = _parameters.edit();
parameters._intensities.x = newIntensity;
@ -172,6 +175,13 @@ void BloomDraw::run(const render::RenderContextPointer& renderContext, const Inp
const auto frameBuffer = inputs.get0();
const auto bloomFrameBuffer = inputs.get1();
const auto bloom = inputs.get2();
if (!bloom) {
return;
}
qDebug() << "boop3" << bloom->getBloomIntensity() << bloom->getBloomThreshold() << bloom->getBloomSize();
if (frameBuffer && bloomFrameBuffer) {
const auto framebufferSize = frameBuffer->getSize();
@ -290,7 +300,7 @@ void DebugBloom::run(const render::RenderContextPointer& renderContext, const In
BloomEffect::BloomEffect() {}
void BloomEffect::configure(const Config& config) {
std::string blurName{ "BloomBlurN" };
std::string blurName { "BloomBlurN" };
for (auto i = 0; i < BLOOM_BLUR_LEVEL_COUNT; i++) {
blurName.back() = '0' + i;
@ -312,10 +322,10 @@ void BloomEffect::build(JobModel& task, const render::Varying& inputs, render::V
const auto& frameBuffer = input[1];
// Mix all blur levels at quarter resolution
const auto applyInput = BloomApply::Inputs(bloomInputBuffer, blurFB0, blurFB1, blurFB2, input.get2()).asVarying();
const auto applyInput = BloomApply::Inputs(bloomInputBuffer, blurFB0, blurFB1, blurFB2, input[2]).asVarying();
task.addJob<BloomApply>("BloomApply", applyInput);
// And then blend result in additive manner on top of final color buffer
const auto drawInput = BloomDraw::Inputs(frameBuffer, bloomInputBuffer).asVarying();
const auto drawInput = BloomDraw::Inputs(frameBuffer, bloomInputBuffer, input[2]).asVarying();
task.addJob<BloomDraw>("BloomDraw", drawInput);
const auto debugInput = DebugBloom::Inputs(frameBuffer, blurFB0, blurFB1, blurFB2, bloomInputBuffer).asVarying();

View file

@ -73,7 +73,7 @@ private:
class BloomDraw {
public:
using Inputs = render::VaryingSet2<gpu::FramebufferPointer, gpu::FramebufferPointer>;
using Inputs = render::VaryingSet3<gpu::FramebufferPointer, gpu::FramebufferPointer, graphics::BloomPointer>;
using JobModel = render::Job::ModelI<BloomDraw, Inputs>;
BloomDraw() {}

View file

@ -24,7 +24,6 @@ void FetchBloomStage::configure(const Config& config) {
_bloom->setBloomIntensity(config.bloomIntensity);
_bloom->setBloomThreshold(config.bloomThreshold);
_bloom->setBloomSize(config.bloomSize);
_bloom->setBloomActive(config.isBloomActive);
}
BloomStage::Index BloomStage::findBloom(const BloomPointer& bloom) const {

View file

@ -85,7 +85,6 @@ class FetchBloomConfig : public render::Job::Config {
Q_PROPERTY(float bloomIntensity MEMBER bloomIntensity WRITE setBloomIntensity NOTIFY dirty);
Q_PROPERTY(float bloomThreshold MEMBER bloomThreshold WRITE setBloomThreshold NOTIFY dirty);
Q_PROPERTY(float bloomSize MEMBER bloomSize WRITE setBloomSize NOTIFY dirty);
Q_PROPERTY(bool isBloomActive MEMBER isBloomActive WRITE setBloomActive NOTIFY dirty);
public:
FetchBloomConfig() : render::Job::Config() {}
@ -94,13 +93,10 @@ public:
float bloomThreshold { graphics::Bloom::INITIAL_BLOOM_THRESHOLD };
float bloomSize { graphics::Bloom::INITIAL_BLOOM_SIZE };
bool isBloomActive { false };
public slots:
void setBloomIntensity(const float value) { bloomIntensity = value; emit dirty(); }
void setBloomThreshold(const float value) { bloomThreshold = value; emit dirty(); }
void setBloomSize(const float value) { bloomSize = value; emit dirty(); }
void setBloomActive(const bool active) { isBloomActive = active; emit dirty(); }
signals:
void dirty();

View file

@ -157,8 +157,10 @@ void MeshPartPayload::render(RenderArgs* args) {
bindMesh(batch);
// apply material properties
RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing);
args->_details._materialSwitches++;
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing);
args->_details._materialSwitches++;
}
// Draw!
{
@ -417,8 +419,10 @@ void ModelMeshPartPayload::render(RenderArgs* args) {
bindMesh(batch);
// apply material properties
RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing);
args->_details._materialSwitches++;
if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) {
RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing);
args->_details._materialSwitches++;
}
// Draw!
{

View file

@ -89,7 +89,7 @@ void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs)
lightStage->_currentFrame.pushAmbientLight(lightStage->getDefaultLight());
backgroundStage->_currentFrame.pushBackground(0);
hazeStage->_currentFrame.pushHaze(0);
bloomStage->_currentFrame.pushBloom(0);
bloomStage->_currentFrame.pushBloom(INVALID_INDEX);
}
const gpu::PipelinePointer& DebugZoneLighting::getKeyLightPipeline() {

View file

@ -40,7 +40,7 @@ void main(void) {
float opacity = getMaterialOpacity(mat) * _color.a;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
<$discardInvisible(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;

View file

@ -51,9 +51,9 @@ void main(void) {
#ifdef PROCEDURAL
#ifdef PROCEDURAL_V1
specular = getProceduralColor().rgb;
diffuse = getProceduralColor().rgb;
// Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline
//specular = pow(specular, vec3(2.2));
//diffuse = pow(diffuse, vec3(2.2));
emissiveAmount = 1.0;
#else
emissiveAmount = getProceduralColors(diffuse, specular, shininess);

View file

@ -1,93 +0,0 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// forward_simple_transparent.frag
// fragment shader
//
// Created by Andrzej Kapolka on 9/15/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include DefaultMaterials.slh@>
<@include ForwardGlobalLight.slh@>
<$declareEvalGlobalLightingAlphaBlended()$>
// the interpolated normal
layout(location=RENDER_UTILS_ATTR_NORMAL_WS) in vec3 _normalWS;
layout(location=RENDER_UTILS_ATTR_NORMAL_MS) in vec3 _normalMS;
layout(location=RENDER_UTILS_ATTR_COLOR) in vec4 _color;
layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
#define _texCoord0 _texCoord01.xy
#define _texCoord1 _texCoord01.zw
layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _positionMS;
layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES;
// For retro-compatibility
#define _normal _normalWS
#define _modelNormal _normalMS
#define _position _positionMS
#define _eyePosition _positionES
layout(location=0) out vec4 _fragColor0;
//PROCEDURAL_COMMON_BLOCK
#line 1001
//PROCEDURAL_BLOCK
#line 2030
void main(void) {
vec3 normal = normalize(_normalWS.xyz);
vec3 diffuse = _color.rgb;
vec3 specular = DEFAULT_SPECULAR;
float shininess = DEFAULT_SHININESS;
float emissiveAmount = 0.0;
#ifdef PROCEDURAL
#ifdef PROCEDURAL_V1
specular = getProceduralColor().rgb;
// Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline
//specular = pow(specular, vec3(2.2));
emissiveAmount = 1.0;
#else
emissiveAmount = getProceduralColors(diffuse, specular, shininess);
#endif
#endif
TransformCamera cam = getTransformCamera();
vec3 fragPosition = _positionES.xyz;
if (emissiveAmount > 0.0) {
_fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze(
cam._viewInverse,
1.0,
DEFAULT_OCCLUSION,
fragPosition,
normal,
specular,
DEFAULT_FRESNEL,
DEFAULT_METALLIC,
DEFAULT_EMISSIVE,
DEFAULT_ROUGHNESS, _color.a),
_color.a);
} else {
_fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze(
cam._viewInverse,
1.0,
DEFAULT_OCCLUSION,
fragPosition,
normal,
diffuse,
DEFAULT_FRESNEL,
DEFAULT_METALLIC,
DEFAULT_EMISSIVE,
DEFAULT_ROUGHNESS, _color.a),
_color.a);
}
}

View file

@ -44,7 +44,7 @@ void main(void) {
float opacity = getMaterialOpacity(mat) * _color.a;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
<$discardInvisible(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;

View file

@ -47,7 +47,7 @@ void main(void) {
float opacity = getMaterialOpacity(mat) * _color.a;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
<$discardInvisible(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;

View file

@ -57,7 +57,7 @@ void main(void) {
float opacity = getMaterialOpacity(mat) * _color.a;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
<$discardInvisible(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;

View file

@ -34,7 +34,7 @@ void main(void) {
float opacity = getMaterialOpacity(mat) * _color.a;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
<$discardInvisible(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;

View file

@ -44,7 +44,7 @@ void main(void) {
float opacity = getMaterialOpacity(mat) * _color.a;
<$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>;
<$discardTransparent(opacity)$>;
<$discardInvisible(opacity)$>;
vec3 albedo = getMaterialAlbedo(mat);
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;

View file

@ -12,7 +12,7 @@
<@include gpu/Transform.slh@>
<$declareStandardTransform()$>
layout(std140, binding=0) uniform parabolaData {
struct ParabolaData {
vec3 velocity;
float parabolicDistance;
vec3 acceleration;
@ -20,34 +20,38 @@ layout(std140, binding=0) uniform parabolaData {
vec4 color;
int numSections;
ivec3 spare;
}
layout(std140, binding=0) uniform parabolaData {
ParabolaData _parabolaData;
};
layout(location=0) out vec4 _color;
void main(void) {
_color = color;
_color = _parabolaData.color;
float t = parabolicDistance * (float(gl_VertexID / 2) / float(numSections));
float t = _parabolaData.parabolicDistance * (float(gl_VertexID / 2) / float(_parabolaData.numSections));
vec4 pos = vec4(velocity * t + 0.5 * acceleration * t * t, 1);
vec4 pos = vec4(_parabolaData.velocity * t + 0.5 * _parabolaData.acceleration * t * t, 1);
const float EPSILON = 0.00001;
vec4 normal;
TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
if (dot(acceleration, acceleration) < EPSILON) {
if (dot(_parabolaData.acceleration, _parabolaData.acceleration) < EPSILON) {
// Handle case where acceleration == (0, 0, 0)
vec3 eyeUp = vec3(0, 1, 0);
vec3 worldUp;
<$transformEyeToWorldDir(cam, eyeUp, worldUp)$>
normal = vec4(normalize(cross(velocity, worldUp)), 0);
normal = vec4(normalize(cross(_parabolaData.velocity, worldUp)), 0);
} else {
normal = vec4(normalize(cross(velocity, acceleration)), 0);
normal = vec4(normalize(cross(_parabolaData.velocity, _parabolaData.acceleration)), 0);
}
if (gl_VertexID % 2 == 0) {
pos += 0.5 * width * normal;
pos += 0.5 * _parabolaData.width * normal;
} else {
pos -= 0.5 * width * normal;
pos -= 0.5 * _parabolaData.width * normal;
}
<$transformModelToClipPos(cam, obj, pos, gl_Position)$>

View file

@ -48,9 +48,9 @@ void main(void) {
#ifdef PROCEDURAL
#ifdef PROCEDURAL_V1
specular = getProceduralColor().rgb;
diffuse = getProceduralColor().rgb;
// Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline
//specular = pow(specular, vec3(2.2));
//diffuse = pow(diffuse, vec3(2.2));
emissiveAmount = 1.0;
#else
emissiveAmount = getProceduralColors(diffuse, specular, shininess);

View file

@ -11,8 +11,10 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
<@include DefaultMaterials.slh@>
<@include DeferredBufferWrite.slh@>
<@include DeferredGlobalLight.slh@>
<$declareEvalGlobalLightingAlphaBlendedWithHaze()$>
<@include render-utils/ShaderConstants.h@>
@ -26,6 +28,8 @@ layout(location=RENDER_UTILS_ATTR_TEXCOORD01) in vec4 _texCoord01;
layout(location=RENDER_UTILS_ATTR_POSITION_MS) in vec4 _positionMS;
layout(location=RENDER_UTILS_ATTR_POSITION_ES) in vec4 _positionES;
layout(location=0) out vec4 _fragColor0;
// For retro-compatibility
#define _normal _normalWS
#define _modelNormal _normalMS
@ -48,9 +52,9 @@ void main(void) {
#ifdef PROCEDURAL
#ifdef PROCEDURAL_V1
specular = getProceduralColor().rgb;
diffuse = getProceduralColor().rgb;
// Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline
//specular = pow(specular, vec3(2.2));
//diffuse = pow(diffuse, vec3(2.2));
emissiveAmount = 1.0;
#else
emissiveAmount = getProceduralColors(diffuse, specular, shininess);
@ -58,19 +62,23 @@ void main(void) {
#endif
TransformCamera cam = getTransformCamera();
vec3 fragPosition = _positionES.xyz;
if (emissiveAmount > 0.0) {
packDeferredFragmentTranslucent(
normal,
_color.a,
specular,
DEFAULT_FRESNEL,
DEFAULT_ROUGHNESS);
_fragColor0 = vec4(diffuse, _color.a);
} else {
packDeferredFragmentTranslucent(
_fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze(
cam._viewInverse,
1.0,
DEFAULT_OCCLUSION,
fragPosition,
normal,
_color.a,
diffuse,
DEFAULT_FRESNEL,
DEFAULT_ROUGHNESS);
length(specular),
DEFAULT_EMISSIVE,
max(0.0, 1.0 - shininess / 128.0), _color.a),
_color.a);
}
}

View file

@ -22,10 +22,8 @@
#include "Args.h"
using namespace render;
const gpu::PipelinePointer DrawSceneOctree::getDrawCellBoundsPipeline() {
if (!_drawCellBoundsPipeline) {
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::drawCellBounds);
@ -71,7 +69,6 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS
std::static_pointer_cast<Config>(renderContext->jobConfig)->numAllocatedCells = (int)scene->getSpatialTree().getNumAllocatedCells();
std::static_pointer_cast<Config>(renderContext->jobConfig)->numFreeCells = (int)scene->getSpatialTree().getNumFreeCells();
gpu::doInBatch("DrawSceneOctree::run", args->_context, [&](gpu::Batch& batch) {
glm::mat4 projMat;
Transform viewMat;
@ -86,44 +83,30 @@ void DrawSceneOctree::run(const RenderContextPointer& renderContext, const ItemS
// bind the one gpu::Pipeline we need
batch.setPipeline(getDrawCellBoundsPipeline());
if (_showVisibleCells) {
for (const auto& cellID : inSelection.cellSelection.insideCells) {
auto drawCellBounds = [this, &scene, &batch](const std::vector<gpu::Stamp>& cells) {
for (const auto& cellID : cells) {
auto cell = scene->getSpatialTree().getConcreteCell(cellID);
auto cellLoc = cell.getlocation();
glm::ivec4 cellLocation(cellLoc.pos.x, cellLoc.pos.y, cellLoc.pos.z, cellLoc.depth);
bool doDraw = true;
if (cell.isBrickEmpty() || !cell.hasBrick()) {
bool empty = cell.isBrickEmpty() || !cell.hasBrick();
if (empty) {
if (!_showEmptyCells) {
doDraw = false;
continue;
}
cellLocation.w *= -1;
cellLocation.w *= -1.0;
} else if (!empty && !_showVisibleCells) {
continue;
}
if (doDraw) {
batch._glUniform4iv(gpu::slot::uniform::Extra0, 1, ((const int*)(&cellLocation)));
batch.draw(gpu::LINES, 24, 0);
}
}
for (const auto& cellID : inSelection.cellSelection.partialCells) {
auto cell = scene->getSpatialTree().getConcreteCell(cellID);
auto cellLoc = cell.getlocation();
glm::ivec4 cellLocation(cellLoc.pos.x, cellLoc.pos.y, cellLoc.pos.z, cellLoc.depth);
bool doDraw = true;
if (cell.isBrickEmpty() || !cell.hasBrick()) {
if (!_showEmptyCells) {
doDraw = false;
}
cellLocation.w *= -1;
}
if (doDraw) {
batch._glUniform4iv(gpu::slot::uniform::Extra0, 1, ((const int*)(&cellLocation)));
batch.draw(gpu::LINES, 24, 0);
}
batch._glUniform4iv(gpu::slot::uniform::Extra0, 1, ((const int*)(&cellLocation)));
batch.draw(gpu::LINES, 24, 0);
}
}
};
drawCellBounds(inSelection.cellSelection.insideCells);
drawCellBounds(inSelection.cellSelection.partialCells);
// Draw the LOD Reticle
{
float angle = glm::degrees(getPerspectiveAccuracyAngle(args->_sizeScale, args->_boundaryLevelAdjust));
@ -142,10 +125,6 @@ const gpu::PipelinePointer DrawItemSelection::getDrawItemBoundPipeline() {
if (!_drawItemBoundPipeline) {
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::drawItemBounds);
//_drawItemBoundPosLoc = program->getUniforms().findLocation("inBoundPos");
//_drawItemBoundDimLoc = program->getUniforms().findLocation("inBoundDim");
//_drawCellLocationLoc = program->getUniforms().findLocation("inCellLocation");
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, false, gpu::LESS_EQUAL);
@ -173,6 +152,19 @@ void DrawItemSelection::run(const RenderContextPointer& renderContext, const Ite
RenderArgs* args = renderContext->args;
auto& scene = renderContext->_scene;
if (!_boundsBufferInside) {
_boundsBufferInside = std::make_shared<gpu::Buffer>(sizeof(render::ItemBound));
}
if (!_boundsBufferInsideSubcell) {
_boundsBufferInsideSubcell = std::make_shared<gpu::Buffer>(sizeof(render::ItemBound));
}
if (!_boundsBufferPartial) {
_boundsBufferPartial = std::make_shared<gpu::Buffer>(sizeof(render::ItemBound));
}
if (!_boundsBufferPartialSubcell) {
_boundsBufferPartialSubcell = std::make_shared<gpu::Buffer>(sizeof(render::ItemBound));
}
gpu::doInBatch("DrawItemSelection::run", args->_context, [&](gpu::Batch& batch) {
glm::mat4 projMat;
Transform viewMat;
@ -187,63 +179,35 @@ void DrawItemSelection::run(const RenderContextPointer& renderContext, const Ite
// bind the one gpu::Pipeline we need
batch.setPipeline(getDrawItemBoundPipeline());
auto drawItemBounds = [&](const render::ItemIDs itemIDs, const gpu::BufferPointer buffer) {
render::ItemBounds itemBounds;
for (const auto& itemID : itemIDs) {
auto& item = scene->getItem(itemID);
auto itemBound = item.getBound();
if (!itemBound.isInvalid()) {
itemBounds.emplace_back(itemID, itemBound);
}
}
if (itemBounds.size() > 0) {
buffer->setData(itemBounds.size() * sizeof(render::ItemBound), (const gpu::Byte*) itemBounds.data());
batch.setResourceBuffer(0, buffer);
batch.draw(gpu::LINES, (gpu::uint32) itemBounds.size() * 24, 0);
}
};
if (_showInsideItems) {
for (const auto& itemID : inSelection.insideItems) {
auto& item = scene->getItem(itemID);
auto itemBound = item.getBound();
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
glm::ivec4 cellLocation(0, 0, 0, itemCell.depth);
//batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
//batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
//batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
batch.draw(gpu::LINES, 24, 0);
}
drawItemBounds(inSelection.insideItems, _boundsBufferInside);
}
if (_showInsideSubcellItems) {
for (const auto& itemID : inSelection.insideSubcellItems) {
auto& item = scene->getItem(itemID);
auto itemBound = item.getBound();
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
glm::ivec4 cellLocation(0, 0, 1, itemCell.depth);
//batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
//batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
//batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
batch.draw(gpu::LINES, 24, 0);
}
drawItemBounds(inSelection.insideSubcellItems, _boundsBufferInsideSubcell);
}
if (_showPartialItems) {
for (const auto& itemID : inSelection.partialItems) {
auto& item = scene->getItem(itemID);
auto itemBound = item.getBound();
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
glm::ivec4 cellLocation(0, 0, 0, itemCell.depth);
//batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
//batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
//batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
batch.draw(gpu::LINES, 24, 0);
}
drawItemBounds(inSelection.partialItems, _boundsBufferPartial);
}
if (_showPartialSubcellItems) {
for (const auto& itemID : inSelection.partialSubcellItems) {
auto& item = scene->getItem(itemID);
auto itemBound = item.getBound();
auto itemCell = scene->getSpatialTree().getCellLocation(item.getCell());
glm::ivec4 cellLocation(0, 0, 1, itemCell.depth);
//batch._glUniform4iv(_drawCellLocationLoc, 1, ((const int*)(&cellLocation)));
//batch._glUniform3fv(_drawItemBoundPosLoc, 1, (const float*)(&itemBound.getCorner()));
//batch._glUniform3fv(_drawItemBoundDimLoc, 1, (const float*)(&itemBound.getScale()));
batch.draw(gpu::LINES, 24, 0);
}
drawItemBounds(inSelection.partialSubcellItems, _boundsBufferPartialSubcell);
}
batch.setResourceBuffer(0, 0);
});
}

View file

@ -52,7 +52,6 @@ namespace render {
class DrawSceneOctree {
gpu::PipelinePointer _drawCellBoundsPipeline;
gpu::BufferPointer _cells;
gpu::PipelinePointer _drawLODReticlePipeline;
gpu::PipelinePointer _drawItemBoundPipeline;
@ -107,6 +106,10 @@ namespace render {
class DrawItemSelection {
gpu::PipelinePointer _drawItemBoundPipeline;
gpu::BufferPointer _boundsBufferInside;
gpu::BufferPointer _boundsBufferInsideSubcell;
gpu::BufferPointer _boundsBufferPartial;
gpu::BufferPointer _boundsBufferPartialSubcell;
bool _showInsideItems; // initialized by Config
bool _showInsideSubcellItems; // initialized by Config

View file

@ -33,9 +33,6 @@ void DrawStatusConfig::dirtyHelper() {
const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() {
if (!_drawItemBoundsPipeline) {
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::drawItemBounds);
//_drawItemBoundPosLoc = program->getUniforms().findLocation("inBoundPos");
//_drawItemBoundDimLoc = program->getUniforms().findLocation("inBoundDim");
//_drawItemCellLocLoc = program->getUniforms().findLocation("inCellLocation");
auto state = std::make_shared<gpu::State>();
@ -54,11 +51,7 @@ const gpu::PipelinePointer DrawStatus::getDrawItemBoundsPipeline() {
const gpu::PipelinePointer DrawStatus::getDrawItemStatusPipeline() {
if (!_drawItemStatusPipeline) {
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::blurGaussianDepthAwareV);
//_drawItemStatusPosLoc = program->getUniforms().findLocation("");
//_drawItemStatusDimLoc = program->getUniforms().findLocation("");
//_drawItemStatusValue0Loc = program->getUniforms().findLocation("");
//_drawItemStatusValue1Loc = program->getUniforms().findLocation("");
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render::program::drawItemStatus);
auto state = std::make_shared<gpu::State>();
@ -99,36 +92,30 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp
const auto& inItems = input.get0();
const auto jitter = input.get1();
// FIrst thing, we collect the bound and the status for all the items we want to render
// First thing, we collect the bound and the status for all the items we want to render
int nbItems = 0;
render::ItemBounds itemBounds;
std::vector<std::pair<glm::ivec4, glm::ivec4>> itemStatus;
{
_itemBounds.resize(inItems.size());
_itemStatus.resize(inItems.size());
_itemCells.resize(inItems.size());
// AABox* itemAABox = reinterpret_cast<AABox*> (_itemBounds->editData());
// glm::ivec4* itemStatus = reinterpret_cast<glm::ivec4*> (_itemStatus->editData());
// Octree::Location* itemCell = reinterpret_cast<Octree::Location*> (_itemCells->editData());
for (size_t i = 0; i < inItems.size(); ++i) {
const auto& item = inItems[i];
if (!item.bound.isInvalid()) {
if (!item.bound.isNull()) {
_itemBounds[i] = item.bound;
itemBounds.emplace_back(render::ItemBound(item.id, item.bound));
} else {
_itemBounds[i].setBox(item.bound.getCorner(), 0.1f);
itemBounds.emplace_back(item.id, AABox(item.bound.getCorner(), 0.1f));
}
auto& itemScene = scene->getItem(item.id);
_itemCells[i] = scene->getSpatialTree().getCellLocation(itemScene.getCell());
auto itemStatusPointer = itemScene.getStatus();
if (itemStatusPointer) {
itemStatus.push_back(std::pair<glm::ivec4, glm::ivec4>());
// Query the current status values, this is where the statusGetter lambda get called
auto&& currentStatusValues = itemStatusPointer->getCurrentValues();
int valueNum = 0;
for (int vec4Num = 0; vec4Num < NUM_STATUS_VEC4_PER_ITEM; vec4Num++) {
auto& value = (vec4Num ? _itemStatus[i].first : _itemStatus[i].second);
auto& value = (vec4Num ? itemStatus[nbItems].first : itemStatus[nbItems].second);
value = glm::ivec4(Item::Status::Value::INVALID.getPackedData());
for (int component = 0; component < VEC4_LENGTH; component++) {
valueNum = vec4Num * VEC4_LENGTH + component;
@ -138,7 +125,8 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp
}
}
} else {
_itemStatus[i].first = _itemStatus[i].second = glm::ivec4(Item::Status::Value::INVALID.getPackedData());
auto invalid = glm::ivec4(Item::Status::Value::INVALID.getPackedData());
itemStatus.emplace_back(invalid, invalid);
}
nbItems++;
}
@ -149,7 +137,11 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp
return;
}
// Allright, something to render let's do it
if (!_boundsBuffer) {
_boundsBuffer = std::make_shared<gpu::Buffer>(sizeof(render::ItemBound));
}
// Alright, something to render let's do it
gpu::doInBatch("DrawStatus::run", args->_context, [&](gpu::Batch& batch) {
glm::mat4 projMat;
Transform viewMat;
@ -165,32 +157,24 @@ void DrawStatus::run(const RenderContextPointer& renderContext, const Input& inp
// bind the one gpu::Pipeline we need
batch.setPipeline(getDrawItemBoundsPipeline());
//AABox* itemAABox = reinterpret_cast<AABox*> (_itemBounds->editData());
//glm::ivec4* itemStatus = reinterpret_cast<glm::ivec4*> (_itemStatus->editData());
//Octree::Location* itemCell = reinterpret_cast<Octree::Location*> (_itemCells->editData());
const unsigned int VEC3_ADRESS_OFFSET = 3;
_boundsBuffer->setData(itemBounds.size() * sizeof(render::ItemBound), (const gpu::Byte*) itemBounds.data());
if (_showDisplay) {
for (int i = 0; i < nbItems; i++) {
//batch._glUniform3fv(gpu::slot::uniform::Extra0, 1, (const float*)&(_itemBounds[i]));
//batch._glUniform3fv(gpu::slot::uniform::Extra1, 1, ((const float*)&(_itemBounds[i])) + VEC3_ADRESS_OFFSET);
//glm::ivec4 cellLocation(_itemCells[i].pos, _itemCells[i].depth);
//batch._glUniform4iv(_drawItemCellLocLoc, 1, ((const int*)(&cellLocation)));
batch.draw(gpu::LINES, 24, 0);
}
batch.setResourceBuffer(0, _boundsBuffer);
batch.draw(gpu::LINES, (gpu::uint32) itemBounds.size() * 24, 0);
}
batch.setResourceBuffer(0, 0);
batch.setResourceTexture(0, gpu::TextureView(getStatusIconMap(), 0));
batch.setPipeline(getDrawItemStatusPipeline());
if (_showNetwork) {
for (int i = 0; i < nbItems; i++) {
batch._glUniform3fv(gpu::slot::uniform::Extra0, 1, (const float*)&(_itemBounds[i]));
batch._glUniform3fv(gpu::slot::uniform::Extra1, 1, ((const float*)&(_itemBounds[i])) + VEC3_ADRESS_OFFSET);
batch._glUniform4iv(gpu::slot::uniform::Extra2, 1, (const int*)&(_itemStatus[i].first));
batch._glUniform4iv(gpu::slot::uniform::Extra3, 1, (const int*)&(_itemStatus[i].second));
for (size_t i = 0; i < itemBounds.size(); i++) {
batch._glUniform3fv(gpu::slot::uniform::Extra0, 1, (const float*)&itemBounds[i].bound.getCorner());
batch._glUniform3fv(gpu::slot::uniform::Extra1, 1, ((const float*)&itemBounds[i].bound.getScale()));
batch._glUniform4iv(gpu::slot::uniform::Extra2, 1, (const int*)&(itemStatus[i].first));
batch._glUniform4iv(gpu::slot::uniform::Extra3, 1, (const int*)&(itemStatus[i].second));
batch.draw(gpu::TRIANGLES, 24 * NUM_STATUS_VEC4_PER_ITEM, 0);
}
}

View file

@ -62,12 +62,7 @@ namespace render {
gpu::PipelinePointer _drawItemBoundsPipeline;
gpu::PipelinePointer _drawItemStatusPipeline;
std::vector<AABox> _itemBounds;
std::vector<std::pair<glm::ivec4, glm::ivec4>> _itemStatus;
std::vector<Octree::Location> _itemCells;
//gpu::BufferPointer _itemBounds;
//gpu::BufferPointer _itemCells;
//gpu::BufferPointer _itemStatus;
gpu::BufferPointer _boundsBuffer;
gpu::TexturePointer _statusIconMap;
};
}

View file

@ -321,6 +321,7 @@ inline QDebug operator<<(QDebug debug, const ItemFilter& me) {
// Handy type to just pass the ID and the bound of an item
class ItemBound {
public:
ItemBound() {}
ItemBound(ItemID id) : id(id) { }
ItemBound(ItemID id, const AABox& bound) : id(id), bound(bound) { }

View file

@ -24,7 +24,6 @@ layout(location=GPU_UNIFORM_EXTRA0) uniform ivec4 inCellLocation;
layout(location=0) out vec4 varColor;
void main(void) {
const vec4 UNIT_BOX[8] = vec4[8](
vec4(0.0, 0.0, 0.0, 1.0),

View file

@ -20,10 +20,8 @@
<@include gpu/Color.slh@>
<$declareColorWheel()$>
layout(location=GPU_UNIFORM_COLOR) uniform vec4 inColor;
struct ItemBound {
vec4 id_boundPos;
vec4 boundDim_s;
@ -48,8 +46,6 @@ ItemBound getItemBound(int i) {
}
#endif
layout(location=0) out vec4 varColor;
layout(location=1) out vec2 varTexcoord;

View file

@ -74,6 +74,7 @@
#include "WebSocketClass.h"
#include "RecordingScriptingInterface.h"
#include "ScriptEngines.h"
#include "StackTestScriptingInterface.h"
#include "ModelScriptingInterface.h"
@ -762,6 +763,10 @@ void ScriptEngine::init() {
qScriptRegisterMetaType(this, meshesToScriptValue, meshesFromScriptValue);
registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());
#if DEV_BUILD || PR_BUILD
registerGlobalObject("StackTest", new StackTestScriptingInterface(this));
#endif
}
void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {

View file

@ -0,0 +1,31 @@
//
// StackTestScriptingInterface.cpp
// libraries/script-engine/src
//
// Created by Clement Brisset on 7/25/18.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "StackTestScriptingInterface.h"
#include <QLoggingCategory>
#include <QCoreApplication>
Q_DECLARE_LOGGING_CATEGORY(stackTest)
Q_LOGGING_CATEGORY(stackTest, "hifi.tools.stack-test")
void StackTestScriptingInterface::pass(QString message) {
qCInfo(stackTest) << "PASS" << qPrintable(message);
}
void StackTestScriptingInterface::fail(QString message) {
qCInfo(stackTest) << "FAIL" << qPrintable(message);
}
void StackTestScriptingInterface::exit(QString message) {
qCInfo(stackTest) << "COMPLETE" << qPrintable(message);
qApp->exit();
}

View file

@ -0,0 +1,31 @@
//
// StackTestScriptingInterface.h
// libraries/script-engine/src
//
// Created by Clement Brisset on 7/25/18.
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_StackTestScriptingInterface_h
#define hifi_StackTestScriptingInterface_h
#include <QObject>
class StackTestScriptingInterface : public QObject {
Q_OBJECT
public:
StackTestScriptingInterface(QObject* parent = nullptr) : QObject(parent) {}
Q_INVOKABLE void pass(QString message = QString());
Q_INVOKABLE void fail(QString message = QString());
Q_INVOKABLE void exit(QString message = QString());
};
#endif // hifi_StackTestScriptingInterface_h

View file

@ -294,7 +294,19 @@ glm::vec3 safeEulerAngles(const glm::quat& q) {
// Helper function returns the positive angle (in radians) between two 3D vectors
float angleBetween(const glm::vec3& v1, const glm::vec3& v2) {
return acosf((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2)));
float lengthFactor = glm::length(v1) * glm::length(v2);
if (lengthFactor < EPSILON) {
qWarning() << "DANGER: don't supply zero-length vec3's as arguments";
}
float cosAngle = glm::dot(v1, v2) / lengthFactor;
// If v1 and v2 are colinear, then floating point rounding errors might cause
// cosAngle to be slightly higher than 1 or slightly lower than -1
// which is are values for which acos is not defined and result in a NaN
// So we clamp the value to insure the value is in the correct range
cosAngle = glm::clamp(cosAngle, -1.0f, 1.0f);
return acosf(cosAngle);
}
// Helper function return the rotation from the first vector onto the second

View file

@ -91,58 +91,7 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
return mergedMap;
}
void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) {
// load the user config
const QString USER_CONFIG_FILE_OPTION = "--user-config";
static const QString USER_CONFIG_FILE_NAME = "config.json";
int userConfigIndex = argumentList.indexOf(USER_CONFIG_FILE_OPTION);
if (userConfigIndex != -1) {
_userConfigFilename = argumentList[userConfigIndex + 1];
} else {
// we weren't passed a user config path
_userConfigFilename = PathUtils::getAppDataFilePath(USER_CONFIG_FILE_NAME);
// as of 1/19/2016 this path was moved so we attempt a migration for first run post migration here
// figure out what the old path was
// if our build version is "dev" we should migrate from a different organization folder
auto oldConfigFilename = QString("%1/%2/%3/%4").arg(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation),
QCoreApplication::organizationName(),
QCoreApplication::applicationName(),
USER_CONFIG_FILE_NAME);
oldConfigFilename = oldConfigFilename.replace("High Fidelity - dev", "High Fidelity");
// check if there's already a config file at the new path
QFile newConfigFile { _userConfigFilename };
if (!newConfigFile.exists()) {
QFile oldConfigFile { oldConfigFilename };
if (oldConfigFile.exists()) {
// we have the old file and not the new file - time to copy the file
// make the destination directory if it doesn't exist
auto dataDirectory = PathUtils::getAppDataPath();
if (QDir().mkpath(dataDirectory)) {
if (oldConfigFile.copy(_userConfigFilename)) {
qCDebug(shared) << "Migrated config file from" << oldConfigFilename << "to" << _userConfigFilename;
} else {
qCWarning(shared) << "Could not copy previous config file from" << oldConfigFilename << "to" << _userConfigFilename
<< "- please try to copy manually and restart.";
}
} else {
qCWarning(shared) << "Could not create application data directory" << dataDirectory << "- unable to migrate previous config file.";
}
}
}
}
void HifiConfigVariantMap::loadConfig() {
loadMapFromJSONFile(_userConfig, _userConfigFilename);
}

View file

@ -21,7 +21,7 @@ class HifiConfigVariantMap {
public:
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
void loadConfig(const QStringList& argumentList);
void loadConfig();
const QVariant value(const QString& key) const { return _userConfig.value(key); }
QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false)
@ -30,6 +30,7 @@ public:
QVariantMap& getConfig() { return _userConfig; }
const QString& getUserConfigFilename() const { return _userConfigFilename; }
void setUserConfigFilename(const QString& filename) { _userConfigFilename = filename; }
private:
QString _userConfigFilename;

View file

@ -33,17 +33,12 @@ LogHandler& LogHandler::getInstance() {
}
LogHandler::LogHandler() {
// when the log handler is first setup we should print our timezone
QString timezoneString = "Time zone: " + QDateTime::currentDateTime().toString("t");
printMessage(LogMsgType::LogInfo, QMessageLogContext(), timezoneString);
// make sure we setup the repeated message flusher, but do it on the LogHandler thread
QMetaObject::invokeMethod(this, "setupRepeatedMessageFlusher");
}
LogHandler::~LogHandler() {
flushRepeatedMessages();
printMessage(LogMsgType::LogDebug, QMessageLogContext(), "LogHandler shutdown.");
}
const char* stringForLogType(LogMsgType msgType) {

View file

@ -90,7 +90,6 @@ const QString& PathUtils::resourcesPath() {
staticResourcePath = projectRootPath() + "/interface/resources/";
}
#endif
qDebug() << "Resource path resolved to " << staticResourcePath;
});
return staticResourcePath;
}
@ -105,7 +104,6 @@ const QString& PathUtils::resourcesUrl() {
staticResourcePath = QUrl::fromLocalFile(projectRootPath() + "/interface/resources/").toString();
}
#endif
qDebug() << "Resource url resolved to " << staticResourcePath;
});
return staticResourcePath;
}

View file

@ -14,20 +14,11 @@ import "configSlider"
Item {
id: root
property var config: Render.getConfig("RenderMainView.Bloom")
property var configThreshold: Render.getConfig("RenderMainView.BloomThreshold")
property var configDebug: Render.getConfig("RenderMainView.DebugBloom")
Column {
spacing: 8
CheckBox {
text: "Enable"
checked: root.config["enabled"]
onCheckedChanged: {
root.config["enabled"] = checked;
}
}
GroupBox {
title: "Debug"
Row {
@ -88,35 +79,5 @@ Item {
}
}
}
ConfigSlider {
label: "Intensity"
integral: false
config: root.config
property: "intensity"
max: 1.0
min: 0.0
width: 280
height:38
}
ConfigSlider {
label: "Size"
integral: false
config: root.config
property: "size"
max: 1.0
min: 0.0
width: 280
height:38
}
ConfigSlider {
label: "Threshold"
integral: false
config: root.configThreshold
property: "threshold"
max: 2.0
min: 0.0
width: 280
height:38
}
}
}

View file

@ -19,7 +19,7 @@ Column {
Component.onCompleted: {
sceneOctree.enabled = true;
itemSelection.enabled = true;
itemSelection.enabled = true;
sceneOctree.showVisibleCells = false;
sceneOctree.showEmptyCells = false;
itemSelection.showInsideItems = false;
@ -29,9 +29,9 @@ Column {
}
Component.onDestruction: {
sceneOctree.enabled = false;
itemSelection.enabled = false;
itemSelection.enabled = false;
Render.getConfig("RenderMainView.FetchSceneSelection").freezeFrustum = false;
Render.getConfig("RenderMainView.CullSceneSelection").freezeFrustum = false;
Render.getConfig("RenderMainView.CullSceneSelection").freezeFrustum = false;
}
GroupBox {
@ -44,7 +44,7 @@ Column {
CheckBox {
text: "Freeze Culling Frustum"
checked: false
onCheckedChanged: {
onCheckedChanged: {
Render.getConfig("RenderMainView.FetchSceneSelection").freezeFrustum = checked;
Render.getConfig("RenderMainView.CullSceneSelection").freezeFrustum = checked;
}
@ -88,15 +88,19 @@ Column {
text: "Partial Sub-cell Items"
checked: false
onCheckedChanged: { root.itemSelection.showPartialSubcellItems = checked }
}
}
}
}
}
}
GroupBox {
title: "Render Items"
anchors.left: parent.left;
anchors.right: parent.right;
Column{
anchors.left: parent.left;
anchors.right: parent.right;
Repeater {
model: [ "Opaque:RenderMainView.DrawOpaqueDeferred", "Transparent:RenderMainView.DrawTransparentDeferred", "Light:RenderMainView.DrawLight",
"Opaque Overlays:RenderMainView.DrawOverlay3DOpaque", "Transparent Overlays:RenderMainView.DrawOverlay3DTransparent" ]

View file

@ -15,6 +15,6 @@ var window = new OverlayWindow({
title: 'Bloom',
source: qml,
width: 285,
height: 210,
height: 40,
});
window.closed.connect(function() { Script.stop(); });

View file

@ -38,30 +38,34 @@ getGrabPointSphereOffset = function(handController, ignoreSensorToWorldScale) {
getControllerWorldLocation = function (handController, doOffset) {
var orientation;
var position;
var pose = Controller.getPoseValue(handController);
var valid = pose.valid;
var controllerJointIndex;
if (pose.valid) {
if (handController === Controller.Standard.RightHand) {
controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND");
} else {
controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
}
orientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(controllerJointIndex));
position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(controllerJointIndex)));
var valid = false;
if (handController >= 0) {
var pose = Controller.getPoseValue(handController);
valid = pose.valid;
var controllerJointIndex;
if (pose.valid) {
if (handController === Controller.Standard.RightHand) {
controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND");
} else {
controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
}
orientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(controllerJointIndex));
position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(controllerJointIndex)));
// add to the real position so the grab-point is out in front of the hand, a bit
if (doOffset) {
var offset = getGrabPointSphereOffset(handController);
position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, offset));
}
// add to the real position so the grab-point is out in front of the hand, a bit
if (doOffset) {
var offset = getGrabPointSphereOffset(handController);
position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, offset));
}
} else if (!HMD.isHandControllerAvailable()) {
// NOTE: keep this offset in sync with scripts/system/controllers/handControllerPointer.js:493
var VERTICAL_HEAD_LASER_OFFSET = 0.1 * MyAvatar.sensorToWorldScale;
position = Vec3.sum(Camera.position, Vec3.multiplyQbyV(Camera.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0}));
orientation = Quat.multiply(Camera.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }));
valid = true;
} else if (!HMD.isHandControllerAvailable()) {
// NOTE: keep this offset in sync with scripts/system/controllers/handControllerPointer.js:493
var VERTICAL_HEAD_LASER_OFFSET = 0.1 * MyAvatar.sensorToWorldScale;
position = Vec3.sum(Camera.position, Vec3.multiplyQbyV(Camera.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0}));
orientation = Quat.multiply(Camera.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }));
valid = true;
}
}
return {position: position,

View file

@ -5,7 +5,7 @@ project(${TARGET_NAME})
SET (CMAKE_AUTOUIC ON)
SET (CMAKE_AUTOMOC ON)
setup_hifi_project (Core Widgets Network)
setup_hifi_project (Core Widgets Network Xml)
link_hifi_libraries ()
# FIX: Qt was built with -reduce-relocations
@ -18,7 +18,7 @@ include_directories (${CMAKE_CURRENT_SOURCE_DIR})
include_directories (${Qt5Core_INCLUDE_DIRS})
include_directories (${Qt5Widgets_INCLUDE_DIRS})
set (QT_LIBRARIES Qt5::Core Qt5::Widgets)
set (QT_LIBRARIES Qt5::Core Qt5::Widgets QT::Gui Qt5::Xml)
if (WIN32)
# Do not show Console

View file

@ -24,7 +24,7 @@ extern AutoTester* autoTester;
#include <math.h>
Test::Test() {
mismatchWindow.setModal(true);
_mismatchWindow.setModal(true);
if (autoTester) {
autoTester->setUserText("highfidelity");
@ -34,35 +34,35 @@ Test::Test() {
bool Test::createTestResultsFolderPath(const QString& directory) {
QDateTime now = QDateTime::currentDateTime();
testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT);
QDir testResultsFolder(testResultsFolderPath);
_testResultsFolderPath = directory + "/" + TEST_RESULTS_FOLDER + "--" + now.toString(DATETIME_FORMAT);
QDir testResultsFolder(_testResultsFolderPath);
// Create a new test results folder
return QDir().mkdir(testResultsFolderPath);
return QDir().mkdir(_testResultsFolderPath);
}
void Test::zipAndDeleteTestResultsFolder() {
QString zippedResultsFileName { testResultsFolderPath + ".zip" };
QString zippedResultsFileName { _testResultsFolderPath + ".zip" };
QFileInfo fileInfo(zippedResultsFileName);
if (!fileInfo.exists()) {
QFile::remove(zippedResultsFileName);
}
QDir testResultsFolder(testResultsFolderPath);
QDir testResultsFolder(_testResultsFolderPath);
if (!testResultsFolder.isEmpty()) {
JlCompress::compressDir(testResultsFolderPath + ".zip", testResultsFolderPath);
JlCompress::compressDir(_testResultsFolderPath + ".zip", _testResultsFolderPath);
}
testResultsFolder.removeRecursively();
//In all cases, for the next evaluation
testResultsFolderPath = "";
index = 1;
_testResultsFolderPath = "";
_index = 1;
}
bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) {
progressBar->setMinimum(0);
progressBar->setMaximum(expectedImagesFullFilenames.length() - 1);
progressBar->setMaximum(_expectedImagesFullFilenames.length() - 1);
progressBar->setValue(0);
progressBar->setVisible(true);
@ -70,10 +70,10 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
// Quit loop if user has aborted due to a failed test.
bool success{ true };
bool keepOn{ true };
for (int i = 0; keepOn && i < expectedImagesFullFilenames.length(); ++i) {
for (int i = 0; keepOn && i < _expectedImagesFullFilenames.length(); ++i) {
// First check that images are the same size
QImage resultImage(resultImagesFullFilenames[i]);
QImage expectedImage(expectedImagesFullFilenames[i]);
QImage resultImage(_resultImagesFullFilenames[i]);
QImage expectedImage(_expectedImagesFullFilenames[i]);
double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical
@ -82,30 +82,30 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size");
similarityIndex = -100.0;
} else {
similarityIndex = imageComparer.compareImages(resultImage, expectedImage);
similarityIndex = _imageComparer.compareImages(resultImage, expectedImage);
}
if (similarityIndex < THRESHOLD) {
TestFailure testFailure = TestFailure{
(float)similarityIndex,
expectedImagesFullFilenames[i].left(expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
QFileInfo(expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image
QFileInfo(resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image
_expectedImagesFullFilenames[i].left(_expectedImagesFullFilenames[i].lastIndexOf("/") + 1), // path to the test (including trailing /)
QFileInfo(_expectedImagesFullFilenames[i].toStdString().c_str()).fileName(), // filename of expected image
QFileInfo(_resultImagesFullFilenames[i].toStdString().c_str()).fileName() // filename of result image
};
mismatchWindow.setTestFailure(testFailure);
_mismatchWindow.setTestFailure(testFailure);
if (!isInteractiveMode) {
appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage());
appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage());
success = false;
} else {
mismatchWindow.exec();
_mismatchWindow.exec();
switch (mismatchWindow.getUserResponse()) {
switch (_mismatchWindow.getUserResponse()) {
case USER_RESPONSE_PASS:
break;
case USE_RESPONSE_FAIL:
appendTestResultsToFile(testResultsFolderPath, testFailure, mismatchWindow.getComparisonImage());
appendTestResultsToFile(_testResultsFolderPath, testFailure, _mismatchWindow.getComparisonImage());
success = false;
break;
case USER_RESPONSE_ABORT:
@ -126,20 +126,20 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar)
return success;
}
void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) {
if (!QDir().exists(testResultsFolderPath)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + testResultsFolderPath + " not found");
void Test::appendTestResultsToFile(const QString& _testResultsFolderPath, TestFailure testFailure, QPixmap comparisonImage) {
if (!QDir().exists(_testResultsFolderPath)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Folder " + _testResultsFolderPath + " not found");
exit(-1);
}
QString err = QString::number(testFailure._error).left(6);
QString failureFolderPath { testResultsFolderPath + "/" + err + "-Failure_" + QString::number(index) + "--" + testFailure._actualImageFilename.left(testFailure._actualImageFilename.length() - 4) };
QString failureFolderPath { _testResultsFolderPath + "/" + err + "-Failure_" + QString::number(_index) + "--" + testFailure._actualImageFilename.left(testFailure._actualImageFilename.length() - 4) };
if (!QDir().mkdir(failureFolderPath)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create folder " + failureFolderPath);
exit(-1);
}
++index;
++_index;
QFile descriptionFile(failureFolderPath + "/" + TEST_RESULTS_FILENAME);
if (!descriptionFile.open(QIODevice::ReadWrite)) {
@ -152,7 +152,7 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai
stream << "Test failed in folder " << testFailure._pathname.left(testFailure._pathname.length() - 1) << endl; // remove trailing '/'
stream << "Expected image was " << testFailure._expectedImageFilename << endl;
stream << "Actual image was " << testFailure._actualImageFilename << endl;
stream << "Similarity index was " << testFailure._error << endl;
stream << "Similarity _index was " << testFailure._error << endl;
descriptionFile.close();
@ -180,26 +180,26 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai
void Test::startTestsEvaluation(const QString& testFolder, const QString& branchFromCommandLine, const QString& userFromCommandLine) {
if (testFolder.isNull()) {
// Get list of JPEG images in folder, sorted by name
QString previousSelection = snapshotDirectory;
QString previousSelection = _snapshotDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent,
_snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (snapshotDirectory == "") {
snapshotDirectory = previousSelection;
if (_snapshotDirectory == "") {
_snapshotDirectory = previousSelection;
return;
}
} else {
snapshotDirectory = testFolder;
exitWhenComplete = true;
_snapshotDirectory = testFolder;
_exitWhenComplete = true;
}
// Quit if test results folder could not be created
if (!createTestResultsFolderPath(snapshotDirectory)) {
if (!createTestResultsFolderPath(_snapshotDirectory)) {
return;
}
@ -207,20 +207,20 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch
// The expected images are represented as a URL to enable download from GitHub
// Images that are in the wrong format are ignored.
QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", snapshotDirectory);
QStringList sortedTestResultsFilenames = createListOfAll_imagesInDirectory("png", _snapshotDirectory);
QStringList expectedImagesURLs;
resultImagesFullFilenames.clear();
expectedImagesFilenames.clear();
expectedImagesFullFilenames.clear();
_resultImagesFullFilenames.clear();
_expectedImagesFilenames.clear();
_expectedImagesFullFilenames.clear();
QString branch = (branchFromCommandLine.isNull()) ? autoTester->getSelectedBranch() : branchFromCommandLine;
QString user = (userFromCommandLine.isNull()) ? autoTester->getSelectedUser() : userFromCommandLine;
foreach(QString currentFilename, sortedTestResultsFilenames) {
QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename;
QString fullCurrentFilename = _snapshotDirectory + "/" + currentFilename;
if (isInSnapshotFilenameFormat("png", currentFilename)) {
resultImagesFullFilenames << fullCurrentFilename;
_resultImagesFullFilenames << fullCurrentFilename;
QString expectedImagePartialSourceDirectory = getExpectedImagePartialSourceDirectory(currentFilename);
@ -237,12 +237,12 @@ void Test::startTestsEvaluation(const QString& testFolder, const QString& branch
// The image retrieved from GitHub needs a unique name
QString expectedImageFilename = currentFilename.replace("/", "_").replace(".png", "_EI.png");
expectedImagesFilenames << expectedImageFilename;
expectedImagesFullFilenames << snapshotDirectory + "/" + expectedImageFilename;
_expectedImagesFilenames << expectedImageFilename;
_expectedImagesFullFilenames << _snapshotDirectory + "/" + expectedImageFilename;
}
}
autoTester->downloadImages(expectedImagesURLs, snapshotDirectory, expectedImagesFilenames);
autoTester->downloadImages(expectedImagesURLs, _snapshotDirectory, _expectedImagesFilenames);
}
void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar) {
@ -258,7 +258,7 @@ void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactive
zipAndDeleteTestResultsFolder();
if (exitWhenComplete) {
if (_exitWhenComplete) {
exit(0);
}
}
@ -310,46 +310,46 @@ void Test::includeTest(QTextStream& textStream, const QString& testPathname) {
// This script will run all text.js scripts in every applicable sub-folder
void Test::createRecursiveScript() {
// Select folder to start recursing from
QString previousSelection = testDirectory;
QString previousSelection = _testDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
testDirectory =
_testDirectory =
QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", parent,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
if (_testDirectory == "") {
_testDirectory = previousSelection;
return;
}
createRecursiveScript(testDirectory, true);
createRecursiveScript(_testDirectory, true);
}
// This method creates a `testRecursive.js` script in every sub-folder.
void Test::createAllRecursiveScripts() {
// Select folder to start recursing from
QString previousSelection = testsRootDirectory;
QString previousSelection = _testsRootDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts",
_testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the recursive scripts",
parent, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testsRootDirectory == "") {
testsRootDirectory = previousSelection;
if (_testsRootDirectory == "") {
_testsRootDirectory = previousSelection;
return;
}
createRecursiveScript(testsRootDirectory, false);
createRecursiveScript(_testsRootDirectory, false);
QDirIterator it(testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
@ -477,42 +477,42 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact
void Test::createTests() {
// Rename files sequentially, as ExpectedResult_00000.jpeg, ExpectedResult_00001.jpg and so on
// Any existing expected result images will be deleted
QString previousSelection = snapshotDirectory;
QString previousSelection = _snapshotDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent,
_snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (snapshotDirectory == "") {
snapshotDirectory = previousSelection;
if (_snapshotDirectory == "") {
_snapshotDirectory = previousSelection;
return;
}
previousSelection = testsRootDirectory;
previousSelection = _testsRootDirectory;
parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent,
_testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select test root folder", parent,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testsRootDirectory == "") {
testsRootDirectory = previousSelection;
if (_testsRootDirectory == "") {
_testsRootDirectory = previousSelection;
return;
}
QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("png", snapshotDirectory);
QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("png", _snapshotDirectory);
int i = 1;
const int maxImages = pow(10, NUM_DIGITS);
foreach (QString currentFilename, sortedImageFilenames) {
QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename;
QString fullCurrentFilename = _snapshotDirectory + "/" + currentFilename;
if (isInSnapshotFilenameFormat("png", currentFilename)) {
if (i >= maxImages) {
QMessageBox::critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported");
@ -522,17 +522,17 @@ void Test::createTests() {
// Path to test is extracted from the file name
// Example:
// filename is tests.engine.interaction.pointer.laser.distanceScaleEnd.00000.jpg
// path is <testDirectory>/engine/interaction/pointer/laser/distanceScaleEnd
// path is <_testDirectory>/engine/interaction/pointer/laser/distanceScaleEnd
//
// Note: we don't use the first part and the last 2 parts of the filename at this stage
//
QStringList pathParts = currentFilename.split(".");
QString fullNewFileName = testsRootDirectory;
QString fullNewFileName = _testsRootDirectory;
for (int j = 1; j < pathParts.size() - 2; ++j) {
fullNewFileName += "/" + pathParts[j];
}
// The image index is the penultimate component of the path parts (the last being the file extension)
// The image _index is the penultimate component of the path parts (the last being the file extension)
QString newFilename = "ExpectedImage_" + pathParts[pathParts.size() - 2].rightJustified(5, '0') + ".png";
fullNewFileName += "/" + newFilename;
@ -621,51 +621,51 @@ ExtractedText Test::getTestScriptLines(QString testFileName) {
// The folder selected must contain a script named "test.js", the file produced is named "test.md"
void Test::createMDFile() {
// Folder selection
QString previousSelection = testDirectory;
QString previousSelection = _testDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", parent,
_testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", parent,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
if (_testDirectory == "") {
_testDirectory = previousSelection;
return;
}
createMDFile(testDirectory);
createMDFile(_testDirectory);
QMessageBox::information(0, "Success", "MD file has been created");
}
void Test::createAllMDFiles() {
// Select folder to start recursing from
QString previousSelection = testsRootDirectory;
QString previousSelection = _testsRootDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", parent,
_testsRootDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select the root folder for the MD files", parent,
QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testsRootDirectory == "") {
testsRootDirectory = previousSelection;
if (_testsRootDirectory == "") {
_testsRootDirectory = previousSelection;
return;
}
// First test if top-level folder has a test.js file
const QString testPathname{ testsRootDirectory + "/" + TEST_FILENAME };
const QString testPathname{ _testsRootDirectory + "/" + TEST_FILENAME };
QFileInfo fileInfo(testPathname);
if (fileInfo.exists()) {
createMDFile(testsRootDirectory);
createMDFile(_testsRootDirectory);
}
QDirIterator it(testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(_testsRootDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
@ -685,9 +685,9 @@ void Test::createAllMDFiles() {
QMessageBox::information(0, "Success", "MD files have been created");
}
void Test::createMDFile(const QString& testDirectory) {
void Test::createMDFile(const QString& _testDirectory) {
// Verify folder contains test.js file
QString testFileName(testDirectory + "/" + TEST_FILENAME);
QString testFileName(_testDirectory + "/" + TEST_FILENAME);
QFileInfo testFileInfo(testFileName);
if (!testFileInfo.exists()) {
QMessageBox::critical(0, "Error", "Could not find file: " + TEST_FILENAME);
@ -696,7 +696,7 @@ void Test::createMDFile(const QString& testDirectory) {
ExtractedText testScriptLines = getTestScriptLines(testFileName);
QString mdFilename(testDirectory + "/" + "test.md");
QString mdFilename(_testDirectory + "/" + "test.md");
QFile mdFile(mdFilename);
if (!mdFile.open(QIODevice::WriteOnly)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
@ -710,7 +710,7 @@ void Test::createMDFile(const QString& testDirectory) {
stream << "# " << testName << "\n";
// Find the relevant part of the path to the test (i.e. from "tests" down
QString partialPath = extractPathFromTestsDown(testDirectory);
QString partialPath = extractPathFromTestsDown(_testDirectory);
stream << "## Run this script URL: [Manual](./test.js?raw=true) [Auto](./testAuto.js?raw=true)(from menu/Edit/Open and Run scripts from URL...)." << "\n\n";
@ -734,23 +734,23 @@ void Test::createMDFile(const QString& testDirectory) {
}
void Test::createTestsOutline() {
QString previousSelection = testDirectory;
QString previousSelection = _testDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
testDirectory =
_testDirectory =
QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (testDirectory == "") {
testDirectory = previousSelection;
if (_testDirectory == "") {
_testDirectory = previousSelection;
return;
}
const QString testsOutlineFilename { "testsOutline.md" };
QString mdFilename(testDirectory + "/" + testsOutlineFilename);
QString mdFilename(_testDirectory + "/" + testsOutlineFilename);
QFile mdFile(mdFilename);
if (!mdFile.open(QIODevice::WriteOnly)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename);
@ -764,10 +764,10 @@ void Test::createTestsOutline() {
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
int rootDepth { testDirectory.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
QDirIterator it(testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
QDirIterator it(_testDirectory.toStdString().c_str(), QDirIterator::Subdirectories);
while (it.hasNext()) {
QString directory = it.next();
@ -821,12 +821,51 @@ void Test::createTestsOutline() {
QMessageBox::information(0, "Success", "Test outline file " + testsOutlineFilename + " has been created");
}
void Test::createTestRailTestCases() {
QString previousSelection = _testDirectory;
QString parent = previousSelection.left(previousSelection.lastIndexOf('/'));
if (!parent.isNull() && parent.right(1) != "/") {
parent += "/";
}
_testDirectory =
QFileDialog::getExistingDirectory(nullptr, "Please select the tests root folder", parent, QFileDialog::ShowDirsOnly);
// If user cancelled then restore previous selection and return
if (_testDirectory == "") {
_testDirectory = previousSelection;
return;
}
QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in",
nullptr, QFileDialog::ShowDirsOnly);
// If user cancelled then return
if (outputDirectory == "") {
return;
}
if (_testRailCreateMode == PYTHON) {
_testRailInterface.createTestSuitePython(_testDirectory, outputDirectory, autoTester->getSelectedUser(),
autoTester->getSelectedBranch());
} else {
_testRailInterface.createTestSuiteXML(_testDirectory, outputDirectory, autoTester->getSelectedUser(),
autoTester->getSelectedBranch());
}
}
void Test::createTestRailRun() {
QString outputDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select a folder to store generated files in",
nullptr, QFileDialog::ShowDirsOnly);
_testRailInterface.createTestRailRun(outputDirectory);
}
QStringList Test::createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory) {
imageDirectory = QDir(pathToImageDirectory);
_imageDirectory = QDir(pathToImageDirectory);
QStringList nameFilters;
nameFilters << "*." + imageFormat;
return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name);
return _imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name);
}
// Snapshots are files in the following format:
@ -889,3 +928,7 @@ QString Test::getExpectedImagePartialSourceDirectory(const QString& filename) {
return result;
}
void Test::setTestRailCreateMode(TestRailCreateMode testRailCreateMode) {
_testRailCreateMode = testRailCreateMode;
}

View file

@ -18,6 +18,7 @@
#include "ImageComparer.h"
#include "ui/MismatchWindow.h"
#include "TestRailInterface.h"
class Step {
public:
@ -33,6 +34,11 @@ public:
StepList stepList;
};
enum TestRailCreateMode {
PYTHON,
XML
};
class Test {
public:
Test();
@ -51,6 +57,9 @@ public:
void createTestsOutline();
void createTestRailTestCases();
void createTestRailRun();
bool compareImageLists(bool isInteractiveMode, QProgressBar* progressBar);
QStringList createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory);
@ -64,11 +73,15 @@ public:
bool createTestResultsFolderPath(const QString& directory);
void zipAndDeleteTestResultsFolder();
bool isAValidDirectory(const QString& pathname);
static bool isAValidDirectory(const QString& pathname);
QString extractPathFromTestsDown(const QString& fullPath);
QString getExpectedImageDestinationDirectory(const QString& filename);
QString getExpectedImagePartialSourceDirectory(const QString& filename);
ExtractedText getTestScriptLines(QString testFileName);
void setTestRailCreateMode(TestRailCreateMode testRailCreateMode);
private:
const QString TEST_FILENAME { "test.js" };
const QString TEST_RESULTS_FOLDER { "TestResults" };
@ -76,14 +89,14 @@ private:
const double THRESHOLD{ 0.96 };
QDir imageDirectory;
QDir _imageDirectory;
MismatchWindow mismatchWindow;
MismatchWindow _mismatchWindow;
ImageComparer imageComparer;
ImageComparer _imageComparer;
QString testResultsFolderPath;
int index { 1 };
QString _testResultsFolderPath;
int _index { 1 };
// Expected images are in the format ExpectedImage_dddd.jpg (d == decimal digit)
const int NUM_DIGITS { 5 };
@ -93,28 +106,30 @@ private:
// The first is the directory containing the test we are working with
// The second is the root directory of all tests
// The third contains the snapshots taken for test runs that need to be evaluated
QString testDirectory;
QString testsRootDirectory;
QString snapshotDirectory;
QString _testDirectory;
QString _testsRootDirectory;
QString _snapshotDirectory;
QStringList expectedImagesFilenames;
QStringList expectedImagesFullFilenames;
QStringList resultImagesFullFilenames;
QStringList _expectedImagesFilenames;
QStringList _expectedImagesFullFilenames;
QStringList _resultImagesFullFilenames;
// Used for accessing GitHub
const QString GIT_HUB_REPOSITORY{ "hifi_tests" };
const QString DATETIME_FORMAT{ "yyyy-MM-dd_hh-mm-ss" };
ExtractedText getTestScriptLines(QString testFileName);
// NOTE: these need to match the appropriate var's in autoTester.js
// var advanceKey = "n";
// var pathSeparator = ".";
const QString ADVANCE_KEY{ "n" };
const QString PATH_SEPARATOR{ "." };
bool exitWhenComplete{ false };
bool _exitWhenComplete{ false };
TestRailInterface _testRailInterface;
TestRailCreateMode _testRailCreateMode { PYTHON };
};
#endif // hifi_test_h

View file

@ -0,0 +1,837 @@
//
// TestRailInterface.cpp
//
// Created by Nissim Hadar on 6 Jul 2018.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TestRailInterface.h"
#include "Test.h"
#include <QDateTime>
#include <QFile>
#include <QMessageBox>
#include <QTextStream>
TestRailInterface::TestRailInterface() {
_testRailTestCasesSelectorWindow.setURL("https://highfidelity.testrail.net");
////_testRailTestCasesSelectorWindow.setURL("https://nissimhadar.testrail.io");
_testRailTestCasesSelectorWindow.setUser("@highfidelity.io");
////_testRailSelectorWindow.setUser("nissim.hadar@gmail.com");
_testRailTestCasesSelectorWindow.setProjectID(INTERFACE_PROJECT_ID);
////_testRailSelectorWindow.setProject(1);
_testRailTestCasesSelectorWindow.setSuiteID(INTERFACE_SUITE_ID);
_testRailRunSelectorWindow.setURL("https://highfidelity.testrail.net");
////_testRailRunSelectorWindow.setURL("https://nissimhadar.testrail.io");
_testRailRunSelectorWindow.setUser("@highfidelity.io");
////_testRailSelectorWindow.setUser("nissim.hadar@gmail.com");
_testRailRunSelectorWindow.setProjectID(INTERFACE_PROJECT_ID);
////_testRailSelectorWindow.setProject(1);
_testRailRunSelectorWindow.setSuiteID(INTERFACE_SUITE_ID);
}
QString TestRailInterface::getObject(const QString& path) {
return path.right(path.length() - path.lastIndexOf("/") - 1);
}
bool TestRailInterface::setPythonCommand() {
if (QProcessEnvironment::systemEnvironment().contains("PYTHON_PATH")) {
QString _pythonPath = QProcessEnvironment::systemEnvironment().value("PYTHON_PATH");
if (!QFile::exists(_pythonPath + "/" + pythonExe)) {
QMessageBox::critical(0, pythonExe, QString("Python executable not found in ") + _pythonPath);
}
_pythonCommand = _pythonPath + "/" + pythonExe;
return true;
} else {
QMessageBox::critical(0, "PYTHON_PATH not defined",
"Please set PYTHON_PATH to directory containing the Python executable");
return false;
}
return false;
}
// Creates the testrail.py script
// This is the file linked to from http://docs.gurock.com/testrail-api2/bindings-python
void TestRailInterface::createTestRailDotPyScript() {
QFile file(_outputDirectory + "/testrail.py");
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not create 'testrail.py'");
exit(-1);
}
QTextStream stream(&file);
stream << "#\n";
stream << "# TestRail API binding for Python 3.x (API v2, available since \n";
stream << "# TestRail 3.0)\n";
stream << "#\n";
stream << "# Learn more:\n";
stream << "#\n";
stream << "# http://docs.gurock.com/testrail-api2/start\n";
stream << "# http://docs.gurock.com/testrail-api2/accessing\n";
stream << "#\n";
stream << "# Copyright Gurock Software GmbH. See license.md for details.\n";
stream << "#\n";
stream << "\n";
stream << "import urllib.request, urllib.error\n";
stream << "import json, base64\n";
stream << "\n";
stream << "class APIClient:\n";
stream << "\tdef __init__(self, base_url):\n";
stream << "\t\tself.user = ''\n";
stream << "\t\tself.password = ''\n";
stream << "\t\tif not base_url.endswith('/'):\n";
stream << "\t\t\tbase_url += '/'\n";
stream << "\t\tself.__url = base_url + 'index.php?/api/v2/'\n";
stream << "\n";
stream << "\t#\n";
stream << "\t# Send Get\n";
stream << "\t#\n";
stream << "\t# Issues a GET request (read) against the API and returns the result\n";
stream << "\t# (as Python dict).\n";
stream << "\t#\n";
stream << "\t# Arguments:\n";
stream << "\t#\n";
stream << "\t# uri The API method to call including parameters\n";
stream << "\t# (e.g. get_case/1)\n";
stream << "\t#\n";
stream << "\tdef send_get(self, uri):\n";
stream << "\t\treturn self.__send_request('GET', uri, None)\n";
stream << "\n";
stream << "\t#\n";
stream << "\t# Send POST\n";
stream << "\t#\n";
stream << "\t# Issues a POST request (write) against the API and returns the result\n";
stream << "\t# (as Python dict).\n";
stream << "\t#\n";
stream << "\t# Arguments:\n";
stream << "\t#\n";
stream << "\t# uri The API method to call including parameters\n";
stream << "\t# (e.g. add_case/1)\n";
stream << "\t# data The data to submit as part of the request (as\n";
stream << "\t# Python dict, strings must be UTF-8 encoded)\n";
stream << "\t#\n";
stream << "\tdef send_post(self, uri, data):\n";
stream << "\t\treturn self.__send_request('POST', uri, data)\n";
stream << "\n";
stream << "\tdef __send_request(self, method, uri, data):\n";
stream << "\t\turl = self.__url + uri\n";
stream << "\t\trequest = urllib.request.Request(url)\n";
stream << "\t\tif (method == 'POST'):\n";
stream << "\t\t\trequest.data = bytes(json.dumps(data), 'utf-8')\n";
stream << "\t\tauth = str(\n";
stream << "\t\t\tbase64.b64encode(\n";
stream << "\t\t\t\tbytes('%s:%s' % (self.user, self.password), 'utf-8')\n";
stream << "\t\t\t),\n";
stream << "\t\t\t'ascii'\n";
stream << "\t\t).strip()\n";
stream << "\t\trequest.add_header('Authorization', 'Basic %s' % auth)\n";
stream << "\t\trequest.add_header('Content-Type', 'application/json')\n";
stream << "\n";
stream << "\t\te = None\n";
stream << "\t\ttry:\n";
stream << "\t\t\tresponse = urllib.request.urlopen(request).read()\n";
stream << "\t\texcept urllib.error.HTTPError as ex:\n";
stream << "\t\t\tresponse = ex.read()\n";
stream << "\t\t\te = ex\n";
stream << "\n";
stream << "\t\tif response:\n";
stream << "\t\t\tresult = json.loads(response.decode())\n";
stream << "\t\telse:\n";
stream << "\t\t\tresult = {}\n";
stream << "\n";
stream << "\t\tif e != None:\n";
stream << "\t\t\tif result and 'error' in result:\n";
stream << "\t\t\t\terror = '\"' + result['error'] + '\"'\n";
stream << "\t\t\telse:\n";
stream << "\t\t\t\terror = 'No additional error message received'\n";
stream << "\t\t\traise APIError('TestRail API returned HTTP %s (%s)' % \n";
stream << "\t\t\t\t(e.code, error))\n";
stream << "\n";
stream << "\t\treturn result\n";
stream << "\n";
stream << "class APIError(Exception):\n";
stream << "\tpass\n";
file.close();
}
// Creates a Stack class
void TestRailInterface::createStackDotPyScript() {
QString filename = _outputDirectory + "/stack.py";
if (QFile::exists(filename)) {
QFile::remove(filename);
}
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not create 'stack.py'");
exit(-1);
}
QTextStream stream(&file);
stream << "class Stack:\n";
stream << "\tdef __init__(self):\n";
stream << "\t\tself.items = []\n";
stream << "\n";
stream << "\tdef is_empty(self):\n";
stream << "\t\treturn self.items == []\n";
stream << "\n";
stream << "\tdef push(self, item):\n";
stream << "\t\tself.items.append(item)\n";
stream << "\n";
stream << "\tdef pop(self):\n";
stream << "\t\treturn self.items.pop()\n";
stream << "\n";
stream << "\tdef peek(self):\n";
stream << "\t\treturn self.items[len(self.items)-1]\n";
stream << "\n";
stream << "\tdef size(self):\n";
stream << "\t\treturn len(self.items)\n";
stream << "\n";
file.close();
}
void TestRailInterface::requestTestRailTestCasesDataFromUser() {
// Make sure correct fields are enabled before calling
_testRailTestCasesSelectorWindow.reset();
_testRailTestCasesSelectorWindow.exec();
if (_testRailTestCasesSelectorWindow.getUserCancelled()) {
return;
}
_url = _testRailTestCasesSelectorWindow.getURL() + "/";
_user = _testRailTestCasesSelectorWindow.getUser();
_password = _testRailTestCasesSelectorWindow.getPassword();
////_password = "tutKA76";
_projectID = QString::number(_testRailTestCasesSelectorWindow.getProjectID());
_suiteID = QString::number(_testRailTestCasesSelectorWindow.getSuiteID());
}
bool TestRailInterface::isAValidTestDirectory(const QString& directory) {
if (Test::isAValidDirectory(directory)) {
// Ignore the utils and preformance directories
if (directory.right(QString("utils").length()) == "utils" ||
directory.right(QString("performance").length()) == "performance") {
return false;
}
return true;
}
return false;
}
void TestRailInterface::processDirectoryPython(const QString& directory,
QTextStream& stream,
const QString& userGitHub,
const QString& branchGitHub) {
// Loop over all entries in directory
QDirIterator it(directory.toStdString().c_str());
while (it.hasNext()) {
QString nextDirectory = it.next();
QString objectName = getObject(nextDirectory);
if (isAValidTestDirectory(nextDirectory)) {
// The name of the section is the directory at the end of the path
stream << "parent_id = parent_ids.peek()\n";
stream << "data = { 'name': '" << objectName << "', 'suite_id': " + _suiteID + ", 'parent_id': parent_id }\n";
stream << "section = client.send_post('add_section/' + str(" << _projectID << "), data)\n";
// Now we push the parent_id, and recursively process each directory
stream << "parent_ids.push(section['id'])\n\n";
processDirectoryPython(nextDirectory, stream, userGitHub, branchGitHub);
} else if (objectName == "test.js") {
processTestPython(nextDirectory, stream, userGitHub, branchGitHub);
}
}
// pop the parent directory before leaving
stream << "parent_ids.pop()\n\n";
}
// A suite of TestRail test cases contains trees.
// The nodes of the trees are sections
// The leaves are the test cases
//
// Each node and leaf have an ID and a parent ID.
// Therefore, the tree is built top-down, using a stack to store the IDs of each node
//
void TestRailInterface::createAddTestCasesPythonScript(const QString& testDirectory,
const QString& userGitHub,
const QString& branchGitHub) {
QString filename = _outputDirectory + "/addTestCases.py";
if (QFile::exists(filename)) {
QFile::remove(filename);
}
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not create 'addTestCases.py'");
exit(-1);
}
QTextStream stream(&file);
// Code to access TestRail
stream << "from testrail import *\n";
stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n";
stream << "client.user = '" << _user << "'\n";
stream << "client.password = '" << _password << "'\n\n";
stream << "from stack import *\n";
stream << "parent_ids = Stack()\n\n";
// top-level section
stream << "data = { 'name': '"
<< "Test Suite - " << QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm") + "', "
<< "'suite_id': " + _suiteID + "}\n";
stream << "section = client.send_post('add_section/' + str(" << _projectID << "), data)\n";
// Now we push the parent_id, and recursively process each directory
stream << "parent_ids.push(section['id'])\n\n";
processDirectoryPython(testDirectory, stream, userGitHub, branchGitHub);
file.close();
if (QMessageBox::Yes == QMessageBox(QMessageBox::Information, "Python script has been created",
"Do you want to run the script and update TestRail?",
QMessageBox::Yes | QMessageBox::No)
.exec()) {
QProcess* process = new QProcess();
connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); });
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
[=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); });
QStringList parameters = QStringList() << _outputDirectory + "/addTestCases.py";
process->start(_pythonCommand, parameters);
}
}
void TestRailInterface::updateMilestonesComboData(int exitCode, QProcess::ExitStatus exitStatus) {
// Quit if user has previously cancelled
if (_testRailTestCasesSelectorWindow.getUserCancelled()) {
return;
}
// Check if process completed successfully
if (exitStatus != QProcess::NormalExit) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not get milestones from TestRail");
exit(-1);
}
// Create map of milestones from the file created by the process
_milestoneNames.clear();
QString filename = _outputDirectory + "/milestones.txt";
if (!QFile::exists(filename)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not find milestones.txt in " + _outputDirectory);
exit(-1);
}
QFile file(filename);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not open " + _outputDirectory + "/milestones.txt");
exit(-1);
}
QTextStream in(&file);
QString line = in.readLine();
while (!line.isNull()) {
QStringList words = line.split(' ');
_milestones[words[0]] = words[1].toInt();
_milestoneNames << words[0];
line = in.readLine();
}
file.close();
// Update the combo
_testRailTestCasesSelectorWindow.updateMilestonesComboBoxData(_milestoneNames);
_testRailTestCasesSelectorWindow.exec();
if (_testRailTestCasesSelectorWindow.getUserCancelled()) {
return;
}
createAddTestCasesPythonScript(_testDirectory, _userGitHub, _branchGitHub);
}
void TestRailInterface::updateSectionsComboData(int exitCode, QProcess::ExitStatus exitStatus) {
// Quit if user has previously cancelled
if (_testRailRunSelectorWindow.getUserCancelled()) {
return;
}
// Check if process completed successfully
if (exitStatus != QProcess::NormalExit) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not get sections from TestRail");
exit(-1);
}
// Create map of sections from the file created by the process
_sectionNames.clear();
QString filename = _outputDirectory + "/sections.txt";
if (!QFile::exists(filename)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not find sections.txt in " + _outputDirectory);
exit(-1);
}
QFile file(filename);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not open " + _outputDirectory + "/sections.txt");
exit(-1);
}
QTextStream in(&file);
QString line = in.readLine();
while (!line.isNull()) {
// The section name is all the words except for the last
// The id is the last word
QString section = line.left(line.lastIndexOf(" "));
QString id = line.right(line.length() - line.lastIndexOf(" ") - 1);
_sections[section] = id.toInt();
_sectionNames << section;
line = in.readLine();
}
file.close();
// Update the combo
_testRailRunSelectorWindow.updateSectionsComboBoxData(_sectionNames);
_testRailRunSelectorWindow.exec();
if (_testRailRunSelectorWindow.getUserCancelled()) {
return;
}
////createAddTestCasesPythonScript(_testDirectory, _userGitHub, _branchGitHub);
}
void TestRailInterface::getMilestonesFromTestRail() {
QString filename = _outputDirectory + "/getMilestones.py";
if (QFile::exists(filename)) {
QFile::remove(filename);
}
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not create 'getMilestones.py'");
exit(-1);
}
QTextStream stream(&file);
// Code to access TestRail
stream << "from testrail import *\n";
stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n";
stream << "client.user = '" << _user << "'\n";
stream << "client.password = '" << _password << "'\n\n";
// Print the list of uncompleted milestones
stream << "file = open('" + _outputDirectory + "/milestones.txt', 'w')\n\n";
stream << "milestones = client.send_get('get_milestones/" + _projectID + "')\n";
stream << "for milestone in milestones:\n";
stream << "\tif milestone['is_completed'] == False:\n";
stream << "\t\tfile.write(milestone['name'] + ' ' + str(milestone['id']) + '\\n')\n\n";
stream << "file.close()\n";
file.close();
QProcess* process = new QProcess();
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
[=](int exitCode, QProcess::ExitStatus exitStatus) { updateMilestonesComboData(exitCode, exitStatus); });
QStringList parameters = QStringList() << _outputDirectory + "/getMilestones.py ";
process->start(_pythonCommand, parameters);
}
void TestRailInterface::createTestSuitePython(const QString& testDirectory,
const QString& outputDirectory,
const QString& userGitHub,
const QString& branchGitHub) {
_testDirectory = testDirectory;
_outputDirectory = outputDirectory;
_userGitHub = userGitHub;
_branchGitHub = branchGitHub;
if (!setPythonCommand()) {
return;
}
requestTestRailTestCasesDataFromUser();
createTestRailDotPyScript();
createStackDotPyScript();
// TestRail will be updated after the process initiated by getMilestonesFromTestRail has completed
getMilestonesFromTestRail();
}
void TestRailInterface::createTestSuiteXML(const QString& testDirectory,
const QString& outputDirectory,
const QString& userGitHub,
const QString& branchGitHub) {
_outputDirectory = outputDirectory;
QDomProcessingInstruction instruction = _document.createProcessingInstruction("xml", "version='1.0' encoding='UTF-8'");
_document.appendChild(instruction);
// We create a single section, within sections
QDomElement root = _document.createElement("sections");
_document.appendChild(root);
QDomElement topLevelSection = _document.createElement("section");
QDomElement suiteName = _document.createElement("name");
suiteName.appendChild(
_document.createTextNode("Test Suite - " + QDateTime::currentDateTime().toString("yyyy-MM-ddTHH:mm")));
topLevelSection.appendChild(suiteName);
// This is the first call to 'process'. This is then called recursively to build the full XML tree
QDomElement secondLevelSections = _document.createElement("sections");
topLevelSection.appendChild(processDirectoryXML(testDirectory, userGitHub, branchGitHub, secondLevelSections));
topLevelSection.appendChild(secondLevelSections);
root.appendChild(topLevelSection);
// Write to file
const QString testRailsFilename{ _outputDirectory + "/TestRailSuite.xml" };
QFile file(testRailsFilename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not create XML file");
exit(-1);
}
QTextStream stream(&file);
stream << _document.toString();
file.close();
QMessageBox::information(0, "Success", "TestRail XML file has been created");
}
QDomElement TestRailInterface::processDirectoryXML(const QString& directory,
const QString& userGitHub,
const QString& branchGitHub,
const QDomElement& element) {
QDomElement result = element;
// Loop over all entries in directory
QDirIterator it(directory.toStdString().c_str());
while (it.hasNext()) {
QString nextDirectory = it.next();
// The object name appears after the last slash (we are assured there is at least 1).
QString objectName = getObject(nextDirectory);
// Only process directories
if (isAValidTestDirectory(nextDirectory)) {
// Create a section and process it
QDomElement sectionElement = _document.createElement("section");
QDomElement sectionElementName = _document.createElement("name");
sectionElementName.appendChild(_document.createTextNode(objectName));
sectionElement.appendChild(sectionElementName);
QDomElement testsElement = _document.createElement("sections");
sectionElement.appendChild(processDirectoryXML(nextDirectory, userGitHub, branchGitHub, testsElement));
result.appendChild(sectionElement);
} else if (objectName == "test.js" || objectName == "testStory.js") {
QDomElement sectionElement = _document.createElement("section");
QDomElement sectionElementName = _document.createElement("name");
sectionElementName.appendChild(_document.createTextNode("all"));
sectionElement.appendChild(sectionElementName);
sectionElement.appendChild(
processTestXML(nextDirectory, objectName, userGitHub, branchGitHub, _document.createElement("cases")));
result.appendChild(sectionElement);
}
}
return result;
}
QDomElement TestRailInterface::processTestXML(const QString& fullDirectory,
const QString& test,
const QString& userGitHub,
const QString& branchGitHub,
const QDomElement& element) {
QDomElement result = element;
QDomElement caseElement = _document.createElement("case");
caseElement.appendChild(_document.createElement("id"));
// The name of the test is derived from the full path.
// The first term is the first word after "tests"
// The last word is the penultimate word
QStringList words = fullDirectory.split('/');
int i = 0;
while (words[i] != "tests") {
++i;
if (i >= words.length() - 1) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Folder \"tests\" not found in " + fullDirectory);
exit(-1);
}
}
++i;
QString title{ words[i] };
for (++i; i < words.length() - 1; ++i) {
title += " / " + words[i];
}
QDomElement titleElement = _document.createElement("title");
titleElement.appendChild(_document.createTextNode(title));
caseElement.appendChild(titleElement);
QDomElement templateElement = _document.createElement("template");
templateElement.appendChild(_document.createTextNode("Test Case (Steps)"));
caseElement.appendChild(templateElement);
QDomElement typeElement = _document.createElement("type");
typeElement.appendChild(_document.createTextNode("3 - Regression"));
caseElement.appendChild(typeElement);
QDomElement priorityElement = _document.createElement("priority");
priorityElement.appendChild(_document.createTextNode("Medium"));
caseElement.appendChild(priorityElement);
QDomElement estimateElementName = _document.createElement("estimate");
estimateElementName.appendChild(_document.createTextNode("60"));
caseElement.appendChild(estimateElementName);
caseElement.appendChild(_document.createElement("references"));
QDomElement customElement = _document.createElement("custom");
QDomElement tester_countElement = _document.createElement("tester_count");
tester_countElement.appendChild(_document.createTextNode("1"));
customElement.appendChild(tester_countElement);
QDomElement domain_bot_loadElement = _document.createElement("domain_bot_load");
QDomElement domain_bot_loadElementId = _document.createElement("id");
domain_bot_loadElementId.appendChild(_document.createTextNode("1"));
domain_bot_loadElement.appendChild(domain_bot_loadElementId);
QDomElement domain_bot_loadElementValue = _document.createElement("value");
domain_bot_loadElementValue.appendChild(
_document.createTextNode(" Without Bots (hifiqa-rc / hifi-qa-stable / hifiqa-master)"));
domain_bot_loadElement.appendChild(domain_bot_loadElementValue);
customElement.appendChild(domain_bot_loadElement);
QDomElement automation_typeElement = _document.createElement("automation_type");
QDomElement automation_typeElementId = _document.createElement("id");
automation_typeElementId.appendChild(_document.createTextNode("0"));
automation_typeElement.appendChild(automation_typeElementId);
QDomElement automation_typeElementValue = _document.createElement("value");
automation_typeElementValue.appendChild(_document.createTextNode("None"));
automation_typeElement.appendChild(automation_typeElementValue);
customElement.appendChild(automation_typeElement);
QDomElement added_to_releaseElement = _document.createElement("added_to_release");
QDomElement added_to_releaseElementId = _document.createElement("id");
added_to_releaseElementId.appendChild(_document.createTextNode("4"));
added_to_releaseElement.appendChild(added_to_releaseElementId);
QDomElement added_to_releaseElementValue = _document.createElement("value");
added_to_releaseElementValue.appendChild(_document.createTextNode(branchGitHub));
added_to_releaseElement.appendChild(added_to_releaseElementValue);
customElement.appendChild(added_to_releaseElement);
QDomElement precondsElement = _document.createElement("preconds");
precondsElement.appendChild(_document.createTextNode(
"Tester is in an empty region of a domain in which they have edit rights\n\n*Note: Press 'n' to advance test script"));
customElement.appendChild(precondsElement);
QString testMDName = QString("https://github.com/") + userGitHub + "/hifi_tests/blob/" + branchGitHub +
"/tests/content/entity/light/point/create/test.md";
QDomElement steps_seperatedElement = _document.createElement("steps_separated");
QDomElement stepElement = _document.createElement("step");
QDomElement stepIndexElement = _document.createElement("index");
stepIndexElement.appendChild(_document.createTextNode("1"));
stepElement.appendChild(stepIndexElement);
QDomElement stepContentElement = _document.createElement("content");
stepContentElement.appendChild(
_document.createTextNode(QString("Execute instructions in [THIS TEST](") + testMDName + ")"));
stepElement.appendChild(stepContentElement);
QDomElement stepExpectedElement = _document.createElement("expected");
stepExpectedElement.appendChild(_document.createTextNode("Refer to the expected result in the linked description."));
stepElement.appendChild(stepExpectedElement);
steps_seperatedElement.appendChild(stepElement);
customElement.appendChild(steps_seperatedElement);
QDomElement notesElement = _document.createElement("notes");
notesElement.appendChild(_document.createTextNode(testMDName));
customElement.appendChild(notesElement);
caseElement.appendChild(customElement);
result.appendChild(caseElement);
return result;
}
void TestRailInterface::processTestPython(const QString& fullDirectory,
QTextStream& stream,
const QString& userGitHub,
const QString& branchGitHub) {
// The name of the test is derived from the full path.
// The first term is the first word after "tests"
// The last word is the penultimate word
QStringList words = fullDirectory.split('/');
int i = 0;
while (words[i] != "tests") {
++i;
if (i >= words.length() - 1) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Folder \"tests\" not found in " + fullDirectory);
exit(-1);
}
}
++i;
QString title{ words[i] };
for (++i; i < words.length() - 1; ++i) {
title += " / " + words[i];
}
// To create the path to test.md, prefix by tests, and remove blanks
QString pathToTestMD = QString("/tests/") + title.remove(" ");
stream << "section_id = parent_ids.peek()\n";
QString testMDName =
QString("https://github.com/") + userGitHub + "/hifi_tests/blob/" + branchGitHub + pathToTestMD + "/test.md ";
QString testContent = QString("Execute instructions in [THIS TEST](") + testMDName + ")";
QString testExpected = QString("Refer to the expected result in the linked description.");
int milestone_id = _milestones[_milestoneNames[_testRailTestCasesSelectorWindow.getMilestoneID()]];
stream << "data = {\n"
<< "\t'title': '" << title << "',\n"
<< "\t'template_id': 2,\n"
<< "\t'milestone_id': " << milestone_id << ",\n"
<< "\t'custom_tester_count': 1,\n"
<< "\t'custom_domain_bot_load': 1,\n"
<< "\t'custom_added_to_release': 4,\n"
<< "\t'custom_preconds': "
<< "'Tester is in an empty region of a domain in which they have edit rights\\n\\n*Note: Press \\'n\\' to advance "
"test script',\n"
<< "\t'custom_steps_separated': "
<< "[\n\t\t{\n\t\t\t'content': '" << testContent << "',\n\t\t\t'expected': '" << testExpected << "'\n\t\t}\n\t]\n"
<< "}\n";
stream << "case = client.send_post('add_case/' + str(section_id), data)\n";
}
void TestRailInterface::requestTestRailRunDataFromUser() {
_testRailRunSelectorWindow.reset();
_testRailRunSelectorWindow.exec();
if (_testRailRunSelectorWindow.getUserCancelled()) {
return;
}
_url = _testRailRunSelectorWindow.getURL() + "/";
_user = _testRailRunSelectorWindow.getUser();
_password = _testRailRunSelectorWindow.getPassword();
////_password = "tutKA76";
_projectID = QString::number(_testRailRunSelectorWindow.getProjectID());
_suiteID = QString::number(_testRailRunSelectorWindow.getSuiteID());
}
void TestRailInterface::getTestSectionsFromTestRail() {
QString filename = _outputDirectory + "/getSections.py";
if (QFile::exists(filename)) {
QFile::remove(filename);
}
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),
"Could not create 'getSections.py'");
exit(-1);
}
QTextStream stream(&file);
// Code to access TestRail
stream << "from testrail import *\n";
stream << "client = APIClient('" << _url.toStdString().c_str() << "')\n";
stream << "client.user = '" << _user << "'\n";
stream << "client.password = '" << _password << "'\n\n";
// Print the list of sections without parents
stream << "sections = client.send_get('get_sections/" + _projectID + "&suite_id=" + _suiteID + "')\n\n";
stream << "file = open('" + _outputDirectory + "/sections.txt', 'w')\n\n";
stream << "for section in sections:\n";
stream << "\tif section['parent_id'] == None:\n";
stream << "\t\tfile.write(section['name'] + ' ' + str(section['id']) + '\\n')\n\n";
stream << "file.close()\n";
file.close();
QProcess* process = new QProcess();
connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this,
[=](int exitCode, QProcess::ExitStatus exitStatus) { updateSectionsComboData(exitCode, exitStatus); });
QStringList parameters = QStringList() << _outputDirectory + "/getSections.py ";
process->start(_pythonCommand, parameters);
}
void TestRailInterface::createTestRailRun(const QString& outputDirectory) {
_outputDirectory = outputDirectory;
if (!setPythonCommand()) {
return;
}
requestTestRailRunDataFromUser();
createTestRailDotPyScript();
createStackDotPyScript();
// TestRail will be updated after the process initiated by getTestCasesFromTestRail has completed
getTestSectionsFromTestRail();
}

View file

@ -0,0 +1,119 @@
//
// TestRailInterface.h
//
// Created by Nissim Hadar on 6 Jul 2018.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_test_testrail_interface_h
#define hifi_test_testrail_interface_h
#include "ui/BusyWindow.h"
#include "ui/TestRailTestCasesSelectorWindow.h"
#include "ui/TestRailRunSelectorWindow.h"
#include <QDirIterator>
#include <QtXml/QDomDocument>
#include <QProcess>
#include <QString>
class TestRailInterface : public QObject{
Q_OBJECT
public:
TestRailInterface();
void createTestSuiteXML(const QString& testDirectory,
const QString& outputDirectory,
const QString& userGitHub,
const QString& branchGitHub);
void createTestSuitePython(const QString& testDirectory,
const QString& outputDirectory,
const QString& userGitHub,
const QString& branchGitHub);
QDomElement processDirectoryXML(const QString& directory,
const QString& useGitHubr,
const QString& branchGitHub,
const QDomElement& element);
QDomElement processTestXML(const QString& fullDirectory,
const QString& test,
const QString& userGitHub,
const QString& branchGitHub,
const QDomElement& element);
void processTestPython(const QString& fullDirectory,
QTextStream& stream,
const QString& userGitHub,
const QString& branchGitHub);
void getMilestonesFromTestRail();
void getTestSectionsFromTestRail();
void createTestRailDotPyScript();
void createStackDotPyScript();
void requestTestRailTestCasesDataFromUser();
void requestTestRailRunDataFromUser();
void createAddTestCasesPythonScript(const QString& testDirectory,
const QString& userGitHub,
const QString& branchGitHub);
void processDirectoryPython(const QString& directory,
QTextStream& stream,
const QString& userGitHub,
const QString& branchGitHub);
bool isAValidTestDirectory(const QString& directory);
QString getObject(const QString& path);
void updateMilestonesComboData(int exitCode, QProcess::ExitStatus exitStatus);
void updateSectionsComboData(int exitCode, QProcess::ExitStatus exitStatus);
void createTestRailRun(const QString& outputDirectory);
bool setPythonCommand();
private:
// HighFidelity Interface project ID in TestRail
const int INTERFACE_PROJECT_ID{ 24 };
// Rendering suite ID
const int INTERFACE_SUITE_ID{ 1147 };
QDomDocument _document;
BusyWindow _busyWindow;
TestRailTestCasesSelectorWindow _testRailTestCasesSelectorWindow;
TestRailRunSelectorWindow _testRailRunSelectorWindow;
QString _url;
QString _user;
QString _password;
QString _projectID;
QString _suiteID;
QString _testDirectory;
QString _outputDirectory;
QString _userGitHub;
QString _branchGitHub;
const QString pythonExe{ "python.exe" };
QString _pythonCommand;
std::map<QString, int> _milestones;
QStringList _milestoneNames;
std::map<QString, int> _sections;
QStringList _sectionNames;
};
#endif

View file

@ -16,56 +16,64 @@
#endif
AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) {
ui.setupUi(this);
ui.checkBoxInteractiveMode->setChecked(true);
ui.progressBar->setVisible(false);
_ui.setupUi(this);
_ui.checkBoxInteractiveMode->setChecked(true);
_ui.progressBar->setVisible(false);
signalMapper = new QSignalMapper();
_signalMapper = new QSignalMapper();
connect(ui.actionClose, &QAction::triggered, this, &AutoTester::on_closeButton_clicked);
connect(ui.actionAbout, &QAction::triggered, this, &AutoTester::about);
connect(_ui.actionClose, &QAction::triggered, this, &AutoTester::on_closeButton_clicked);
connect(_ui.actionAbout, &QAction::triggered, this, &AutoTester::about);
#ifndef Q_OS_WIN
ui.hideTaskbarButton->setVisible(false);
ui.showTaskbarButton->setVisible(false);
_ui.hideTaskbarButton->setVisible(false);
_ui.showTaskbarButton->setVisible(false);
#endif
}
void AutoTester::setup() {
test = new Test();
_test = new Test();
}
void AutoTester::runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user) {
isRunningFromCommandline = true;
test->startTestsEvaluation(testFolder, branch, user);
_isRunningFromCommandline = true;
_test->startTestsEvaluation(testFolder, branch, user);
}
void AutoTester::on_evaluateTestsButton_clicked() {
test->startTestsEvaluation();
_test->startTestsEvaluation();
}
void AutoTester::on_createRecursiveScriptButton_clicked() {
test->createRecursiveScript();
_test->createRecursiveScript();
}
void AutoTester::on_createAllRecursiveScriptsButton_clicked() {
test->createAllRecursiveScripts();
_test->createAllRecursiveScripts();
}
void AutoTester::on_createTestsButton_clicked() {
test->createTests();
_test->createTests();
}
void AutoTester::on_createMDFileButton_clicked() {
test->createMDFile();
_test->createMDFile();
}
void AutoTester::on_createAllMDFilesButton_clicked() {
test->createAllMDFiles();
_test->createAllMDFiles();
}
void AutoTester::on_createTestsOutlineButton_clicked() {
test->createTestsOutline();
_test->createTestsOutline();
}
void AutoTester::on_createTestRailTestCasesButton_clicked() {
_test->createTestRailTestCases();
}
void AutoTester::on_createTestRailRunButton_clicked() {
_test->createTestRailRun();
}
// To toggle between show and hide
@ -96,11 +104,19 @@ void AutoTester::on_closeButton_clicked() {
exit(0);
}
void AutoTester::downloadImage(const QUrl& url) {
downloaders.emplace_back(new Downloader(url, this));
connect(downloaders[_index], SIGNAL (downloaded()), signalMapper, SLOT (map()));
void AutoTester::on_createPythonScriptRadioButton_clicked() {
_test->setTestRailCreateMode(PYTHON);
}
signalMapper->setMapping(downloaders[_index], _index);
void AutoTester::on_createXMLScriptRadioButton_clicked() {
_test->setTestRailCreateMode(XML);
}
void AutoTester::downloadImage(const QUrl& url) {
_downloaders.emplace_back(new Downloader(url, this));
connect(_downloaders[_index], SIGNAL (downloaded()), _signalMapper, SLOT (map()));
_signalMapper->setMapping(_downloaders[_index], _index);
++_index;
}
@ -113,39 +129,39 @@ void AutoTester::downloadImages(const QStringList& URLs, const QString& director
_numberOfImagesDownloaded = 0;
_index = 0;
ui.progressBar->setMinimum(0);
ui.progressBar->setMaximum(_numberOfImagesToDownload - 1);
ui.progressBar->setValue(0);
ui.progressBar->setVisible(true);
_ui.progressBar->setMinimum(0);
_ui.progressBar->setMaximum(_numberOfImagesToDownload - 1);
_ui.progressBar->setValue(0);
_ui.progressBar->setVisible(true);
downloaders.clear();
_downloaders.clear();
for (int i = 0; i < _numberOfImagesToDownload; ++i) {
QUrl imageURL(URLs[i]);
downloadImage(imageURL);
}
connect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int)));
connect(_signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int)));
}
void AutoTester::saveImage(int index) {
try {
QFile file(_directoryName + "/" + _filenames[index]);
file.open(QIODevice::WriteOnly);
file.write(downloaders[index]->downloadedData());
file.write(_downloaders[index]->downloadedData());
file.close();
} catch (...) {
QMessageBox::information(0, "Test Aborted", "Failed to save image: " + _filenames[index]);
ui.progressBar->setVisible(false);
_ui.progressBar->setVisible(false);
return;
}
++_numberOfImagesDownloaded;
if (_numberOfImagesDownloaded == _numberOfImagesToDownload) {
disconnect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int)));
test->finishTestsEvaluation(isRunningFromCommandline, ui.checkBoxInteractiveMode->isChecked(), ui.progressBar);
disconnect(_signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int)));
_test->finishTestsEvaluation(_isRunningFromCommandline, _ui.checkBoxInteractiveMode->isChecked(), _ui.progressBar);
} else {
ui.progressBar->setValue(_numberOfImagesDownloaded);
_ui.progressBar->setValue(_numberOfImagesDownloaded);
}
}
@ -154,18 +170,18 @@ void AutoTester::about() {
}
void AutoTester::setUserText(const QString& user) {
ui.userTextEdit->setText(user);
_ui.userTextEdit->setText(user);
}
QString AutoTester::getSelectedUser()
{
return ui.userTextEdit->toPlainText();
return _ui.userTextEdit->toPlainText();
}
void AutoTester::setBranchText(const QString& branch) {
ui.branchTextEdit->setText(branch);
_ui.branchTextEdit->setText(branch);
}
QString AutoTester::getSelectedBranch() {
return ui.branchTextEdit->toPlainText();
return _ui.branchTextEdit->toPlainText();
}

View file

@ -45,10 +45,15 @@ private slots:
void on_createMDFileButton_clicked();
void on_createAllMDFilesButton_clicked();
void on_createTestsOutlineButton_clicked();
void on_createTestRailTestCasesButton_clicked();
void on_createTestRailRunButton_clicked();
void on_hideTaskbarButton_clicked();
void on_showTaskbarButton_clicked();
void on_createPythonScriptRadioButton_clicked();
void on_createXMLScriptRadioButton_clicked();
void on_closeButton_clicked();
void saveImage(int index);
@ -56,23 +61,23 @@ private slots:
void about();
private:
Ui::AutoTesterClass ui;
Test* test;
Ui::AutoTesterClass _ui;
Test* _test;
std::vector<Downloader*> downloaders;
std::vector<Downloader*> _downloaders;
// local storage for parameters - folder to store downloaded files in, and a list of their names
QString _directoryName;
QStringList _filenames;
// Used to enable passing a parameter to slots
QSignalMapper* signalMapper;
QSignalMapper* _signalMapper;
int _numberOfImagesToDownload { 0 };
int _numberOfImagesDownloaded { 0 };
int _index { 0 };
bool isRunningFromCommandline { false };
bool _isRunningFromCommandline { false };
};
#endif // hifi_AutoTester_h

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>612</width>
<height>537</height>
<width>645</width>
<height>814</height>
</rect>
</property>
<property name="windowTitle">
@ -18,7 +18,7 @@
<property name="geometry">
<rect>
<x>380</x>
<y>430</y>
<y>620</y>
<width>101</width>
<height>40</height>
</rect>
@ -44,7 +44,7 @@
<property name="geometry">
<rect>
<x>430</x>
<y>270</y>
<y>490</y>
<width>101</width>
<height>40</height>
</rect>
@ -57,7 +57,7 @@
<property name="geometry">
<rect>
<x>330</x>
<y>110</y>
<y>340</y>
<width>220</width>
<height>40</height>
</rect>
@ -70,7 +70,7 @@
<property name="geometry">
<rect>
<x>320</x>
<y>280</y>
<y>500</y>
<width>131</width>
<height>20</height>
</rect>
@ -86,7 +86,7 @@
<property name="geometry">
<rect>
<x>320</x>
<y>330</y>
<y>550</y>
<width>255</width>
<height>23</height>
</rect>
@ -99,7 +99,7 @@
<property name="geometry">
<rect>
<x>330</x>
<y>170</y>
<y>400</y>
<width>220</width>
<height>40</height>
</rect>
@ -229,13 +229,68 @@
</rect>
</property>
</widget>
<widget class="QPushButton" name="createTestRailTestCasesButton">
<property name="geometry">
<rect>
<x>410</x>
<y>100</y>
<width>140</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create TestRail Test Cases</string>
</property>
</widget>
<widget class="QRadioButton" name="createPythonScriptRadioButton">
<property name="geometry">
<rect>
<x>310</x>
<y>100</y>
<width>95</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>Python</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
<widget class="QRadioButton" name="createXMLScriptRadioButton">
<property name="geometry">
<rect>
<x>310</x>
<y>120</y>
<width>95</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>XML</string>
</property>
</widget>
<widget class="QPushButton" name="createTestRailRunButton">
<property name="geometry">
<rect>
<x>410</x>
<y>180</y>
<width>140</width>
<height>40</height>
</rect>
</property>
<property name="text">
<string>Create TestRail Run</string>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>612</width>
<width>645</width>
<height>21</height>
</rect>
</property>

View file

@ -0,0 +1,18 @@
//
// BusyWindow.cpp
//
// Created by Nissim Hadar on 26 Jul 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "BusyWindow.h"
#include <QtCore/QFileInfo>
#include <cmath>
BusyWindow::BusyWindow(QWidget *parent) {
setupUi(this);
}

View file

@ -0,0 +1,22 @@
//
// BusyWindow.h
//
// Created by Nissim Hadar on 29 Jul 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_BusyWindow_h
#define hifi_BusyWindow_h
#include "ui_BusyWindow.h"
class BusyWindow : public QDialog, public Ui::BusyWindow {
Q_OBJECT
public:
BusyWindow(QWidget* parent = Q_NULLPTR);
};
#endif

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BusyWindow</class>
<widget class="QDialog" name="BusyWindow">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>542</width>
<height>189</height>
</rect>
</property>
<property name="windowTitle">
<string>Updating TestRail - please wait</string>
</property>
<widget class="QLabel" name="errorLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>850</y>
<width>500</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>similarity</string>
</property>
</widget>
<widget class="QProgressBar" name="progressBar">
<property name="geometry">
<rect>
<x>40</x>
<y>40</y>
<width>481</width>
<height>101</height>
</rect>
</property>
<property name="maximum">
<number>0</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>50</x>
<y>60</y>
<width>431</width>
<height>61</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>20</pointsize>
</font>
</property>
<property name="text">
<string>Please wait for this window to close</string>
</property>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

View file

@ -66,14 +66,14 @@ void MismatchWindow::setTestFailure(TestFailure testFailure) {
QPixmap expectedPixmap = QPixmap(testFailure._pathname + testFailure._expectedImageFilename);
QPixmap actualPixmap = QPixmap(testFailure._pathname + testFailure._actualImageFilename);
diffPixmap = computeDiffPixmap(
_diffPixmap = computeDiffPixmap(
QImage(testFailure._pathname + testFailure._expectedImageFilename),
QImage(testFailure._pathname + testFailure._actualImageFilename)
);
expectedImage->setPixmap(expectedPixmap);
resultImage->setPixmap(actualPixmap);
diffImage->setPixmap(diffPixmap);
diffImage->setPixmap(_diffPixmap);
}
void MismatchWindow::on_passTestButton_clicked() {
@ -92,5 +92,5 @@ void MismatchWindow::on_abortTestsButton_clicked() {
}
QPixmap MismatchWindow::getComparisonImage() {
return diffPixmap;
return _diffPixmap;
}

View file

@ -14,8 +14,7 @@
#include "../common.h"
class MismatchWindow : public QDialog, public Ui::MismatchWindow
{
class MismatchWindow : public QDialog, public Ui::MismatchWindow {
Q_OBJECT
public:
@ -36,7 +35,7 @@ private slots:
private:
UserResponse _userResponse{ USER_RESPONSE_INVALID };
QPixmap diffPixmap;
QPixmap _diffPixmap;
};

View file

@ -2,6 +2,9 @@
<ui version="4.0">
<class>MismatchWindow</class>
<widget class="QDialog" name="MismatchWindow">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
@ -193,4 +196,4 @@
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
</ui>

View file

@ -0,0 +1,101 @@
//
// TestRailRunSelectorWindow.cpp
//
// Created by Nissim Hadar on 31 Jul 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TestRailRunSelectorWindow.h"
#include <QtCore/QFileInfo>
#include <cmath>
TestRailRunSelectorWindow::TestRailRunSelectorWindow(QWidget *parent) {
setupUi(this);
projectIDLineEdit->setValidator(new QIntValidator(1, 999, this));
}
void TestRailRunSelectorWindow::reset() {
urlLineEdit->setDisabled(false);
userLineEdit->setDisabled(false);
passwordLineEdit->setDisabled(false);
projectIDLineEdit->setDisabled(false);
OKButton->setDisabled(true);
sectionsComboBox->setDisabled(true);
}
void TestRailRunSelectorWindow::on_acceptButton_clicked() {
urlLineEdit->setDisabled(true);
userLineEdit->setDisabled(true);
passwordLineEdit->setDisabled(true);
projectIDLineEdit->setDisabled(true);
OKButton->setDisabled(false);
sectionsComboBox->setDisabled(false);
close();
}
void TestRailRunSelectorWindow::on_OKButton_clicked() {
userCancelled = false;
close();
}
void TestRailRunSelectorWindow::on_cancelButton_clicked() {
userCancelled = true;
close();
}
bool TestRailRunSelectorWindow::getUserCancelled() {
return userCancelled;
}
void TestRailRunSelectorWindow::setURL(const QString& user) {
urlLineEdit->setText(user);
}
QString TestRailRunSelectorWindow::getURL() {
return urlLineEdit->text();
}
void TestRailRunSelectorWindow::setUser(const QString& user) {
userLineEdit->setText(user);
}
QString TestRailRunSelectorWindow::getUser() {
return userLineEdit->text();
}
QString TestRailRunSelectorWindow::getPassword() {
return passwordLineEdit->text();
}
void TestRailRunSelectorWindow::setProjectID(const int project) {
projectIDLineEdit->setText(QString::number(project));
}
int TestRailRunSelectorWindow::getProjectID() {
return projectIDLineEdit->text().toInt();
}
void TestRailRunSelectorWindow::setSuiteID(const int project) {
suiteIDLineEdit->setText(QString::number(project));
}
int TestRailRunSelectorWindow::getSuiteID() {
return suiteIDLineEdit->text().toInt();
}
void TestRailRunSelectorWindow::updateSectionsComboBoxData(QStringList data) {
sectionsComboBox->insertItems(0, data);
}
int TestRailRunSelectorWindow::getSectionID() {
return 0;
sectionsComboBox->currentIndex();
}

View file

@ -0,0 +1,50 @@
//
// TestRailRunSelectorWindow.h
//
// Created by Nissim Hadar on 31 Jul 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_TestRailRunSelectorWindow_h
#define hifi_TestRailRunSelectorWindow_h
#include "ui_TestRailRunSelectorWindow.h"
class TestRailRunSelectorWindow : public QDialog, public Ui::TestRailRunSelectorWindow {
Q_OBJECT
public:
TestRailRunSelectorWindow(QWidget* parent = Q_NULLPTR);
void reset();
bool getUserCancelled();
void setURL(const QString& user);
QString getURL();
void setUser(const QString& user);
QString getUser();
QString getPassword();
void setProjectID(const int project);
int getProjectID();
void setSuiteID(const int project);
int getSuiteID();
bool userCancelled{ false };
void updateSectionsComboBoxData(QStringList data);
int getSectionID();
private slots:
void on_acceptButton_clicked();
void on_OKButton_clicked();
void on_cancelButton_clicked();
};
#endif

View file

@ -0,0 +1,283 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TestRailRunSelectorWindow</class>
<widget class="QDialog" name="TestRailRunSelectorWindow">
<property name="windowModality">
<enum>Qt::ApplicationModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>489</width>
<height>474</height>
</rect>
</property>
<property name="windowTitle">
<string>TestRail Run Selector Window</string>
</property>
<widget class="QLabel" name="errorLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>850</y>
<width>500</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>similarity</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>70</x>
<y>125</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail Password</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>70</x>
<y>25</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail URL</string>
</property>
</widget>
<widget class="QPushButton" name="OKButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>120</x>
<y>420</y>
<width>93</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>OK</string>
</property>
</widget>
<widget class="QPushButton" name="cancelButton">
<property name="geometry">
<rect>
<x>280</x>
<y>420</y>
<width>93</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
<widget class="QLineEdit" name="passwordLineEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>120</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>70</x>
<y>75</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail User</string>
</property>
</widget>
<widget class="QLineEdit" name="projectIDLineEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>170</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Normal</enum>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>70</x>
<y>175</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail Project ID</string>
</property>
</widget>
<widget class="QPushButton" name="acceptButton">
<property name="geometry">
<rect>
<x>200</x>
<y>270</y>
<width>231</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>Accept</string>
</property>
</widget>
<widget class="QComboBox" name="sectionsComboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>140</x>
<y>350</y>
<width>311</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="milestoneLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>20</x>
<y>350</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail Sections</string>
</property>
</widget>
<widget class="QLineEdit" name="urlLineEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>20</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Normal</enum>
</property>
</widget>
<widget class="QLineEdit" name="userLineEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>70</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Normal</enum>
</property>
</widget>
<widget class="QLineEdit" name="suiteIDLineEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>215</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Normal</enum>
</property>
</widget>
<widget class="QLabel" name="label_5">
<property name="geometry">
<rect>
<x>70</x>
<y>220</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail Suite ID</string>
</property>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<tabstops>
<tabstop>urlLineEdit</tabstop>
<tabstop>userLineEdit</tabstop>
<tabstop>passwordLineEdit</tabstop>
<tabstop>projectIDLineEdit</tabstop>
<tabstop>suiteIDLineEdit</tabstop>
<tabstop>acceptButton</tabstop>
<tabstop>sectionsComboBox</tabstop>
<tabstop>OKButton</tabstop>
<tabstop>cancelButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,100 @@
//
// TestRailTestCasesSelectorWindow.cpp
//
// Created by Nissim Hadar on 26 Jul 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TestRailTestCasesSelectorWindow.h"
#include <QtCore/QFileInfo>
#include <cmath>
TestRailTestCasesSelectorWindow::TestRailTestCasesSelectorWindow(QWidget *parent) {
setupUi(this);
projectIDLineEdit->setValidator(new QIntValidator(1, 999, this));
}
void TestRailTestCasesSelectorWindow::reset() {
urlLineEdit->setDisabled(false);
userLineEdit->setDisabled(false);
passwordLineEdit->setDisabled(false);
projectIDLineEdit->setDisabled(false);
OKButton->setDisabled(true);
milestonesComboBox->setDisabled(true);
}
void TestRailTestCasesSelectorWindow::on_acceptButton_clicked() {
urlLineEdit->setDisabled(true);
userLineEdit->setDisabled(true);
passwordLineEdit->setDisabled(true);
projectIDLineEdit->setDisabled(true);
OKButton->setDisabled(false);
milestonesComboBox->setDisabled(false);
close();
}
void TestRailTestCasesSelectorWindow::on_OKButton_clicked() {
userCancelled = false;
close();
}
void TestRailTestCasesSelectorWindow::on_cancelButton_clicked() {
userCancelled = true;
close();
}
bool TestRailTestCasesSelectorWindow::getUserCancelled() {
return userCancelled;
}
void TestRailTestCasesSelectorWindow::setURL(const QString& user) {
urlLineEdit->setText(user);
}
QString TestRailTestCasesSelectorWindow::getURL() {
return urlLineEdit->text();
}
void TestRailTestCasesSelectorWindow::setUser(const QString& user) {
userLineEdit->setText(user);
}
QString TestRailTestCasesSelectorWindow::getUser() {
return userLineEdit->text();
}
QString TestRailTestCasesSelectorWindow::getPassword() {
return passwordLineEdit->text();
}
void TestRailTestCasesSelectorWindow::setProjectID(const int project) {
projectIDLineEdit->setText(QString::number(project));
}
int TestRailTestCasesSelectorWindow::getProjectID() {
return projectIDLineEdit->text().toInt();
}
void TestRailTestCasesSelectorWindow::setSuiteID(const int project) {
suiteIDLineEdit->setText(QString::number(project));
}
int TestRailTestCasesSelectorWindow::getSuiteID() {
return suiteIDLineEdit->text().toInt();
}
void TestRailTestCasesSelectorWindow::updateMilestonesComboBoxData(QStringList data) {
milestonesComboBox->insertItems(0, data);
}
int TestRailTestCasesSelectorWindow::getMilestoneID() {
return milestonesComboBox->currentIndex();
}

View file

@ -0,0 +1,50 @@
//
// TestRailTestCasesSelectorWindow.h
//
// Created by Nissim Hadar on 26 Jul 2017.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_TestRailTestCasesSelectorWindow_h
#define hifi_TestRailTestCasesSelectorWindow_h
#include "ui_TestRailTestCasesSelectorWindow.h"
class TestRailTestCasesSelectorWindow : public QDialog, public Ui::TestRailTestCasesSelectorWindow {
Q_OBJECT
public:
TestRailTestCasesSelectorWindow(QWidget* parent = Q_NULLPTR);
void reset();
bool getUserCancelled();
void setURL(const QString& user);
QString getURL();
void setUser(const QString& user);
QString getUser();
QString getPassword();
void setProjectID(const int project);
int getProjectID();
void setSuiteID(const int project);
int getSuiteID();
bool userCancelled{ false };
void updateMilestonesComboBoxData(QStringList data);
int getMilestoneID();
private slots:
void on_acceptButton_clicked();
void on_OKButton_clicked();
void on_cancelButton_clicked();
};
#endif

View file

@ -0,0 +1,280 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TestRailTestCasesSelectorWindow</class>
<widget class="QDialog" name="TestRailTestCasesSelectorWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>489</width>
<height>474</height>
</rect>
</property>
<property name="windowTitle">
<string>TestRail Test Case Selector Window</string>
</property>
<widget class="QLabel" name="errorLabel">
<property name="geometry">
<rect>
<x>30</x>
<y>850</y>
<width>500</width>
<height>28</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>similarity</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>70</x>
<y>125</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail Password</string>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>70</x>
<y>25</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail URL</string>
</property>
</widget>
<widget class="QPushButton" name="OKButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>120</x>
<y>420</y>
<width>93</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>OK</string>
</property>
</widget>
<widget class="QPushButton" name="cancelButton">
<property name="geometry">
<rect>
<x>280</x>
<y>420</y>
<width>93</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>Cancel</string>
</property>
</widget>
<widget class="QLineEdit" name="passwordLineEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>120</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>70</x>
<y>75</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail User</string>
</property>
</widget>
<widget class="QLineEdit" name="projectIDLineEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>170</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Normal</enum>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>70</x>
<y>175</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail Project ID</string>
</property>
</widget>
<widget class="QPushButton" name="acceptButton">
<property name="geometry">
<rect>
<x>200</x>
<y>270</y>
<width>231</width>
<height>28</height>
</rect>
</property>
<property name="text">
<string>Accept</string>
</property>
</widget>
<widget class="QComboBox" name="milestonesComboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>270</x>
<y>350</y>
<width>161</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QLabel" name="milestoneLabel">
<property name="enabled">
<bool>true</bool>
</property>
<property name="geometry">
<rect>
<x>140</x>
<y>350</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail Milestone</string>
</property>
</widget>
<widget class="QLineEdit" name="urlLineEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>20</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Normal</enum>
</property>
</widget>
<widget class="QLineEdit" name="userLineEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>70</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Normal</enum>
</property>
</widget>
<widget class="QLineEdit" name="suiteIDLineEdit">
<property name="geometry">
<rect>
<x>200</x>
<y>215</y>
<width>231</width>
<height>24</height>
</rect>
</property>
<property name="echoMode">
<enum>QLineEdit::Normal</enum>
</property>
</widget>
<widget class="QLabel" name="label_5">
<property name="geometry">
<rect>
<x>70</x>
<y>220</y>
<width>121</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>TestRail Suite ID</string>
</property>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<tabstops>
<tabstop>urlLineEdit</tabstop>
<tabstop>userLineEdit</tabstop>
<tabstop>passwordLineEdit</tabstop>
<tabstop>projectIDLineEdit</tabstop>
<tabstop>suiteIDLineEdit</tabstop>
<tabstop>acceptButton</tabstop>
<tabstop>milestonesComboBox</tabstop>
<tabstop>OKButton</tabstop>
<tabstop>cancelButton</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>