Skip to content

Commit

Permalink
Add tests for MANIFEST.MF parser
Browse files Browse the repository at this point in the history
Signed-off-by: Evan Goode <[email protected]>
  • Loading branch information
evan-goode committed Jul 27, 2024
1 parent c7de09b commit e1041dd
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 22 deletions.
31 changes: 20 additions & 11 deletions launcher/Manifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}

Expand All @@ -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);

Expand All @@ -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;
}
Expand Down Expand Up @@ -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) + ")");
}
}
44 changes: 33 additions & 11 deletions launcher/Manifest.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
3 changes: 3 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
122 changes: 122 additions & 0 deletions tests/Manifest_test.cpp
Original file line number Diff line number Diff line change
@@ -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"

0 comments on commit e1041dd

Please sign in to comment.