Some test madness

This commit is contained in:
Petr Mrázek 2013-12-17 02:09:58 +01:00
parent 20e86801b3
commit d6c71488b3
22 changed files with 457 additions and 201 deletions

View File

@ -43,11 +43,18 @@ ENDIF()
######## 3rd Party Libs ########
# Find the required Qt parts
find_package(Qt5Core REQUIRED)
find_package(Qt5Widgets REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5Test REQUIRED)
find_package(Qt5LinguistTools REQUIRED)
include_directories(${Qt5Widgets_INCLUDE_DIRS})
include_directories(
${Qt5Core_INCLUDE_DIRS}
${Qt5Widgets_INCLUDE_DIRS}
${Qt5Network_INCLUDE_DIRS}
${Qt5Test_INCLUDE_DIRS}
)
# The Qt5 cmake files don't provide its install paths, so ask qmake.
get_target_property(QMAKE_EXECUTABLE Qt5::qmake LOCATION)

View File

@ -286,7 +286,7 @@ void MultiMC::initLogger()
QsLogging::Logger &logger = QsLogging::Logger::instance();
logger.setLoggingLevel(QsLogging::TraceLevel);
m_fileDestination = QsLogging::DestinationFactory::MakeFileDestination("MultiMC.log");
m_debugDestination = QsLogging::DestinationFactory::MakeDebugOutputDestination();
m_debugDestination = QsLogging::DestinationFactory::MakeQDebugDestination();
logger.addDestination(m_fileDestination.get());
logger.addDestination(m_debugDestination.get());
// log all the things

View File

@ -23,10 +23,7 @@
QString PathCombine(QString path1, QString path2)
{
if (!path1.endsWith('/'))
return path1.append('/').append(path2);
else
return path1.append(path2);
return QDir::cleanPath(path1 + QDir::separator() + path2);
}
QString PathCombine(QString path1, QString path2, QString path3)

View File

@ -77,6 +77,15 @@ void DebugOutputDestination::write(const QString &message)
QsDebugOutput::output(message);
}
class QDebugDestination : public Destination
{
public:
virtual void write(const QString &message)
{
qDebug() << message;
};
};
DestinationPtr DestinationFactory::MakeFileDestination(const QString &filePath)
{
return DestinationPtr(new FileDestination(filePath));
@ -87,4 +96,9 @@ DestinationPtr DestinationFactory::MakeDebugOutputDestination()
return DestinationPtr(new DebugOutputDestination);
}
DestinationPtr DestinationFactory::MakeQDebugDestination()
{
return DestinationPtr(new QDebugDestination);
}
} // end namespace

View File

@ -47,6 +47,7 @@ class DestinationFactory
public:
static DestinationPtr MakeFileDestination(const QString &filePath);
static DestinationPtr MakeDebugOutputDestination();
static DestinationPtr MakeQDebugDestination();
};
} // end namespace

View File

