Add tests for MANIFEST.MF parser

Signed-off-by: Evan Goode <mail@evangoo.de>
This commit is contained in:
Evan Goode 2024-07-27 00:47:56 -04:00
parent c7de09bcc6
commit e1041ddc61
4 changed files with 178 additions and 22 deletions

View File

@ -60,6 +60,11 @@ Manifest::Manifest(std::istream& is)
read(is, std::nullopt);
}
Manifest::Manifest(std::istream& is, const std::string& jar_filename)
{
read(is, jar_filename);
}
bool Manifest::isValidName(const std::string& name)
{
const auto len = name.length();
@ -74,21 +79,22 @@ std::string Manifest::getErrorPosition(const std::optional<std::string>& filenam
return "manifest of " + *filename + ":" + std::to_string(line_number);
}
int Manifest::readAttributes(section_t& section, std::istream& is, lbuf_t lbuf, const std::optional<std::string>& filename, int line_number)
int Manifest::readAttributes(manifest_section_t& section,
std::istream& is,
lbuf_t lbuf,
const std::optional<std::string>& filename,
int line_number)
{
std::optional<std::string> name;
std::string value;
std::string full_line;
while (!is.eof() && is.getline(lbuf, MAX_LINE_LENGTH)) {
while (!is.eof() && is.getline(lbuf, MANIFEST_MAX_LINE_LENGTH)) {
std::size_t len = strlen(lbuf);
line_number += 1;
if (is.fail()) {
throw std::length_error("line too long (" + getErrorPosition(filename, line_number) + ")");
}
if (len > 0 && lbuf[len - 1] == '\r') {
len -= 1;
}
@ -130,6 +136,9 @@ int Manifest::readAttributes(section_t& section, std::istream& is, lbuf_t lbuf,
}
section[*name] = value;
}
if (!is.eof() && is.fail()) {
throw std::length_error("line too long (" + getErrorPosition(filename, line_number) + ")");
}
return line_number;
}
@ -144,7 +153,7 @@ std::optional<std::string> Manifest::parseName(lbuf_t lbuf, std::size_t len)
void Manifest::read(std::istream& is, const std::optional<std::string>& jar_filename)
{
// Line buffer
char lbuf[MAX_LINE_LENGTH];
char lbuf[MANIFEST_MAX_LINE_LENGTH];
// Read the main attributes for the manifest
int line_number = readAttributes(m_main_section, is, lbuf, jar_filename, 0);
@ -153,14 +162,11 @@ void Manifest::read(std::istream& is, const std::optional<std::string>& jar_file
bool skip_empty_lines = true;
std::optional<std::string> lastline;
while (!is.eof() && is.getline(lbuf, MAX_LINE_LENGTH)) {
while (!is.eof() && is.getline(lbuf, MANIFEST_MAX_LINE_LENGTH)) {
std::size_t len = strlen(lbuf);
line_number += 1;
if (is.fail()) {
throw std::length_error("manifest line too long (" + getErrorPosition(jar_filename, line_number) + ")");
}
if (len > 0 && lbuf[len - 1] == '\r') {
len -= 1;
}
@ -192,10 +198,13 @@ void Manifest::read(std::istream& is, const std::optional<std::string>& jar_file
lastline = std::nullopt;
}
section_t& attr = m_individual_sections[*name];
manifest_section_t& attr = m_individual_sections[*name];
line_number = readAttributes(attr, is, lbuf, jar_filename, line_number);
name = std::nullopt;
skip_empty_lines = true;
}
if (!is.eof() && is.fail()) {
throw std::length_error("manifest line too long (" + getErrorPosition(jar_filename, line_number) + ")");
}
}

View File

@ -20,26 +20,48 @@
#include <optional>
#include <string>
constexpr const int MAX_LINE_LENGTH = 512;
using lbuf_t = char[MAX_LINE_LENGTH];
constexpr const int MANIFEST_MAX_LINE_LENGTH = 512;
using lbuf_t = char[MANIFEST_MAX_LINE_LENGTH];
using section_t = std::map<std::string, std::string>;
using sections_t = std::map<std::string, section_t>;
using manifest_t = std::pair<section_t, sections_t>;
using manifest_section_t = std::map<std::string, std::string>;
using manifest_sections_t = std::map<std::string, manifest_section_t>;
class Manifest {
public:
Manifest(std::istream& is);
section_t& getMainAttributes() { return m_main_section; }
sections_t& getEntries() { return m_individual_sections; }
section_t& getAttributes(std::string& name) { return m_individual_sections.at(name); }
Manifest(std::istream& is, const std::string& jar_filename);
Manifest(const Manifest& other)
{
m_main_section = other.m_main_section;
m_individual_sections = other.m_individual_sections;
}
manifest_section_t& getMainAttributes() { return m_main_section; }
manifest_sections_t& getEntries() { return m_individual_sections; }
manifest_section_t& getAttributes(const std::string& name) { return m_individual_sections.at(name); }
Manifest& operator=(const Manifest& other)
{
if (this == &other) {
return *this;
}
m_main_section = other.m_main_section;
m_individual_sections = other.m_individual_sections;
return *this;
}
bool operator==(const Manifest& other) const
{
return m_main_section == other.m_main_section && m_individual_sections == other.m_individual_sections;
}
private:
static std::string getErrorPosition(const std::optional<std::string>& filename, int line_number);
static std::optional<std::string> parseName(lbuf_t lbuf, std::size_t len);
static bool isValidName(const std::string& name);
int readAttributes(section_t& section, std::istream& is, lbuf_t lbuf, const std::optional<std::string>& jar_filename, int line_number);
int readAttributes(manifest_section_t& section,
std::istream& is,
lbuf_t lbuf,
const std::optional<std::string>& jar_filename,
int line_number);
void read(std::istream& is, const std::optional<std::string>& jar_filename);
section_t m_main_section;
sections_t m_individual_sections;
manifest_section_t m_main_section;
manifest_sections_t m_individual_sections;
};

View File

@ -27,6 +27,9 @@ ecm_add_test(ResourceModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION
ecm_add_test(TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME TexturePackParse)
ecm_add_test(Manifest_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME Manifest)
ecm_add_test(DataPackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
TEST_NAME DataPackParse)

122
tests/Manifest_test.cpp Normal file
View File

@ -0,0 +1,122 @@
#include <QTest>
#include <QTimer>
#include <iostream>
#include "Manifest.h"
class ManifestTest : public QObject {
Q_OBJECT
private slots:
void test_emptyManifest()
{
std::istringstream iss;
const auto& manifest = new Manifest(iss);
QVERIFY(manifest->getMainAttributes().empty());
QVERIFY(manifest->getEntries().empty());
}
void test_parseManifest()
{
std::string manifest_text{ R"(Manifest-Version: 1.0
Created-By: 1.8.0 (Oracle Inc.)
Name: common/class1.class
SHA-256-Digest: D7fzW7bq0W+7YRCfxfZ4LY5LlCy+PXisjRgMCIiebS4=
Name: common/class2.class
SHA-256-Digest: TwUa/b/a2EQRsHWKupdFAR7S/BTeL52xTBvaB8C78Kc=
)" };
std::istringstream iss{ manifest_text };
const auto& manifest = new Manifest(iss);
QVERIFY(manifest->getEntries().size() == 2);
auto& main_attributes = manifest->getMainAttributes();
QVERIFY(main_attributes.size() == 2);
QVERIFY(main_attributes["Manifest-Version"] == "1.0");
QVERIFY(main_attributes["Created-By"] == "1.8.0 (Oracle Inc.)");
auto& class1_attributes = manifest->getAttributes("common/class1.class");
QVERIFY(class1_attributes.size() == 1);
QVERIFY(class1_attributes["SHA-256-Digest"] == "D7fzW7bq0W+7YRCfxfZ4LY5LlCy+PXisjRgMCIiebS4=");
auto& class2_attributes = manifest->getAttributes("common/class2.class");
QVERIFY(class2_attributes.size() == 1);
QVERIFY(class2_attributes["SHA-256-Digest"] == "TwUa/b/a2EQRsHWKupdFAR7S/BTeL52xTBvaB8C78Kc=");
// Manifest should parse even without the trailing newline
std::string manifest_text_no_newline{ manifest_text };
manifest_text_no_newline.pop_back();
std::istringstream iss_no_newline{ manifest_text_no_newline };
const auto& manifest_no_newline = new Manifest(iss_no_newline);
QVERIFY(*manifest_no_newline == *manifest);
}
void test_invalidName()
{
std::istringstream iss{ R"(Manifest-Version: 1.0
A-Name-That-Is-Way-Too-Loooooooooooooooooooooooooooooooooooooooooooooooonoooooooooong: 1
)" };
bool caught = false;
try {
new Manifest(iss);
} catch (const std::runtime_error&) {
caught = true;
}
QVERIFY(caught);
}
void test_lineTooLong()
{
std::string manifest_text{ "Manifest-Version: " };
manifest_text.append(std::string(MANIFEST_MAX_LINE_LENGTH, '1'));
std::istringstream iss{ manifest_text };
bool caught = false;
try {
new Manifest(iss);
} catch (const std::length_error&) {
caught = true;
}
QVERIFY(caught);
}
void test_misplacedContinuation()
{
std::istringstream iss{ " Manifest-Version: 1.0" };
bool caught = false;
try {
new Manifest(iss);
} catch (const std::runtime_error&) {
caught = true;
}
QVERIFY(caught);
}
void test_misingColon()
{
std::istringstream iss{ "Manifest-Version 1.0" };
bool caught = false;
try {
new Manifest(iss);
} catch (const std::runtime_error&) {
caught = true;
}
QVERIFY(caught);
}
void test_misingSpace()
{
std::istringstream iss{ "Manifest-Version:1.0" };
bool caught = false;
try {
new Manifest(iss);
} catch (const std::runtime_error&) {
caught = true;
}
QVERIFY(caught);
}
};
QTEST_GUILESS_MAIN(ManifestTest)
#include "Manifest_test.moc"