From d79d092dc8b361f55d5d2250f3b809e06ad0105b Mon Sep 17 00:00:00 2001 From: NissimHadar <nissim.hadar@gmail.com> Date: Thu, 4 Oct 2018 16:31:38 -0700 Subject: [PATCH] Can write an HTML file created from zipped results --- tools/auto-tester/src/AWSInterface.cpp | 216 +++++++++++++++++++++++++ tools/auto-tester/src/AWSInterface.h | 44 +++++ tools/auto-tester/src/TestRunner.cpp | 26 ++- 3 files changed, 271 insertions(+), 15 deletions(-) create mode 100644 tools/auto-tester/src/AWSInterface.cpp create mode 100644 tools/auto-tester/src/AWSInterface.h diff --git a/tools/auto-tester/src/AWSInterface.cpp b/tools/auto-tester/src/AWSInterface.cpp new file mode 100644 index 0000000000..865ce5a3d4 --- /dev/null +++ b/tools/auto-tester/src/AWSInterface.cpp @@ -0,0 +1,216 @@ +// +// 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 +// +#include "AWSInterface.h" + +#include <QDirIterator> +#include <QMessageBox> + +#include <quazip5/quazip.h> +#include <quazip5/JlCompress.h> + +AWSInterface::AWSInterface(QObject* parent) : + QObject(parent) { +} + +void AWSInterface::createWebPageFromResults(const QString& testResults, const QString& tempDirectory) { + // Extract test failures from zipped folder + _tempDirectory = tempDirectory; + + QDir dir = _tempDirectory; + dir.mkdir(_tempDirectory); + JlCompress::extractDir(testResults, _tempDirectory); + + createHTMLFile(testResults, tempDirectory); +} + +void AWSInterface::createHTMLFile(const QString& testResults, const QString& tempDirectory) { + // For file named `D:/tt/snapshots/TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ].zip` + // - the HTML will be named `TestResults--2018-10-03_15-35-28(9433)[DESKTOP-PMKNLSQ]` + QString resultsPath = tempDirectory + "/" + resultsFolder + "/"; + QDir().mkdir(resultsPath); + QStringList tokens = testResults.split('/'); + QString htmlFilename = resultsPath + tokens[tokens.length() - 1].split('.')[0] + ".html"; + + 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(testResults, 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(const QString& testResults, QTextStream& stream) { + stream << "\t" << "<body>\n"; + writeTitle(testResults, stream); + writeTable(stream); + stream << "\t" << "</body>\n"; +} + +void AWSInterface::finishHTMLpage(QTextStream& stream) { + stream << "</html>\n"; +} + +void AWSInterface::writeTitle(const QString& testResults, QTextStream& stream) { + // 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" << "<h1>Failures 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"; +} + +void AWSInterface::writeTable(QTextStream& stream) { + 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 + + QStringList originalNames; + QDirIterator it1(_tempDirectory.toStdString().c_str()); + while (it1.hasNext()) { + QString nextDirectory = it1.next(); + + // Skip `.` and `..` directories + if (nextDirectory.right(1) == ".") { + continue; + } + + // Only process failure folders + if (!nextDirectory.contains("--tests.")) { + continue; + } + + originalNames.append(nextDirectory); + } + + QStringList newNames; + for (int i = 0; i < originalNames.length(); ++i) { + newNames.append(originalNames[i].split("--tests.")[1]); + } + + for (int i = 0; i < newNames.length(); ++i) { + QDir dir(originalNames[i]); + dir.rename(originalNames[i], _tempDirectory + "/" + resultsFolder + "/" + newNames[i]); + } + + QDirIterator it2((_tempDirectory + "/" + resultsFolder).toStdString().c_str()); + 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; + } + + int splitIndex = nextDirectory.lastIndexOf("."); + QString testName = nextDirectory.left(splitIndex).replace(".", " / "); + QString testNumber = nextDirectory.right(nextDirectory.length() - (splitIndex + 1)); + + // 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); + } + + createEntry(testNumber.toInt(), nextDirectory, stream); + } + + closeTable(stream); +} + +void AWSInterface::openTable(QTextStream& stream) { + 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>Difference Image</h1></th>\n"; + stream << "\t\t\t</tr>\n"; +} + +void AWSInterface::closeTable(QTextStream& stream) { + stream << "\t\t</table>\n"; +} + +void AWSInterface::createEntry(int index, const QString& testFailure, QTextStream& stream) { + stream << "\t\t\t<tr>\n"; + stream << "\t\t\t\t\<td><h1>" << QString::number(index) << "</h1></td>\n"; + + // For a test named `D:/t/fgadhcUDHSFaidsfh3478JJJFSDFIUSOEIrf/Failure_1--tests.engine.interaction.pick.collision.many.00000` + // we need `Failure_1--tests.engine.interaction.pick.collision.many.00000` + QStringList failureNameComponents = testFailure.split('/'); + QString failureName = failureNameComponents[failureNameComponents.length() - 1]; + + stream << "\t\t\t\t<td><img src=\"" << failureName << "/Actual Image.png\" width = \"576\" height = \"324\" ></td>\n"; + stream << "\t\t\t\t<td><img src=\"" << failureName << "/Expected Image.png\" width = \"576\" height = \"324\" ></td>\n"; + stream << "\t\t\t\t<td><img src=\"" << failureName << "/Difference Image.png\" width = \"576\" height = \"324\" ></td>\n"; + stream << "\t\t\t</tr>\n"; +} \ No newline at end of file diff --git a/tools/auto-tester/src/AWSInterface.h b/tools/auto-tester/src/AWSInterface.h new file mode 100644 index 0000000000..975ac03817 --- /dev/null +++ b/tools/auto-tester/src/AWSInterface.h @@ -0,0 +1,44 @@ +// +// AWSInterface.h +// +// 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 +// + +#ifndef hifi_AWSInterface_h +#define hifi_AWSInterface_h + +#include <QObject> +#include <QTextStream> + +class AWSInterface : public QObject { + Q_OBJECT +public: + explicit AWSInterface(QObject* parent = 0); + + void createWebPageFromResults(const QString& testResults, const QString& tempDirectory); + + void createHTMLFile(const QString& testResults, const QString& tempDirectory); + + void startHTMLpage(QTextStream& stream); + void writeHead(QTextStream& stream); + void writeBody(const QString& testResults, QTextStream& stream); + void finishHTMLpage(QTextStream& stream); + + void writeTitle(const QString& testResults, QTextStream& stream); + void writeTable(QTextStream& stream); + void openTable(QTextStream& stream); + void closeTable(QTextStream& stream); + + void createEntry(int index, const QString& testFailure, QTextStream& stream); + +private: + QString _tempDirectory; + + const QString resultsFolder{ "HTML" }; +}; + +#endif // hifi_AWSInterface_h \ No newline at end of file diff --git a/tools/auto-tester/src/TestRunner.cpp b/tools/auto-tester/src/TestRunner.cpp index ec7c8eadf8..e790575ce5 100644 --- a/tools/auto-tester/src/TestRunner.cpp +++ b/tools/auto-tester/src/TestRunner.cpp @@ -110,6 +110,8 @@ void TestRunner::run() { saveExistingHighFidelityAppDataFolder(); // Download the latest High Fidelity build XML. + // Note that this is not needed for PR builds (or whenever `Run Latest` is unchecked) + // It is still downloaded, to simplify the flow QStringList urls; QStringList filenames; @@ -129,12 +131,12 @@ void TestRunner::downloadComplete() { // Download of Build XML has completed buildXMLDownloaded = true; - parseBuildInformation(); - // Download the High Fidelity installer QStringList urls; QStringList filenames; if (_runLatest->isChecked()) { + parseBuildInformation(); + _installerFilename = INSTALLER_FILENAME_LATEST; urls << _buildInformation.url; @@ -392,18 +394,16 @@ void TestRunner::automaticTestRunEvaluationComplete(QString zippedFolder, int nu } void TestRunner::addBuildNumberAndHostnameToResults(QString zippedFolderName) { + QString augmentedFilename; if (!_runLatest->isChecked()) { QStringList filenameParts = zippedFolderName.split("."); - QString augmentedFilename = filenameParts[0] + "(" + getPRNumberFromURL(_url->toPlainText()) + ")." + filenameParts[1]; - QFile::rename(zippedFolderName, augmentedFilename); - - return; + augmentedFilename = + filenameParts[0] + "(" + getPRNumberFromURL(_url->toPlainText()) + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; + } else { + QStringList filenameParts = zippedFolderName.split("."); + augmentedFilename = + filenameParts[0] + "(" + _buildInformation.build + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; } - - QStringList filenameParts = zippedFolderName.split("."); - QString augmentedFilename = - filenameParts[0] + "(" + _buildInformation.build + ")[" + QHostInfo::localHostName() + "]." + filenameParts[1]; - QFile::rename(zippedFolderName, augmentedFilename); } @@ -504,10 +504,6 @@ QString TestRunner::getInstallerNameFromURL(const QString& url) { // An example URL: https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe try { QStringList urlParts = url.split("/"); - int rr = urlParts.size(); - if (urlParts.size() != 8) { - throw "URL not in expected format, should look like `https://deployment.highfidelity.com/jobs/pr-build/label%3Dwindows/13023/HighFidelity-Beta-Interface-PR14006-be76c43.exe`"; - } return urlParts[urlParts.size() - 1]; } catch (QString errorMessage) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), errorMessage);