624 lines · 25.4 KB · C++
RawDownload
////  AWSInterface.cpp////  Created by Nissim Hadar on 3 Oct 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////  ##### NOTICE: THIS FILE NEEDS TO BE UPDATED WITH THE CORRECT LATEST BUCKET.//#include "AWSInterface.h"#include "common.h" #include <QDirIterator>#include <QJsonDocument>#include <QJsonObject>#include <QMessageBox>#include <QProcess>#include <QRegularExpression> #include <quazip/quazip.h>#include <quazip/JlCompress.h> AWSInterface::AWSInterface(QObject* parent) : QObject(parent) {    _pythonInterface = new PythonInterface();    _pythonCommand = _pythonInterface->getPythonCommand();} void AWSInterface::createWebPageFromResults(const QString& testResults,                                            const QString& workingDirectory,                                            QCheckBox* updateAWSCheckBox,                                            QRadioButton* diffImageRadioButton,                                            QRadioButton* ssimImageRadionButton,                                            QLineEdit* urlLineEdit,                                            const QString& branch,                                            const QString& user) {    _workingDirectory = workingDirectory;     // Verify filename is in correct format     // For example `D:/tt/TestResults--2019-02-10_17-30-57(local)[DESKTOP-6BO62Q9].zip`    QStringList parts = testResults.split('/');    QString zipFilename = parts[parts.length() - 1];     QStringList zipFolderNameParts = zipFilename.split(QRegExp("[\\(\\)\\[\\]]"), Qt::SkipEmptyParts);     if (!QRegularExpression("TestResults--\\d{4}(-\\d\\d){2}_\\d\\d(-\\d\\d){2}").match(zipFolderNameParts[0]).hasMatch() ||        !QRegularExpression("\\w").match(zipFolderNameParts[1]).hasMatch() ||                                                 // build (local, build number or PR number)        !QRegularExpression("\\w").match(zipFolderNameParts[2]).hasMatch()                                                  // machine name    ) {        QMessageBox::critical(0, "Filename is in wrong format", "'" + zipFilename + "' is not in nitpick format");        return;    }     _testResults = testResults;     _urlLineEdit = urlLineEdit;    _urlLineEdit->setEnabled(false);     _branch = branch;    _user = user;     QString zipFilenameWithoutExtension = zipFilename.split('.')[0];    extractTestFailuresFromZippedFolder(_workingDirectory + "/" + zipFilenameWithoutExtension);     if (diffImageRadioButton->isChecked()) {        _comparisonImageFilename = "Difference Image.png";    } else {        _comparisonImageFilename = "SSIM Image.png";    }            createHTMLFile();     if (updateAWSCheckBox->isChecked()) {        updateAWS();        QMessageBox::information(0, "Success", "HTML file has been created and copied to AWS");    } else {        QMessageBox::information(0, "Success", "HTML file has been created");    }} void AWSInterface::extractTestFailuresFromZippedFolder(const QString& folderName) {    // For a test results zip file called `D:/tt/TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ].zip`    //   the folder will be called `TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]`    //   and, this folder will be in the working directory    if (QDir(folderName).exists()) {        QDir dir = folderName;        dir.removeRecursively();    }     JlCompress::extractDir(_testResults, _workingDirectory);} void AWSInterface::createHTMLFile() {    // For file named `D:/tt/snapshots/TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ].zip`     //    - the HTML will be in a folder named `TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ]`    QStringList pathComponents = _testResults.split('/');    QString filename = pathComponents[pathComponents.length() - 1];    _resultsFolder = filename.left(filename.length() - 4);     QString resultsPath = _workingDirectory + "/" + _resultsFolder + "/";    QDir().mkdir(resultsPath);    _htmlFilename = resultsPath + HTML_FILENAME;     QFile file(_htmlFilename);    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {        QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),                              "Could not create '" + _htmlFilename + "'");        exit(-1);    }     QTextStream stream(&file);     startHTMLpage(stream);    writeHead(stream);    writeBody(stream);    finishHTMLpage(stream);     file.close();} void AWSInterface::startHTMLpage(QTextStream& stream) {    stream << "<!DOCTYPE html>\n";    stream << "<html>\n";} void AWSInterface::writeHead(QTextStream& stream) {    stream << "\t" << "<head>\n";    stream << "\t" << "\t" << "<style>\n";    stream << "\t" << "\t" << "\t" << "table, th, td {\n";    stream << "\t" << "\t" << "\t" << "\t" << "border: 1px solid blue;\n";    stream << "\t" << "\t" << "\t" << "}\n";    stream << "\t" << "\t" << "</style>\n";    stream << "\t" << "</head>\n";} void AWSInterface::writeBody(QTextStream& stream) {    stream << "\t" << "<body>\n";     // The results are read here as they are used both in the title (for the summary) and for table    QStringList originalNamesFailures;    QStringList originalNamesSuccesses;    QDirIterator it1(_workingDirectory);    while (it1.hasNext()) {        QString nextDirectory = it1.next();         // Skip `.` and `..` directories        if (nextDirectory.right(1) == ".") {            continue;        }         // Only process result folders        if (!nextDirectory.contains("--tests.")) {            continue;        }         // Look at the filename at the end of the path        QStringList parts = nextDirectory.split('/');        QString name = parts[parts.length() - 1];        if (name.left(7) == "Failure") {            originalNamesFailures.append(nextDirectory);        } else {            originalNamesSuccesses.append(nextDirectory);        }    }     writeTitle(stream, originalNamesFailures, originalNamesSuccesses);    writeTable(stream, originalNamesFailures, originalNamesSuccesses);    stream << "\t" << "</body>\n";} void AWSInterface::finishHTMLpage(QTextStream& stream) {    stream << "</html>\n";} void AWSInterface::writeTitle(QTextStream& stream, const QStringList& originalNamesFailures, const QStringList& originalNamesSuccesses) {    // Separate relevant components from the results name    // The expected format is as follows: `D:/tt/snapshots/TestResults--2018-10-04_11-09-41(PR14128)[DESKTOP-PMKNLSQ].zip`    QStringList tokens = _testResults.split('/');     // date_buildorPR_hostName will be 2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ]    QString date_buildorPR_hostName = tokens[tokens.length() - 1].split("--")[1].split(".")[0];     QString buildorPR = date_buildorPR_hostName.split('(')[1].split(')')[0];    QString hostName = date_buildorPR_hostName.split('[')[1].split(']')[0];     QStringList dateList = date_buildorPR_hostName.split('(')[0].split('_')[0].split('-');    QString year = dateList[0];    QString month = dateList[1];    QString day = dateList[2];     QStringList timeList = date_buildorPR_hostName.split('(')[0].split('_')[1].split('-');    QString hour = timeList[0];    QString minute = timeList[1];    QString second = timeList[2];     const QString months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };     stream << "\t" << "\t" << "<font color=\"Green\">\n";    stream << "\t" << "\t" << "<h1>Results for ";    stream << months[month.toInt() - 1] << " " << day << ", " << year << ", ";    stream << hour << ":" << minute << ":" << second << ", ";     if (buildorPR.left(2) == "PR") {        stream << "PR " << buildorPR.right(buildorPR.length() - 2) << ", ";    } else {        stream << "build " << buildorPR << ", ";    }     stream << "run on " << hostName << "</h1>\n";     stream << "<h2>";    stream << "nitpick  " << nitpickVersion;    stream << ", tests from GitHub: " << _user << "/" << _branch;    stream << "</h2>";     _numberOfFailures = originalNamesFailures.length();    _numberOfSuccesses = originalNamesSuccesses.length();     stream << "<h2>" << QString::number(_numberOfFailures) << " failed, out of a total of " << QString::number(_numberOfFailures + _numberOfSuccesses) << " tests</h2>\n";     stream << "\t" << "\t" << "<font color=\"red\">\n";     if (_numberOfFailures > 0) {        stream << "\t" << "\t" << "<h1>The following tests failed:</h1>";    }} void AWSInterface::writeTable(QTextStream& stream, const QStringList& originalNamesFailures, const QStringList& originalNamesSuccesses) {    QString previousTestName{ "" };     // Loop over all entries in directory.  This is done in stages, as the names are not in the order of the tests    //      The first stage reads the directory names into a list     //      The second stage renames the tests by removing everything up to "--tests."    //      The third stage renames the directories    //      The fourth and lasts stage creates the HTML entries    //    // Note that failures are processed first, then successes     QStringList newNamesFailures;    for (int i = 0; i < originalNamesFailures.length(); ++i) {        newNamesFailures.append(originalNamesFailures[i].split("--tests.")[1]);    }     QStringList newNamesSuccesses;    for (int i = 0; i < originalNamesSuccesses.length(); ++i) {        newNamesSuccesses.append(originalNamesSuccesses[i].split("--tests.")[1]);    }     _htmlFailuresFolder = _workingDirectory + "/" + _resultsFolder + "/" + FAILURES_FOLDER;    QDir().mkdir(_htmlFailuresFolder);     _htmlSuccessesFolder = _workingDirectory + "/" + _resultsFolder + "/" + SUCCESSES_FOLDER;    QDir().mkdir(_htmlSuccessesFolder);     for (int i = 0; i < newNamesFailures.length(); ++i) {        QDir().rename(originalNamesFailures[i], _htmlFailuresFolder + "/" + newNamesFailures[i]);    }     for (int i = 0; i < newNamesSuccesses.length(); ++i) {        QDir().rename(originalNamesSuccesses[i], _htmlSuccessesFolder + "/" + newNamesSuccesses[i]);    }     // Mac does not read folders in lexicographic order, so this step is divided into 2    // Each test consists of the test name and its index.    QStringList folderNames;     QDirIterator it2(_htmlFailuresFolder);    while (it2.hasNext()) {        QString nextDirectory = it2.next();         // Skip `.` and `..` directories, as well as the HTML directory        if (nextDirectory.right(1) == "." || nextDirectory.contains(QString("/") + _resultsFolder + "/TestResults--")) {            continue;        }         QStringList pathComponents = nextDirectory.split('/');        QString folderName = pathComponents[pathComponents.length() - 1];         folderNames << folderName;    }     folderNames.sort();    for (const auto& folderName : folderNames) {        int splitIndex = folderName.lastIndexOf(".");        QString testName = folderName.left(splitIndex).replace('.', " / ");         int testNumber = folderName.right(folderName.length() - (splitIndex + 1)).toInt();         // The failures are ordered lexicographically, so we know that we can rely on the testName changing to create a new table        if (testName != previousTestName) {            if (!previousTestName.isEmpty()) {                closeTable(stream);            }             previousTestName = testName;             stream << "\t\t<h2>" << testName << "</h2>\n";            openTable(stream, folderName, true);        }         createEntry(testNumber, folderName, stream, true);    }     closeTable(stream);    stream << "\t" << "\t" << "<font color=\"blue\">\n";     if (_numberOfSuccesses > 0) {        stream << "\t" << "\t" << "<h1>The following tests passed:</h1>";    }     // Now do the same for passes    folderNames.clear();     QDirIterator it3(_htmlSuccessesFolder);    while (it3.hasNext()) {        QString nextDirectory = it3.next();         // Skip `.` and `..` directories, as well as the HTML directory        if (nextDirectory.right(1) == "." || nextDirectory.contains(QString("/") + _resultsFolder + "/TestResults--")) {            continue;        }         QStringList pathComponents = nextDirectory.split('/');        QString folderName = pathComponents[pathComponents.length() - 1];         folderNames << folderName;    }     folderNames.sort();    for (const auto& folderName : folderNames) {        int splitIndex = folderName.lastIndexOf(".");        QString testName = folderName.left(splitIndex).replace('.', " / ");         int testNumber = folderName.right(folderName.length() - (splitIndex + 1)).toInt();         // The failures are ordered lexicographically, so we know that we can rely on the testName changing to create a new table        if (testName != previousTestName) {            if (!previousTestName.isEmpty()) {                closeTable(stream);            }             previousTestName = testName;             stream << "\t\t<h2>" << testName << "</h2>\n";            openTable(stream, folderName, false);        }         createEntry(testNumber, folderName, stream, false);    }     closeTable(stream);} void AWSInterface::openTable(QTextStream& stream, const QString& testResult, const bool isFailure) {    QStringList resultNameComponents = testResult.split('/');    QString resultName = resultNameComponents[resultNameComponents.length() - 1];     bool textResultsFileFound;    if (isFailure) {        textResultsFileFound = QFile::exists(_htmlFailuresFolder + "/" + resultName + "/Result.txt");    } else {        textResultsFileFound = QFile::exists(_htmlSuccessesFolder + "/" + resultName + "/Result.txt");    }     if (textResultsFileFound) {        if (isFailure) {            stream << "\t\t<table>\n";            stream << "\t\t\t<tr>\n";            stream << "\t\t\t\t<th><h1>Element</h1></th>\n";            stream << "\t\t\t\t<th><h1>Actual Value</h1></th>\n";            stream << "\t\t\t\t<th><h1>Expected Value</h1></th>\n";            stream << "\t\t\t</tr>\n";        } else {            stream << "\t\t<h3>No errors found</h3>\n\n";            stream << "\t\t<h3>===============</h3>\n\n";        }    } else {        stream << "\t\t<table>\n";        stream << "\t\t\t<tr>\n";        stream << "\t\t\t\t<th><h1>Test</h1></th>\n";        stream << "\t\t\t\t<th><h1>Actual Image</h1></th>\n";        stream << "\t\t\t\t<th><h1>Expected Image</h1></th>\n";        stream << "\t\t\t\t<th><h1>Comparison Image</h1></th>\n";        stream << "\t\t\t</tr>\n";    }} void AWSInterface::closeTable(QTextStream& stream) {    stream << "\t\t</table>\n";} void AWSInterface::createEntry(const int index, const QString& testResult, QTextStream& stream, const bool isFailure) {    // For a test named `D:/t/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf/Failure_1--tests.engine.interaction.pick.collision.many.00000`    // we need `Failure_1--tests.engine.interaction.pick.collision.many.00000`    QStringList resultNameComponents = testResult.split('/');    QString resultName = resultNameComponents[resultNameComponents.length() - 1];     QString textResultFilename;    if (isFailure) {        textResultFilename = _htmlFailuresFolder + "/" + resultName + "/Result.txt";    } else {        textResultFilename = _htmlSuccessesFolder + "/" + resultName + "/Result.txt";    }    bool textResultsFileFound{ QFile::exists(textResultFilename) };     QString folder;    bool differenceFileFound;        if (isFailure) {        folder = FAILURES_FOLDER;        differenceFileFound = QFile::exists(_htmlFailuresFolder + "/" + resultName + "/" + _comparisonImageFilename);    } else {        folder = SUCCESSES_FOLDER;        differenceFileFound = QFile::exists(_htmlSuccessesFolder + "/" + resultName + "/" + _comparisonImageFilename);    }     if (textResultsFileFound) {        // Parse the JSON file        QFile file;        file.setFileName(textResultFilename);        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {            QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__),                "Failed to open file " + textResultFilename);        }         QString value = file.readAll();        file.close();         // The Result.txt file is an object containing elements such as the following:        //    "angularDamping": {        //        "actual": 0.3938899040222168,        //        "expected" : 0.3938899,        //        "result" : "pass"        //    },        //        // Failures are thos element that have "fail for the result         QJsonDocument document = QJsonDocument::fromJson(value.toUtf8());        QJsonObject json = document.object();        foreach(const QString& key, json.keys()) {            QJsonValue value = json.value(key);            QJsonObject object = value.toObject();             QJsonValue actualValue = object.value("actual");            QString actualValueString;            if (actualValue.isString()) {                actualValueString = actualValue.toString();            } else if (actualValue.isBool()) {                actualValueString = actualValue.toBool() ? "true" : "false";            } else if (actualValue.isDouble()) {                actualValueString = QString::number(actualValue.toDouble());            }             QJsonValue expectedValue = object.value("expected");            QString expectedValueString;            if (expectedValue.isString()) {                expectedValueString = expectedValue.toString();            } else if (expectedValue.isBool()) {                expectedValueString = expectedValue.toBool() ? "true" : "false";            } else if (expectedValue.isDouble()) {                expectedValueString = QString::number(expectedValue.toDouble());            }            QString result = object.value("result").toString();                       if (result == "fail") {                stream << "\t\t\t<tr>\n";                stream << "\t\t\t\t<td><font size=\"6\">" + key + "</td>\n";                stream << "\t\t\t\t<td><font size=\"6\">" + actualValueString + "</td>\n";                stream << "\t\t\t\t<td><font size=\"6\">" + expectedValueString + "</td>\n";                stream << "\t\t\t</tr>\n";            }        }    } else {        stream << "\t\t\t<tr>\n";        stream << "\t\t\t\t<td><h1>" << QString::number(index) << "</h1></td>\n";         stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/Actual Image.png\" width = \"576\" height = \"324\" ></td>\n";        stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/Expected Image.png\" width = \"576\" height = \"324\" ></td>\n";         if (differenceFileFound) {            stream << "\t\t\t\t<td><img src=\"./" << folder << "/" << resultName << "/" << _comparisonImageFilename << "\" width = \"576\" height = \"324\" ></td>\n";        } else {            stream << "\t\t\t\t<td><h2>Image size mismatch</h2>\n";        }         stream << "\t\t\t</tr>\n";    } } void AWSInterface::updateAWS() {    QString filename = _workingDirectory + "/updateAWS.py";    if (QFile::exists(filename)) {        QFile::remove(filename);    }    QFile file(filename);     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {

Lines 1–500 of 624.