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);
|
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)
|
bool Manifest::isValidName(const std::string& name)
|
||||||
{
|
{
|
||||||
const auto len = name.length();
|
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);
|
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::optional<std::string> name;
|
||||||
std::string value;
|
std::string value;
|
||||||
|
|
||||||
std::string full_line;
|
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);
|
std::size_t len = strlen(lbuf);
|
||||||
|
|
||||||
line_number += 1;
|
line_number += 1;
|
||||||
|
|
||||||
if (is.fail()) {
|
|
||||||
throw std::length_error("line too long (" + getErrorPosition(filename, line_number) + ")");
|
|
||||||
}
|
|
||||||
if (len > 0 && lbuf[len - 1] == '\r') {
|
if (len > 0 && lbuf[len - 1] == '\r') {
|
||||||
len -= 1;
|
len -= 1;
|
||||||
}
|
}
|
||||||
@ -130,6 +136,9 @@ int Manifest::readAttributes(section_t& section, std::istream& is, lbuf_t lbuf,
|
|||||||
}
|
}
|
||||||
section[*name] = value;
|
section[*name] = value;
|
||||||
}
|
}
|
||||||
|
if (!is.eof() && is.fail()) {
|
||||||
|
throw std::length_error("line too long (" + getErrorPosition(filename, line_number) + ")");
|
||||||
|
}
|
||||||
return 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)
|
void Manifest::read(std::istream& is, const std::optional<std::string>& jar_filename)
|
||||||
{
|
{
|
||||||
// Line buffer
|
// Line buffer
|
||||||
char lbuf[MAX_LINE_LENGTH];
|
char lbuf[MANIFEST_MAX_LINE_LENGTH];
|
||||||
// Read the main attributes for the manifest
|
// Read the main attributes for the manifest
|
||||||
int line_number = readAttributes(m_main_section, is, lbuf, jar_filename, 0);
|
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;
|
bool skip_empty_lines = true;
|
||||||
std::optional<std::string> lastline;
|
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);
|
std::size_t len = strlen(lbuf);
|
||||||
|
|
||||||
line_number += 1;
|
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') {
|
if (len > 0 && lbuf[len - 1] == '\r') {
|
||||||
len -= 1;
|
len -= 1;
|
||||||
}
|
}
|
||||||
@ -192,10 +198,13 @@ void Manifest::read(std::istream& is, const std::optional<std::string>& jar_file
|
|||||||
lastline = std::nullopt;
|
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);
|
line_number = readAttributes(attr, is, lbuf, jar_filename, line_number);
|
||||||
|
|
||||||
name = std::nullopt;
|
name = std::nullopt;
|
||||||
skip_empty_lines = true;
|
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 <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
constexpr const int MAX_LINE_LENGTH = 512;
|
constexpr const int MANIFEST_MAX_LINE_LENGTH = 512;
|
||||||
using lbuf_t = char[MAX_LINE_LENGTH];
|
using lbuf_t = char[MANIFEST_MAX_LINE_LENGTH];
|
||||||
|
|
||||||
using section_t = std::map<std::string, std::string>;
|
using manifest_section_t = std::map<std::string, std::string>;
|
||||||
using sections_t = std::map<std::string, section_t>;
|
using manifest_sections_t = std::map<std::string, manifest_section_t>;
|
||||||
using manifest_t = std::pair<section_t, sections_t>;
|
|
||||||
|
|
||||||
class Manifest {
|
class Manifest {
|
||||||
public:
|
public:
|
||||||
Manifest(std::istream& is);
|
Manifest(std::istream& is);
|
||||||
section_t& getMainAttributes() { return m_main_section; }
|
Manifest(std::istream& is, const std::string& jar_filename);
|
||||||
sections_t& getEntries() { return m_individual_sections; }
|
Manifest(const Manifest& other)
|
||||||
section_t& getAttributes(std::string& name) { return m_individual_sections.at(name); }
|
{
|
||||||
|
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:
|
private:
|
||||||
static std::string getErrorPosition(const std::optional<std::string>& filename, int line_number);
|
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 std::optional<std::string> parseName(lbuf_t lbuf, std::size_t len);
|
||||||
static bool isValidName(const std::string& name);
|
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);
|
void read(std::istream& is, const std::optional<std::string>& jar_filename);
|
||||||
section_t m_main_section;
|
manifest_section_t m_main_section;
|
||||||
sections_t m_individual_sections;
|
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
|
ecm_add_test(TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
||||||
TEST_NAME TexturePackParse)
|
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
|
ecm_add_test(DataPackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test
|
||||||
TEST_NAME DataPackParse)
|
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