@ -20,6 +20,7 @@
#include <QCryptographicHash>
#include <QFileInfo>
#include <QDateTime>
#include <QDir>
#include "logger/QsLog.h"
ForgeXzDownload::ForgeXzDownload(QString relative_path, MetaEntryPtr entry) : NetAction()
@ -312,9 +313,11 @@ void ForgeXzDownload::decompressAndInstall()
// revert pack200
pack200_file.close();
QString pack_name = pack200_file.fileName();
QString source_native = QDir::toNativeSeparators(pack_name);
QString target_native = QDir::toNativeSeparators(m_target_path);
try
{
unpack_200(pack_name.toStdString(), m_target_path.toStdString());
unpack_200(source_native.toStdString(), target_native.toStdString());
}
catch (std::runtime_error &err)
{

View File

@ -26,9 +26,8 @@
#include <QDomDocument>
DownloadUpdateTask::DownloadUpdateTask(QString repoUrl, int versionId, QObject* parent) :
Task(parent)
DownloadUpdateTask::DownloadUpdateTask(QString repoUrl, int versionId, QObject *parent)
: Task(parent)
{
m_cVersionId = MMC->version().build;
@ -87,7 +86,8 @@ void DownloadUpdateTask::findCurrentVersionInfo()
// Load the channel list and wait for it to finish loading.
QLOG_INFO() << "No channel list entries found. Will try reloading it.";
QObject::connect(checker.get(), &UpdateChecker::channelListLoaded, this, &DownloadUpdateTask::processChannels);
QObject::connect(checker.get(), &UpdateChecker::channelListLoaded, this,
&DownloadUpdateTask::processChannels);
checker->updateChanList();
}
else
@ -101,11 +101,12 @@ void DownloadUpdateTask::loadVersionInfo()
setStatus(tr("Loading version information."));
// Create the net job for loading version info.
NetJob* netJob = new NetJob("Version Info");
NetJob *netJob = new NetJob("Version Info");
// Find the index URL.
QUrl newIndexUrl = QUrl(m_nRepoUrl).resolved(QString::number(m_nVersionId) + ".json");
QLOG_DEBUG() << m_nRepoUrl << " turns into " << newIndexUrl;
// Add a net action to download the version info for the version we're updating to.
netJob->addNetAction(ByteArrayDownload::make(newIndexUrl));
@ -114,10 +115,12 @@ void DownloadUpdateTask::loadVersionInfo()
{
QUrl cIndexUrl = QUrl(m_cRepoUrl).resolved(QString::number(m_cVersionId) + ".json");
netJob->addNetAction(ByteArrayDownload::make(cIndexUrl));
QLOG_DEBUG() << m_cRepoUrl << " turns into " << cIndexUrl;
}
// Connect slots so we know when it's done.
QObject::connect(netJob, &NetJob::succeeded, this, &DownloadUpdateTask::vinfoDownloadFinished);
QObject::connect(netJob, &NetJob::succeeded, this,
&DownloadUpdateTask::vinfoDownloadFinished);
QObject::connect(netJob, &NetJob::failed, this, &DownloadUpdateTask::vinfoDownloadFailed);
// Store the NetJob in a class member. We don't want to lose it!
@ -135,7 +138,8 @@ void DownloadUpdateTask::vinfoDownloadFinished()
void DownloadUpdateTask::vinfoDownloadFailed()
{
// Something failed. We really need the second download (current version info), so parse downloads anyways as long as the first one succeeded.
// Something failed. We really need the second download (current version info), so parse
// downloads anyways as long as the first one succeeded.
if (m_vinfoNetJob->first()->m_status != Job_Failed)
{
parseDownloadedVersionInfo();
@ -154,43 +158,51 @@ void DownloadUpdateTask::parseDownloadedVersionInfo()
setStatus(tr("Reading file list for new version."));
QLOG_DEBUG() << "Reading file list for new version.";
QString error;
if (!parseVersionInfo(std::dynamic_pointer_cast<ByteArrayDownload>(
m_vinfoNetJob->first())->m_data, &m_nVersionFileList, &error))
if (!parseVersionInfo(
std::dynamic_pointer_cast<ByteArrayDownload>(m_vinfoNetJob->first())->m_data,
&m_nVersionFileList, &error))
{
emitFailed(error);
return;
}
// If there is a second entry in the network job's list, load it as the current version's info.
// If there is a second entry in the network job's list, load it as the current version's
// info.
if (m_vinfoNetJob->size() >= 2 && m_vinfoNetJob->operator[](1)->m_status != Job_Failed)
{
setStatus(tr("Reading file list for current version."));
QLOG_DEBUG() << "Reading file list for current version.";
QString error;
parseVersionInfo(std::dynamic_pointer_cast<ByteArrayDownload>(
m_vinfoNetJob->operator[](1))->m_data, &m_cVersionFileList, &error);
parseVersionInfo(
std::dynamic_pointer_cast<ByteArrayDownload>(m_vinfoNetJob->operator[](1))->m_data,
&m_cVersionFileList, &error);
}
// We don't need this any more.
m_vinfoNetJob.reset();
// Now that we're done loading version info, we can move on to the next step. Process file lists and download files.
// Now that we're done loading version info, we can move on to the next step. Process file
// lists and download files.
processFileLists();
}
bool DownloadUpdateTask::parseVersionInfo(const QByteArray &data, VersionFileList* list, QString *error)
bool DownloadUpdateTask::parseVersionInfo(const QByteArray &data, VersionFileList *list,
QString *error)
{
QJsonParseError jsonError;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &jsonError);
if (jsonError.error != QJsonParseError::NoError)
{
*error = QString("Failed to parse version info JSON: %1 at %2").arg(jsonError.errorString()).arg(jsonError.offset);
*error = QString("Failed to parse version info JSON: %1 at %2")
.arg(jsonError.errorString())
.arg(jsonError.offset);
QLOG_ERROR() << error;
return false;
}
QJsonObject json = jsonDoc.object();
QLOG_DEBUG() << data;
QLOG_DEBUG() << "Loading version info from JSON.";
QJsonArray filesArray = json.value("Files").toArray();
for (QJsonValue fileValue : filesArray)
@ -198,13 +210,10 @@ bool DownloadUpdateTask::parseVersionInfo(const QByteArray &data, VersionFileLis
QJsonObject fileObj = fileValue.toObject();
VersionFileEntry file{
fileObj.value("Path").toString(),
fileObj.value("Perms").toVariant().toInt(),
FileSourceList(),
fileObj.value("MD5").toString(),
};
fileObj.value("Path").toString(), fileObj.value("Perms").toVariant().toInt(),
FileSourceList(), fileObj.value("MD5").toString(), };
QLOG_DEBUG() << "File" << file.path << "with perms" << file.mode;
QJsonArray sourceArray = fileObj.value("Sources").toArray();
for (QJsonValue val : sourceArray)
{
@ -213,11 +222,14 @@ bool DownloadUpdateTask::parseVersionInfo(const QByteArray &data, VersionFileLis
QString type = sourceObj.value("SourceType").toString();
if (type == "http")
{
file.sources.append(FileSource("http", preparePath(sourceObj.value("Url").toString())));
file.sources.append(
FileSource("http", preparePath(sourceObj.value("Url").toString())));
}
else if (type == "httpc")
{
file.sources.append(FileSource("httpc", preparePath(sourceObj.value("Url").toString()), sourceObj.value("CompressionType").toString()));
file.sources.append(FileSource("httpc",
preparePath(sourceObj.value("Url").toString()),
sourceObj.value("CompressionType").toString()));
}
else
{
@ -236,13 +248,19 @@ bool DownloadUpdateTask::parseVersionInfo(const QByteArray &data, VersionFileLis
void DownloadUpdateTask::processFileLists()
{
// Create a network job for downloading files.
NetJob* netJob = new NetJob("Update Files");
NetJob *netJob = new NetJob("Update Files");
processFileLists(netJob, m_cVersionFileList, m_nVersionFileList, m_operationList);
if (!processFileLists(netJob, m_cVersionFileList, m_nVersionFileList, m_operationList))
{
emitFailed(tr("Failed to process update lists..."));
return;
}
// Add listeners to wait for the downloads to finish.
QObject::connect(netJob, &NetJob::succeeded, this, &DownloadUpdateTask::fileDownloadFinished);
QObject::connect(netJob, &NetJob::progress, this, &DownloadUpdateTask::fileDownloadProgressChanged);
QObject::connect(netJob, &NetJob::succeeded, this,
&DownloadUpdateTask::fileDownloadFinished);
QObject::connect(netJob, &NetJob::progress, this,
&DownloadUpdateTask::fileDownloadProgressChanged);
QObject::connect(netJob, &NetJob::failed, this, &DownloadUpdateTask::fileDownloadFailed);
// Now start the download.
@ -254,75 +272,144 @@ void DownloadUpdateTask::processFileLists()
writeInstallScript(m_operationList, PathCombine(m_updateFilesDir.path(), "file_list.xml"));
}
void DownloadUpdateTask::processFileLists(NetJob *job, const VersionFileList &currentVersion, const VersionFileList &newVersion, DownloadUpdateTask::UpdateOperationList &ops)
bool
DownloadUpdateTask::processFileLists(NetJob *job,
const DownloadUpdateTask::VersionFileList &currentVersion,
const DownloadUpdateTask::VersionFileList &newVersion,
DownloadUpdateTask::UpdateOperationList &ops)
{
setStatus(tr("Processing file lists. Figuring out how to install the update."));
// First, if we've loaded the current version's file list, we need to iterate through it and
// First, if we've loaded the current version's file list, we need to iterate through it and
// delete anything in the current one version's list that isn't in the new version's list.
for (VersionFileEntry entry : currentVersion)
{
QFileInfo toDelete(entry.path);
if (!toDelete.exists())
{
QLOG_ERROR() << "Expected file " << toDelete.absoluteFilePath()
<< " doesn't exist!";
QLOG_ERROR() << "CWD: " << QDir::currentPath();
}
bool keep = false;
//
for (VersionFileEntry newEntry : newVersion)
{
if (newEntry.path == entry.path)
{
QLOG_DEBUG() << "Not deleting" << entry.path << "because it is still present in the new version.";
QLOG_DEBUG() << "Not deleting" << entry.path
<< "because it is still present in the new version.";
keep = true;
break;
}
}
// If the loop reaches the end and we didn't find a match, delete the file.
if(!keep)
ops.append(UpdateOperation::DeleteOp(entry.path));
if (!keep)
{
QFileInfo toDelete(entry.path);
if (toDelete.exists())
ops.append(UpdateOperation::DeleteOp(entry.path));
}
}
// Next, check each file in MultiMC's folder and see if we need to update them.
for (VersionFileEntry entry : newVersion)
{
// TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a way to do this in the background.
// TODO: Let's not MD5sum a ton of files on the GUI thread. We should probably find a
// way to do this in the background.
QString fileMD5;
QFile entryFile(entry.path);
if (entryFile.open(QFile::ReadOnly))
QFileInfo entryInfo(entry.path);
bool needs_upgrade = false;
if (!entryFile.exists())
{
QCryptographicHash hash(QCryptographicHash::Md5);
hash.addData(entryFile.readAll());
fileMD5 = hash.result().toHex();
needs_upgrade = true;
}
else
{
bool pass = true;
if (!entryInfo.isReadable())
{
QLOG_ERROR() << "File " << entry.path << " is not readable.";
pass = false;
}
if (!entryInfo.isWritable())
{
QLOG_ERROR() << "File " << entry.path << " is not writable.";
pass = false;
}
if (!entryFile.open(QFile::ReadOnly))
{
QLOG_ERROR() << "File " << entry.path << " cannot be opened for reading.";
pass = false;
}
if (!pass)
{
QLOG_ERROR() << "CWD: " << QDir::currentPath();
ops.clear();
return false;
}
}
if (!entryFile.exists() || fileMD5.isEmpty() || fileMD5 != entry.md5)
QCryptographicHash hash(QCryptographicHash::Md5);
auto foo = entryFile.readAll();
hash.addData(foo);
fileMD5 = hash.result().toHex();
if ((fileMD5 != entry.md5))
{
QLOG_DEBUG() << "Found file" << entry.path << "that needs updating.";
QLOG_DEBUG() << "MD5Sum does not match!";
QLOG_DEBUG() << "Expected:'" << entry.md5 << "'";
QLOG_DEBUG() << "Got: '" << fileMD5 << "'";
needs_upgrade = true;
}
// Go through the sources list and find one to use.
// TODO: Make a NetAction that takes a source list and tries each of them until one works. For now, we'll just use the first http one.
for (FileSource source : entry.sources)
// skip file. it doesn't need an upgrade.
if (!needs_upgrade)
{
QLOG_DEBUG() << "File" << entry.path << " does not need updating.";
continue;
}
// yep. this file actually needs an upgrade. PROCEED.
QLOG_DEBUG() << "Found file" << entry.path << " that needs updating.";
// Go through the sources list and find one to use.
// TODO: Make a NetAction that takes a source list and tries each of them until one
// works. For now, we'll just use the first http one.
for (FileSource source : entry.sources)
{
if (source.type == "http")
{
if (source.type == "http")
QLOG_DEBUG() << "Will download" << entry.path << "from" << source.url;
// Download it to updatedir/<filepath>-<md5> where filepath is the file's
// path with slashes replaced by underscores.
QString dlPath =
PathCombine(m_updateFilesDir.path(), QString(entry.path).replace("/", "_"));
if (job)
{
QLOG_DEBUG() << "Will download" << entry.path << "from" << source.url;
// Download it to updatedir/<filepath>-<md5> where filepath is the file's path with slashes replaced by underscores.
QString dlPath = PathCombine(m_updateFilesDir.path(), QString(entry.path).replace("/", "_"));
if (job)
{
// We need to download the file to the updatefiles folder and add a task to copy it to its install path.
auto download = MD5EtagDownload::make(source.url, dlPath);
download->m_check_md5 = true;
download->m_expected_md5 = entry.md5;
job->addNetAction(download);
}
// Now add a copy operation to our operations list to install the file.
ops.append(UpdateOperation::CopyOp(dlPath, entry.path, entry.mode));
// We need to download the file to the updatefiles folder and add a task
// to copy it to its install path.
auto download = MD5EtagDownload::make(source.url, dlPath);
download->m_check_md5 = true;
download->m_expected_md5 = entry.md5;
job->addNetAction(download);
}
// Now add a copy operation to our operations list to install the file.
ops.append(UpdateOperation::CopyOp(dlPath, entry.path, entry.mode));
}
}
}
return true;
}
bool DownloadUpdateTask::writeInstallScript(UpdateOperationList& opsList, QString scriptFile)
bool DownloadUpdateTask::writeInstallScript(UpdateOperationList &opsList, QString scriptFile)
{
// Build the base structure of the XML document.
QDomDocument doc;
@ -342,38 +429,43 @@ bool DownloadUpdateTask::writeInstallScript(UpdateOperationList& opsList, QStrin
{
QDomElement file = doc.createElement("file");
QString native_file = QDir::toNativeSeparators(op.file);
QString native_dest = QDir::toNativeSeparators(op.dest);
switch (op.type)
{
case UpdateOperation::OP_COPY:
{
// Install the file.
QDomElement name = doc.createElement("source");
QDomElement path = doc.createElement("dest");
QDomElement mode = doc.createElement("mode");
name.appendChild(doc.createTextNode(op.file));
path.appendChild(doc.createTextNode(op.dest));
// We need to add a 0 at the beginning here, because Qt doesn't convert to octal correctly.
mode.appendChild(doc.createTextNode("0" + QString::number(op.mode, 8)));
file.appendChild(name);
file.appendChild(path);
file.appendChild(mode);
installFiles.appendChild(file);
QLOG_DEBUG() << "Will install file" << op.file;
}
break;
case UpdateOperation::OP_COPY:
{
// Install the file.
QDomElement name = doc.createElement("source");
QDomElement path = doc.createElement("dest");
QDomElement mode = doc.createElement("mode");
name.appendChild(doc.createTextNode(native_file));
path.appendChild(doc.createTextNode(native_dest));
// We need to add a 0 at the beginning here, because Qt doesn't convert to octal
// correctly.
mode.appendChild(doc.createTextNode("0" + QString::number(op.mode, 8)));
file.appendChild(name);
file.appendChild(path);
file.appendChild(mode);
installFiles.appendChild(file);
QLOG_DEBUG() << "Will install file" << native_file;
}
break;
case UpdateOperation::OP_DELETE:
{
// Delete the file.
file.appendChild(doc.createTextNode(op.file));
removeFiles.appendChild(file);
QLOG_DEBUG() << "Will remove file" << op.file;
}
break;
case UpdateOperation::OP_DELETE:
{
// Delete the file.
file.appendChild(doc.createTextNode(native_file));
removeFiles.appendChild(file);
QLOG_DEBUG() << "Will remove file" << native_file;
}
break;
default:
QLOG_WARN() << "Can't write update operation of type" << op.type << "to file. Not implemented.";
continue;
default:
QLOG_WARN() << "Can't write update operation of type" << op.type
<< "to file. Not implemented.";
continue;
}
}
@ -395,7 +487,9 @@ bool DownloadUpdateTask::writeInstallScript(UpdateOperationList& opsList, QStrin
QString DownloadUpdateTask::preparePath(const QString &path)
{
return QString(path).replace("$PWD", qApp->applicationDirPath());
QString foo = path;
foo.replace("$PWD", qApp->applicationDirPath());
return QUrl::fromLocalFile(foo).toString(QUrl::FullyEncoded);
}
void DownloadUpdateTask::fileDownloadFinished()
@ -412,11 +506,10 @@ void DownloadUpdateTask::fileDownloadFailed()
void DownloadUpdateTask::fileDownloadProgressChanged(qint64 current, qint64 total)
{
setProgress((int)(((float)current / (float)total)*100));
setProgress((int)(((float)current / (float)total) * 100));
}
QString DownloadUpdateTask::updateFilesDir()
{
return m_updateFilesDir.path();
}

View File

@ -156,7 +156,7 @@ protected:
* Takes a list of file entries for the current version's files and the new version's files
* and populates the downloadList and operationList with information about how to download and install the update.
*/
virtual void processFileLists(NetJob *job, const VersionFileList &currentVersion, const VersionFileList &newVersion, UpdateOperationList &ops);
virtual bool processFileLists(NetJob *job, const VersionFileList &currentVersion, const VersionFileList &newVersion, UpdateOperationList &ops);
/*!
* Calls \see processFileLists to populate the \see m_operationList and a NetJob, and then executes
@ -195,7 +195,8 @@ protected:
QTemporaryDir m_updateFilesDir;
/*!
* Substitutes $PWD for the application directory
* Filters paths
* Path of the format $PWD/path, it is converted to a file:///$PWD/ URL
*/
static QString preparePath(const QString &path);

View File

@ -8,6 +8,9 @@ macro(add_unit_test name)
unset(srcs)
foreach(arg ${testname} ${ARGN})
list(APPEND srcs ${CMAKE_CURRENT_SOURCE_DIR}/${arg})
if (WIN32)
list(APPEND srcs ${CMAKE_CURRENT_SOURCE_DIR}/test.rc)
endif()
endforeach()
add_executable(tst_${name} ${srcs})
qt5_use_modules(tst_${name} Test Core Network Widgets)
@ -81,4 +84,9 @@ if(MultiMC_CODE_COVERAGE)
add_custom_target(MultiMC_RUN_TESTS DEPENDS MultiMC_GENERATE_COVERAGE_HTML)
endif(MultiMC_CODE_COVERAGE)
add_subdirectory(data)
add_custom_target(MultiMC_Test_Data
ALL
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_CURRENT_BINARY_DIR}/data
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/data ${CMAKE_CURRENT_BINARY_DIR}/data
)

View File

@ -31,6 +31,9 @@ struct TestsInternal
# define _MMC_EXTRA_ARGV
# define _MMC_EXTRA_ARGC 0
#endif
#define QTEST_GUILESS_MAIN_MULTIMC(TestObject) \
int main(int argc, char *argv[]) \
{ \

2
tests/data/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
* -text -diff

View File

@ -8,7 +8,7 @@
"Sources": [
{
"SourceType": "http",
"Url": "file://$PWD/tests/data/fileOneA"
"Url": "$PWD/tests/data/fileOneA"
}
],
"Executable": true,
@ -20,7 +20,7 @@
"Sources": [
{
"SourceType": "http",
"Url": "file://$PWD/tests/data/fileTwo"
"Url": "$PWD/tests/data/fileTwo"
}
],
"Executable": false,
@ -32,7 +32,7 @@
"Sources": [
{
"SourceType": "http",
"Url": "file://$PWD/tests/data/fileThree"
"Url": "$PWD/tests/data/fileThree"
}
],
"Executable": false,

View File

@ -8,7 +8,7 @@
"Sources": [
{
"SourceType": "http",
"Url": "file://$PWD/tests/data/fileOneB"
"Url": "$PWD/tests/data/fileOneB"
}
],
"Executable": true,
@ -20,7 +20,7 @@
"Sources": [
{
"SourceType": "http",
"Url": "file://$PWD/tests/data/fileTwo"
"Url": "$PWD/tests/data/fileTwo"
}
],
"Executable": false,

View File

@ -1,4 +0,0 @@
add_custom_target(MultiMC_Test_Data
ALL
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}
)

View File

@ -5,7 +5,7 @@
"id": "develop",
"name": "Develop",
"description": "The channel called \"develop\"",
"url": "file://$PWD/tests/data/"
"url": "$PWD/tests/data/"
},
{
"id": "stable",

View File

@ -0,0 +1,17 @@
<update version="3">
<install>
<file>
<source>sourceOne</source>
<dest>destOne</dest>
<mode>0777</mode>
</file>
<file>
<source>MultiMC.exe</source>
<dest>M\u\l\t\i\M\C\e\x\e</dest>
<mode>0644</mode>
</file>
</install>
<uninstall>
<file>toDelete.abc</file>
</uninstall>
</update>

27
tests/test.manifest Normal file
View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity name="MultiMC.Test.0" type="win32" version="5.0.0.0" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="x86" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<description>Custom Minecraft launcher for managing multiple installs.</description>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!--The ID below indicates app support for Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!--The ID below indicates app support for Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!--The ID below indicates app support for Windows Developer Preview / Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
</application>
</compatibility>
</assembly>

28
tests/test.rc Normal file
View File

@ -0,0 +1,28 @@
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
1 RT_MANIFEST "test.manifest"
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_APP
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000004b0"
BEGIN
VALUE "CompanyName", "MultiMC Contributors"
VALUE "FileDescription", "Testcase"
VALUE "FileVersion", "1.0.0.0"
VALUE "ProductName", "MultiMC Testcase"
VALUE "ProductVersion", "5"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0000, 0x04b0 // Unicode
END
END

View File

@ -7,54 +7,73 @@
#include "logic/updater/UpdateChecker.h"
#include "depends/util/include/pathutils.h"
DownloadUpdateTask::FileSourceList encodeBaseFile(const char *suffix)
{
auto base = qApp->applicationDirPath();
QUrl localFile = QUrl::fromLocalFile(base + suffix);
QString localUrlString = localFile.toString(QUrl::FullyEncoded);
auto item = DownloadUpdateTask::FileSource("http", localUrlString);
return DownloadUpdateTask::FileSourceList({item});
}
Q_DECLARE_METATYPE(DownloadUpdateTask::VersionFileList)
Q_DECLARE_METATYPE(DownloadUpdateTask::UpdateOperation)
bool operator==(const DownloadUpdateTask::FileSource &f1, const DownloadUpdateTask::FileSource &f2)
bool operator==(const DownloadUpdateTask::FileSource &f1,
const DownloadUpdateTask::FileSource &f2)
{
return f1.type == f2.type &&
f1.url == f2.url &&
f1.compressionType == f2.compressionType;
return f1.type == f2.type && f1.url == f2.url && f1.compressionType == f2.compressionType;
}
bool operator==(const DownloadUpdateTask::VersionFileEntry &v1, const DownloadUpdateTask::VersionFileEntry &v2)
bool operator==(const DownloadUpdateTask::VersionFileEntry &v1,
const DownloadUpdateTask::VersionFileEntry &v2)
{
return v1.path == v2.path &&
v1.mode == v2.mode &&
v1.sources == v2.sources &&
v1.md5 == v2.md5;
return v1.path == v2.path && v1.mode == v2.mode && v1.sources == v2.sources &&
v1.md5 == v2.md5;
}
bool operator==(const DownloadUpdateTask::UpdateOperation &u1, const DownloadUpdateTask::UpdateOperation &u2)
bool operator==(const DownloadUpdateTask::UpdateOperation &u1,
const DownloadUpdateTask::UpdateOperation &u2)
{
return u1.type == u2.type &&
u1.file == u2.file &&
u1.dest == u2.dest &&
u1.mode == u2.mode;
return u1.type == u2.type && u1.file == u2.file && u1.dest == u2.dest && u1.mode == u2.mode;
}
QDebug operator<<(QDebug dbg, const DownloadUpdateTask::FileSource &f)
{
dbg.nospace() << "FileSource(type=" << f.type << " url=" << f.url << " comp=" << f.compressionType << ")";
dbg.nospace() << "FileSource(type=" << f.type << " url=" << f.url
<< " comp=" << f.compressionType << ")";
return dbg.maybeSpace();
}
QDebug operator<<(QDebug dbg, const DownloadUpdateTask::VersionFileEntry &v)
{
dbg.nospace() << "VersionFileEntry(path=" << v.path << " mode=" << v.mode << " md5=" << v.md5 << " sources=" << v.sources << ")";
dbg.nospace() << "VersionFileEntry(path=" << v.path << " mode=" << v.mode
<< " md5=" << v.md5 << " sources=" << v.sources << ")";
return dbg.maybeSpace();
}
QDebug operator<<(QDebug dbg, const DownloadUpdateTask::UpdateOperation::Type &t)
{
switch (t)
{
case DownloadUpdateTask::UpdateOperation::OP_COPY: dbg << "OP_COPY"; break;
case DownloadUpdateTask::UpdateOperation::OP_DELETE: dbg << "OP_DELETE"; break;
case DownloadUpdateTask::UpdateOperation::OP_MOVE: dbg << "OP_MOVE"; break;
case DownloadUpdateTask::UpdateOperation::OP_CHMOD: dbg << "OP_CHMOD"; break;
case DownloadUpdateTask::UpdateOperation::OP_COPY:
dbg << "OP_COPY";
break;
case DownloadUpdateTask::UpdateOperation::OP_DELETE:
dbg << "OP_DELETE";
break;
case DownloadUpdateTask::UpdateOperation::OP_MOVE:
dbg << "OP_MOVE";
break;
case DownloadUpdateTask::UpdateOperation::OP_CHMOD:
dbg << "OP_CHMOD";
break;
}
return dbg.maybeSpace();
}
QDebug operator<<(QDebug dbg, const DownloadUpdateTask::UpdateOperation &u)
{
dbg.nospace() << "UpdateOperation(type=" << u.type << " file=" << u.file << " dest=" << u.dest << " mode=" << u.mode << ")";
dbg.nospace() << "UpdateOperation(type=" << u.type << " file=" << u.file
<< " dest=" << u.dest << " mode=" << u.mode << ")";
return dbg.maybeSpace();
}
@ -65,26 +84,30 @@ private
slots:
void initTestCase()
{
}
void cleanupTestCase()
{
}
void test_writeInstallScript()
{
DownloadUpdateTask task(QUrl::fromLocalFile(QDir::current().absoluteFilePath("tests/data/")).toString(), 0);
DownloadUpdateTask task(
QUrl::fromLocalFile(QDir::current().absoluteFilePath("tests/data/")).toString(), 0);
DownloadUpdateTask::UpdateOperationList ops;
ops << DownloadUpdateTask::UpdateOperation::CopyOp("sourceOne", "destOne", 0777)
<< DownloadUpdateTask::UpdateOperation::CopyOp("MultiMC.exe", "M/u/l/t/i/M/C/e/x/e")
<< DownloadUpdateTask::UpdateOperation::DeleteOp("toDelete.abc");
#if defined(Q_OS_WIN)
auto testFile = "tests/data/tst_DownloadUpdateTask-test_writeInstallScript_win32.xml";
#else
auto testFile = "tests/data/tst_DownloadUpdateTask-test_writeInstallScript.xml";
#endif
const QString script = QDir::temp().absoluteFilePath("MultiMCUpdateScript.xml");
QVERIFY(task.writeInstallScript(ops, script));
QCOMPARE(TestsInternal::readFileUtf8(script), MULTIMC_GET_TEST_FILE_UTF8("tests/data/tst_DownloadUpdateTask-test_writeInstallScript.xml"));
QCOMPARE(TestsInternal::readFileUtf8(script).replace(QRegExp("[\r\n]+"), "\n"),
MULTIMC_GET_TEST_FILE_UTF8(testFile).replace(QRegExp("[\r\n]+"), "\n"));
}
void test_parseVersionInfo_data()
@ -94,29 +117,34 @@ slots:
QTest::addColumn<QString>("error");
QTest::addColumn<bool>("ret");
QTest::newRow("one") << MULTIMC_GET_TEST_FILE("tests/data/1.json")
<< (DownloadUpdateTask::VersionFileList()
<< DownloadUpdateTask::VersionFileEntry{"fileOne", 493,
(DownloadUpdateTask::FileSourceList() << DownloadUpdateTask::FileSource("http", "file://" + qApp->applicationDirPath() + "/tests/data/fileOneA")),
"9eb84090956c484e32cb6c08455a667b"}
<< DownloadUpdateTask::VersionFileEntry{"fileTwo", 644,
(DownloadUpdateTask::FileSourceList() << DownloadUpdateTask::FileSource("http", "file://" + qApp->applicationDirPath() + "/tests/data/fileTwo")),
"38f94f54fa3eb72b0ea836538c10b043"}
<< DownloadUpdateTask::VersionFileEntry{"fileThree", 750,
(DownloadUpdateTask::FileSourceList() << DownloadUpdateTask::FileSource("http", "file://" + qApp->applicationDirPath() + "/tests/data/fileThree")),
"f12df554b21e320be6471d7154130e70"})
<< QString()
<< true;
QTest::newRow("two") << MULTIMC_GET_TEST_FILE("tests/data/2.json")
<< (DownloadUpdateTask::VersionFileList()
<< DownloadUpdateTask::VersionFileEntry{"fileOne", 493,
(DownloadUpdateTask::FileSourceList() << DownloadUpdateTask::FileSource("http", "file://" + qApp->applicationDirPath() + "/tests/data/fileOneB")),
"42915a71277c9016668cce7b82c6b577"}
<< DownloadUpdateTask::VersionFileEntry{"fileTwo", 644,
(DownloadUpdateTask::FileSourceList() << DownloadUpdateTask::FileSource("http", "file://" + qApp->applicationDirPath() + "/tests/data/fileTwo")),
"38f94f54fa3eb72b0ea836538c10b043"})
<< QString()
<< true;
QTest::newRow("one")
<< MULTIMC_GET_TEST_FILE("tests/data/1.json")
<< (DownloadUpdateTask::VersionFileList()
<< DownloadUpdateTask::VersionFileEntry{"fileOne",
493,
encodeBaseFile("/tests/data/fileOneA"),
"9eb84090956c484e32cb6c08455a667b"}
<< DownloadUpdateTask::VersionFileEntry{"fileTwo",
644,
encodeBaseFile("/tests/data/fileTwo"),
"38f94f54fa3eb72b0ea836538c10b043"}
<< DownloadUpdateTask::VersionFileEntry{"fileThree",
750,
encodeBaseFile("/tests/data/fileThree"),
"f12df554b21e320be6471d7154130e70"})
<< QString() << true;
QTest::newRow("two")
<< MULTIMC_GET_TEST_FILE("tests/data/2.json")
<< (DownloadUpdateTask::VersionFileList()
<< DownloadUpdateTask::VersionFileEntry{"fileOne",
493,
encodeBaseFile("/tests/data/fileOneB"),
"42915a71277c9016668cce7b82c6b577"}
<< DownloadUpdateTask::VersionFileEntry{"fileTwo",
644,
encodeBaseFile("/tests/data/fileTwo"),
"38f94f54fa3eb72b0ea836538c10b043"})
<< QString() << true;
}
void test_parseVersionInfo()
{
@ -143,23 +171,45 @@ slots:
DownloadUpdateTask *downloader = new DownloadUpdateTask(QString(), -1);
// update fileOne, keep fileTwo, remove fileThree
QTest::newRow("test 1") << downloader
<< (DownloadUpdateTask::VersionFileList()
<< DownloadUpdateTask::VersionFileEntry{QFINDTESTDATA("tests/data/fileOne"), 493, DownloadUpdateTask::FileSourceList()
<< DownloadUpdateTask::FileSource("http", "http://host/path/fileOne-1"), "9eb84090956c484e32cb6c08455a667b"}
<< DownloadUpdateTask::VersionFileEntry{QFINDTESTDATA("tests/data/fileTwo"), 644, DownloadUpdateTask::FileSourceList()
<< DownloadUpdateTask::FileSource("http", "http://host/path/fileTwo-1"), "38f94f54fa3eb72b0ea836538c10b043"}
<< DownloadUpdateTask::VersionFileEntry{QFINDTESTDATA("tests/data/fileThree"), 420, DownloadUpdateTask::FileSourceList()
<< DownloadUpdateTask::FileSource("http", "http://host/path/fileThree-1"), "f12df554b21e320be6471d7154130e70"})
<< (DownloadUpdateTask::VersionFileList()
<< DownloadUpdateTask::VersionFileEntry{QFINDTESTDATA("tests/data/fileOne"), 493, DownloadUpdateTask::FileSourceList()
<< DownloadUpdateTask::FileSource("http", "http://host/path/fileOne-2"), "42915a71277c9016668cce7b82c6b577"}
<< DownloadUpdateTask::VersionFileEntry{QFINDTESTDATA("tests/data/fileTwo"), 644, DownloadUpdateTask::FileSourceList()
<< DownloadUpdateTask::FileSource("http", "http://host/path/fileTwo-2"), "38f94f54fa3eb72b0ea836538c10b043"})
<< (DownloadUpdateTask::UpdateOperationList()
<< DownloadUpdateTask::UpdateOperation::DeleteOp(QFINDTESTDATA("tests/data/fileThree"))
<< DownloadUpdateTask::UpdateOperation::CopyOp(PathCombine(downloader->updateFilesDir(), QFINDTESTDATA("tests/data/fileOne").replace("/", "_")),
QFINDTESTDATA("tests/data/fileOne"), 493));
QTest::newRow("test 1")
<< downloader << (DownloadUpdateTask::VersionFileList()
<< DownloadUpdateTask::VersionFileEntry{
"tests/data/fileOne", 493,
DownloadUpdateTask::FileSourceList()
<< DownloadUpdateTask::FileSource(
"http", "http://host/path/fileOne-1"),
"9eb84090956c484e32cb6c08455a667b"}
<< DownloadUpdateTask::VersionFileEntry{
"tests/data/fileTwo", 644,
DownloadUpdateTask::FileSourceList()
<< DownloadUpdateTask::FileSource(
"http", "http://host/path/fileTwo-1"),
"38f94f54fa3eb72b0ea836538c10b043"}
<< DownloadUpdateTask::VersionFileEntry{
"tests/data/fileThree", 420,
DownloadUpdateTask::FileSourceList()
<< DownloadUpdateTask::FileSource(
"http", "http://host/path/fileThree-1"),
"f12df554b21e320be6471d7154130e70"})
<< (DownloadUpdateTask::VersionFileList()
<< DownloadUpdateTask::VersionFileEntry{
"tests/data/fileOne", 493,
DownloadUpdateTask::FileSourceList()
<< DownloadUpdateTask::FileSource("http",
"http://host/path/fileOne-2"),
"42915a71277c9016668cce7b82c6b577"}
<< DownloadUpdateTask::VersionFileEntry{
"tests/data/fileTwo", 644,
DownloadUpdateTask::FileSourceList()
<< DownloadUpdateTask::FileSource("http",
"http://host/path/fileTwo-2"),
"38f94f54fa3eb72b0ea836538c10b043"})
<< (DownloadUpdateTask::UpdateOperationList()
<< DownloadUpdateTask::UpdateOperation::DeleteOp("tests/data/fileThree")
<< DownloadUpdateTask::UpdateOperation::CopyOp(
PathCombine(downloader->updateFilesDir(),
QString("tests/data/fileOne").replace("/", "_")),
"tests/data/fileOne", 493));
}
void test_processFileLists()
{
@ -170,7 +220,8 @@ slots:
DownloadUpdateTask::UpdateOperationList operations;
downloader->processFileLists(new NetJob("Dummy"), currentVersion, newVersion, operations);
downloader->processFileLists(new NetJob("Dummy"), currentVersion, newVersion,
operations);
qDebug() << (operations == expectedOperations);
qDebug() << operations;
qDebug() << expectedOperations;
@ -182,10 +233,15 @@ slots:
QLOG_INFO() << "#####################";
MMC->m_version.build = 1;
MMC->m_version.channel = "develop";
MMC->updateChecker()->setChannelListUrl(QUrl::fromLocalFile(QDir::current().absoluteFilePath("tests/data/channels.json")).toString());
auto channels =
QUrl::fromLocalFile(QDir::current().absoluteFilePath("tests/data/channels.json"));
auto root = QUrl::fromLocalFile(QDir::current().absoluteFilePath("tests/data/"));
QLOG_DEBUG() << "channels: " << channels;
QLOG_DEBUG() << "root: " << root;
MMC->updateChecker()->setChannelListUrl(channels.toString());
MMC->updateChecker()->setCurrentChannel("develop");
DownloadUpdateTask task(QUrl::fromLocalFile(QDir::current().absoluteFilePath("tests/data/")).toString(), 2);
DownloadUpdateTask task(root.toString(), 2);
QSignalSpy succeededSpy(&task, SIGNAL(succeeded()));

View File

@ -76,7 +76,7 @@ slots:
<< true
<< true
<< (QList<UpdateChecker::ChannelListEntry>()
<< UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "file://$PWD/tests/data/"}
<< UpdateChecker::ChannelListEntry{"develop", "Develop", "The channel called \"develop\"", "$PWD/tests/data/"}
<< UpdateChecker::ChannelListEntry{"stable", "Stable", "It's stable at least", "ftp://username@host/path/to/stuff"}
<< UpdateChecker::ChannelListEntry{"42", "The Channel", "This is the channel that is going to answer all of your questions", "https://dent.me/tea"});
}

View File

@ -23,13 +23,12 @@ slots:
QTest::addColumn<QString>("path1");
QTest::addColumn<QString>("path2");
#if defined(Q_OS_UNIX)
QTest::newRow("unix 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl";
QTest::newRow("unix 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl";
#elif defined(Q_OS_WIN)
QTest::newRow("win, from C:") << "C:\\abc" << "C:" << "abc\\def";
QTest::newRow("win 1") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc\\def" << "ghi\\jkl";
QTest::newRow("win 2") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc\\def\\" << "ghi\\jkl";
QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc/def" << "ghi/jkl";
QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/def/" << "ghi/jkl";
#if defined(Q_OS_WIN)
QTest::newRow("win native, from C:") << "C:/abc" << "C:" << "abc";
QTest::newRow("win native 1") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def" << "ghi\\jkl";
QTest::newRow("win native 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\def\\" << "ghi\\jkl";
#endif
}
void test_PathCombine1()
@ -48,16 +47,15 @@ slots:
QTest::addColumn<QString>("path2");
QTest::addColumn<QString>("path3");
#if defined(Q_OS_UNIX)
QTest::newRow("unix 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl";
QTest::newRow("unix 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl";
QTest::newRow("unix 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl";
QTest::newRow("unix 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl";
#elif defined(Q_OS_WIN)
QTest::newRow("win 1") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc" << "def" << "ghi\\jkl";
QTest::newRow("win 2") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
QTest::newRow("win 3") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc" << "def\\" << "ghi\\jkl";
QTest::newRow("win 4") << "C:\\abc\\def\\ghi\\jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
QTest::newRow("qt 1") << "/abc/def/ghi/jkl" << "/abc" << "def" << "ghi/jkl";
QTest::newRow("qt 2") << "/abc/def/ghi/jkl" << "/abc/" << "def" << "ghi/jkl";
QTest::newRow("qt 3") << "/abc/def/ghi/jkl" << "/abc" << "def/" << "ghi/jkl";
QTest::newRow("qt 4") << "/abc/def/ghi/jkl" << "/abc/" << "def/" << "ghi/jkl";
#if defined(Q_OS_WIN)
QTest::newRow("win 1") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def" << "ghi\\jkl";
QTest::newRow("win 2") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
QTest::newRow("win 3") << "C:/abc/def/ghi/jkl" << "C:\\abc" << "def\\" << "ghi\\jkl";
QTest::newRow("win 4") << "C:/abc/def/ghi/jkl" << "C:\\abc\\" << "def" << "ghi\\jkl";
#endif
}
void test_PathCombine2()

View File

@ -23,6 +23,9 @@ slots:
QCOMPARE(Util::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
}
// this is only valid on linux
// FIXME: implement on windows, OSX, then test.
#if defined(Q_OS_LINUX)
void test_createShortcut_data()
{
QTest::addColumn<QString>("location");
@ -40,7 +43,7 @@ slots:
#if defined(Q_OS_LINUX)
<< MULTIMC_GET_TEST_FILE("data/tst_userutils-test_createShortcut-unix")
#elif defined(Q_OS_WIN)
<< QString()
<< QByteArray()
#endif
;
}
@ -59,8 +62,10 @@ slots:
//QDir().remove(location);
}
#endif
};
QTEST_GUILESS_MAIN_MULTIMC(UserUtilsTest)
#include "tst_userutils.moc"