Files
chatterino2/tests/src/lib/Snapshot.cpp

262 lines
6.8 KiB
C++

#include "lib/Snapshot.hpp"
#include "common/Literals.hpp"
#include <QDebug>
#include <QDir>
#include <QJsonArray>
#include <QJsonDocument>
#include <QStringBuilder>
namespace {
using namespace chatterino::literals;
bool compareJson(const QJsonValue &expected, const QJsonValue &got,
const QString &context)
{
if (expected == got)
{
return true;
}
if (expected.type() != got.type())
{
qWarning() << context
<< "- mismatching type - expected:" << expected.type()
<< "got:" << got.type();
return false;
}
switch (expected.type())
{
case QJsonValue::Array: {
auto expArr = expected.toArray();
auto gotArr = got.toArray();
if (expArr.size() != gotArr.size())
{
qWarning() << context << "- Mismatching array size - expected:"
<< expArr.size() << "got:" << gotArr.size();
return false;
}
for (QJsonArray::size_type i = 0; i < expArr.size(); i++)
{
if (!compareJson(expArr[i], gotArr[i],
context % '[' % QString::number(i) % ']'))
{
return false;
}
}
}
break; // unreachable
case QJsonValue::Object: {
auto expObj = expected.toObject();
auto gotObj = got.toObject();
if (expObj.size() != gotObj.size())
{
qWarning() << context << "- Mismatching object size - expected:"
<< expObj.size() << "got:" << gotObj.size();
return false;
}
for (auto it = expObj.constBegin(); it != expObj.constEnd(); it++)
{
if (!gotObj.contains(it.key()))
{
qWarning() << context << "- Object doesn't contain key"
<< it.key();
return false;
}
if (!compareJson(it.value(), gotObj[it.key()],
context % '.' % it.key()))
{
return false;
}
}
}
break;
case QJsonValue::Null:
case QJsonValue::Bool:
case QJsonValue::Double:
case QJsonValue::String:
case QJsonValue::Undefined:
break;
}
qWarning() << context << "- expected:" << expected << "got:" << got;
return false;
}
void mergeJson(QJsonObject &base, const QJsonObject &additional)
{
for (auto it = additional.begin(); it != additional.end(); it++)
{
auto ref = base[it.key()];
if (ref.isArray())
{
// there's no way of pushing to the array without detaching first
auto arr = ref.toArray();
if (!it->isArray())
{
throw std::runtime_error("Mismatched types");
}
// append all additional values
auto addArr = it->toArray();
for (auto v : addArr)
{
arr.append(v);
}
ref = arr;
continue;
}
if (ref.isObject())
{
// same here, detach first and overwrite
auto obj = ref.toObject();
if (!it->isObject())
{
throw std::runtime_error("Mismatched types");
}
mergeJson(obj, it->toObject());
ref = obj;
continue;
}
ref = it.value(); // overwrite for simple types/non-existent keys
}
}
QDir baseDir(const QString &category)
{
QDir snapshotDir(QStringLiteral(__FILE__));
snapshotDir.cd("../../../snapshots/");
snapshotDir.cd(category);
return snapshotDir;
}
QString filePath(const QString &category, const QString &name)
{
return baseDir(category).filePath(name);
}
} // namespace
namespace chatterino::testlib {
std::unique_ptr<Snapshot> Snapshot::read(QString category, QString name)
{
if (!name.endsWith(u".json"))
{
name.append(u".json");
}
QFile file(filePath(category, name));
if (!file.open(QFile::ReadOnly))
{
throw std::runtime_error("Failed to open file");
}
auto content = file.readAll();
file.close();
const auto doc = QJsonDocument::fromJson(content).object();
return std::unique_ptr<Snapshot>(
new Snapshot(std::move(category), std::move(name), doc));
}
QStringList Snapshot::discover(const QString &category)
{
auto files =
baseDir(category).entryList(QDir::NoDotAndDotDot | QDir::Files);
for (auto &file : files)
{
file.remove(".json");
}
return files;
}
QStringList Snapshot::discoverNested(const QString &category)
{
auto directories =
baseDir(category).entryList(QDir::NoDotAndDotDot | QDir::Dirs);
QStringList all;
for (const auto &dir : directories)
{
auto d = baseDir(category);
d.cd(dir);
auto files = d.entryList(QDir::NoDotAndDotDot | QDir::Files);
for (const auto &file : files)
{
QStringView view(file);
if (view.endsWith(u".json"))
{
view = view.sliced(0, view.size() - 5);
}
all.append(dir % u'/' % view);
}
}
return all;
}
bool Snapshot::run(const QJsonValue &got, bool updateSnapshots) const
{
if (updateSnapshots)
{
this->write(got);
return true;
}
return compareJson(this->output_, got, QStringLiteral("output"));
}
Snapshot::Snapshot(QString category, QString name, const QJsonObject &root)
: category_(std::move(category))
, name_(std::move(name))
, input_(root["input"_L1])
, params_(root["params"_L1].toObject())
, settings_(root["settings"_L1].toObject())
, output_(root["output"_L1])
{
}
void Snapshot::write(const QJsonValue &got) const
{
QFile file(filePath(this->category_, this->name_));
if (!file.open(QFile::WriteOnly))
{
throw std::runtime_error("Failed to open file");
}
QJsonObject obj{
{"input"_L1, this->input_},
{"output"_L1, got},
};
if (!this->params_.isEmpty())
{
obj.insert("params"_L1, this->params_);
}
if (!this->settings_.isEmpty())
{
obj.insert("settings"_L1, this->settings_);
}
file.write(QJsonDocument{obj}.toJson());
file.close();
}
QByteArray Snapshot::mergedSettings(const QByteArray &base) const
{
auto baseDoc = QJsonDocument::fromJson(base);
if (!baseDoc.isObject())
{
throw std::runtime_error("Invalid base settings");
}
auto baseObj = baseDoc.object();
mergeJson(baseObj, this->settings_);
baseDoc.setObject(baseObj);
return baseDoc.toJson(QJsonDocument::Compact);
}
} // namespace chatterino::testlib