Add tests for MANIFEST.MF parser
Signed-off-by: Evan Goode <mail@evangoo.de>
This commit is contained in:
parent
c7de09bcc6
commit
e1041ddc61
@ -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) + ")");
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
122
tests/Manifest_test.cpp
Normal 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"
|
Loading…
Reference in New Issue
Block a user