Tests: Add for CueParser and ElfFile

This commit is contained in:
Stenzek
2025-09-24 23:38:03 +10:00
parent 8e65beb736
commit ea11ce2dd5
7 changed files with 795 additions and 0 deletions

View File

@@ -1,5 +1,7 @@
add_executable(util-tests
animated_image_tests.cpp
elf_parser_tests.cpp
cue_parser_tests.cpp
image_tests.cpp
)

View File

@@ -0,0 +1,372 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "util/cue_parser.h"
#include "common/error.h"
#include <gtest/gtest.h>
// Test basic valid CUE sheet
TEST(CueParser, BasicValidCue)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 MODE2/2352\n"
"INDEX 01 00:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error));
const CueParser::Track* track = cue_file.GetTrack(1);
ASSERT_NE(track, nullptr);
EXPECT_EQ(track->number, 1);
EXPECT_EQ(track->mode, CueParser::TrackMode::Mode2Raw);
EXPECT_EQ(track->file, "game.bin");
EXPECT_EQ(track->file_format, CueParser::FileFormat::Binary);
const CueParser::MSF* index1 = track->GetIndex(1);
ASSERT_NE(index1, nullptr);
EXPECT_EQ(index1->minute, 0);
EXPECT_EQ(index1->second, 0);
EXPECT_EQ(index1->frame, 0);
}
// Test multiple tracks
TEST(CueParser, MultipleTracksCue)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 MODE1/2352\n"
"INDEX 01 00:00:00\n"
"TRACK 02 AUDIO\n"
"INDEX 00 02:30:65\n"
"INDEX 01 02:31:45\n"
"TRACK 03 MODE2/2336\n"
"INDEX 01 05:45:20\n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error));
// Test track 1
const CueParser::Track* track1 = cue_file.GetTrack(1);
ASSERT_NE(track1, nullptr);
EXPECT_EQ(track1->number, 1);
EXPECT_EQ(track1->mode, CueParser::TrackMode::Mode1Raw);
// Test track 2
const CueParser::Track* track2 = cue_file.GetTrack(2);
ASSERT_NE(track2, nullptr);
EXPECT_EQ(track2->number, 2);
EXPECT_EQ(track2->mode, CueParser::TrackMode::Audio);
// Check track 2 indices
const CueParser::MSF* index0 = track2->GetIndex(0);
ASSERT_NE(index0, nullptr);
EXPECT_EQ(index0->minute, 2);
EXPECT_EQ(index0->second, 30);
EXPECT_EQ(index0->frame, 65);
const CueParser::MSF* index1 = track2->GetIndex(1);
ASSERT_NE(index1, nullptr);
EXPECT_EQ(index1->minute, 2);
EXPECT_EQ(index1->second, 31);
EXPECT_EQ(index1->frame, 45);
// Test track 3
const CueParser::Track* track3 = cue_file.GetTrack(3);
ASSERT_NE(track3, nullptr);
EXPECT_EQ(track3->number, 3);
EXPECT_EQ(track3->mode, CueParser::TrackMode::Mode2);
}
// Test different track modes
TEST(CueParser, DifferentTrackModes)
{
const std::string cue = "FILE \"audio.wav\" WAVE\n"
"TRACK 01 AUDIO\n"
"INDEX 01 00:00:00\n"
"FILE \"data.bin\" BINARY\n"
"TRACK 02 MODE1/2048\n"
"INDEX 01 00:00:00\n"
"TRACK 03 MODE1/2352\n"
"INDEX 01 15:34:50\n"
"TRACK 04 MODE2/2048\n"
"INDEX 01 30:10:00\n"
"TRACK 05 MODE2/2352\n"
"INDEX 01 45:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error));
// Verify track modes
const CueParser::Track* track1 = cue_file.GetTrack(1);
ASSERT_NE(track1, nullptr);
EXPECT_EQ(track1->mode, CueParser::TrackMode::Audio);
EXPECT_EQ(track1->file_format, CueParser::FileFormat::Wave);
const CueParser::Track* track2 = cue_file.GetTrack(2);
ASSERT_NE(track2, nullptr);
EXPECT_EQ(track2->mode, CueParser::TrackMode::Mode1);
EXPECT_EQ(track2->file_format, CueParser::FileFormat::Binary);
const CueParser::Track* track3 = cue_file.GetTrack(3);
ASSERT_NE(track3, nullptr);
EXPECT_EQ(track3->mode, CueParser::TrackMode::Mode1Raw);
const CueParser::Track* track4 = cue_file.GetTrack(4);
ASSERT_NE(track4, nullptr);
EXPECT_EQ(track4->mode, CueParser::TrackMode::Mode2Form1);
const CueParser::Track* track5 = cue_file.GetTrack(5);
ASSERT_NE(track5, nullptr);
EXPECT_EQ(track5->mode, CueParser::TrackMode::Mode2Raw);
}
// Test track flags
TEST(CueParser, TrackFlags)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"FLAGS PRE DCP 4CH\n"
"INDEX 01 00:00:00\n"
"TRACK 02 AUDIO\n"
"FLAGS SCMS\n"
"INDEX 01 03:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error));
const CueParser::Track* track1 = cue_file.GetTrack(1);
ASSERT_NE(track1, nullptr);
EXPECT_TRUE(track1->HasFlag(CueParser::TrackFlag::PreEmphasis));
EXPECT_TRUE(track1->HasFlag(CueParser::TrackFlag::CopyPermitted));
EXPECT_TRUE(track1->HasFlag(CueParser::TrackFlag::FourChannelAudio));
EXPECT_FALSE(track1->HasFlag(CueParser::TrackFlag::SerialCopyManagement));
const CueParser::Track* track2 = cue_file.GetTrack(2);
ASSERT_NE(track2, nullptr);
EXPECT_FALSE(track2->HasFlag(CueParser::TrackFlag::PreEmphasis));
EXPECT_FALSE(track2->HasFlag(CueParser::TrackFlag::CopyPermitted));
EXPECT_FALSE(track2->HasFlag(CueParser::TrackFlag::FourChannelAudio));
EXPECT_TRUE(track2->HasFlag(CueParser::TrackFlag::SerialCopyManagement));
}
// Test PREGAP handling
TEST(CueParser, PregapHandling)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"INDEX 01 00:00:00\n"
"TRACK 02 AUDIO\n"
"PREGAP 00:02:00\n"
"INDEX 01 03:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error));
const CueParser::Track* track1 = cue_file.GetTrack(1);
ASSERT_NE(track1, nullptr);
EXPECT_FALSE(track1->zero_pregap.has_value());
const CueParser::Track* track2 = cue_file.GetTrack(2);
ASSERT_NE(track2, nullptr);
ASSERT_TRUE(track2->zero_pregap.has_value());
EXPECT_EQ(track2->zero_pregap->minute, 0);
EXPECT_EQ(track2->zero_pregap->second, 2);
EXPECT_EQ(track2->zero_pregap->frame, 0);
}
// Test track lengths are correctly calculated
TEST(CueParser, TrackLengthCalculation)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"INDEX 01 00:00:00\n"
"TRACK 02 AUDIO\n"
"INDEX 00 02:30:00\n"
"INDEX 01 02:32:00\n"
"TRACK 03 AUDIO\n"
"INDEX 01 05:45:20\n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error));
const CueParser::Track* track1 = cue_file.GetTrack(1);
ASSERT_NE(track1, nullptr);
ASSERT_TRUE(track1->length.has_value());
// Track 1 length should be calculated to end at track 2's index 0
EXPECT_EQ(track1->length->minute, 2);
EXPECT_EQ(track1->length->second, 30);
EXPECT_EQ(track1->length->frame, 0);
const CueParser::Track* track2 = cue_file.GetTrack(2);
ASSERT_NE(track2, nullptr);
ASSERT_TRUE(track2->length.has_value());
// Track 2 length should be calculated to end at track 3's index 1
EXPECT_EQ(track2->length->minute, 3);
EXPECT_EQ(track2->length->second, 13);
EXPECT_EQ(track2->length->frame, 20);
}
// Test missing FILE command
TEST(CueParser, MissingFileCommand)
{
const std::string cue = "TRACK 01 AUDIO\n"
"INDEX 01 00:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_FALSE(cue_file.Parse(cue, &error));
}
// Test invalid track number
TEST(CueParser, InvalidTrackNumber)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 100 AUDIO\n"
"INDEX 01 00:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_FALSE(cue_file.Parse(cue, &error));
}
// Test missing index 1
TEST(CueParser, MissingIndex1)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"INDEX 00 00:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_FALSE(cue_file.Parse(cue, &error));
}
// Test invalid index number
TEST(CueParser, InvalidIndexNumber)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"INDEX 100 00:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_FALSE(cue_file.Parse(cue, &error));
}
// Test invalid MSF format
TEST(CueParser, InvalidMSFFormat)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"INDEX 01 00:99:00\n";
CueParser::File cue_file;
Error error;
ASSERT_FALSE(cue_file.Parse(cue, &error));
}
// Test duplicate index
TEST(CueParser, DuplicateIndex)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"INDEX 01 00:00:00\n"
"INDEX 01 00:01:00\n";
CueParser::File cue_file;
Error error;
ASSERT_FALSE(cue_file.Parse(cue, &error));
}
// Test reverse index order
TEST(CueParser, ReverseIndexOrder)
{
const std::string cue = "FILE \"game.bin\" BINARY\n"
"TRACK 01 AUDIO\n"
"INDEX 02 00:01:00\n"
"INDEX 01 00:02:00\n";
CueParser::File cue_file;
Error error;
ASSERT_FALSE(cue_file.Parse(cue, &error));
}
// Test handling of comments and whitespace
TEST(CueParser, CommentsAndWhitespace)
{
const std::string cue = "REM This is a comment\n"
" FILE \"game.bin\" BINARY \n"
"\n"
"REM Another comment\n"
" TRACK 01 MODE2/2352 \n"
" INDEX 01 00:00:00 \n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error));
const CueParser::Track* track = cue_file.GetTrack(1);
ASSERT_NE(track, nullptr);
EXPECT_EQ(track->mode, CueParser::TrackMode::Mode2Raw);
}
// Test handling of multiple files
TEST(CueParser, MultipleFiles)
{
const std::string cue = "FILE \"track1.bin\" BINARY\n"
"TRACK 01 MODE1/2352\n"
"INDEX 01 00:00:00\n"
"FILE \"track2.wav\" WAVE\n"
"TRACK 02 AUDIO\n"
"INDEX 01 00:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error));
const CueParser::Track* track1 = cue_file.GetTrack(1);
ASSERT_NE(track1, nullptr);
EXPECT_EQ(track1->file, "track1.bin");
EXPECT_EQ(track1->file_format, CueParser::FileFormat::Binary);
const CueParser::Track* track2 = cue_file.GetTrack(2);
ASSERT_NE(track2, nullptr);
EXPECT_EQ(track2->file, "track2.wav");
EXPECT_EQ(track2->file_format, CueParser::FileFormat::Wave);
}
// Test ignoring unsupported metadata
TEST(CueParser, IgnoreUnsupportedMetadata)
{
const std::string cue = "CATALOG 1234567890123\n"
"TITLE \"Game Title\"\n"
"PERFORMER \"Developer\"\n"
"FILE \"game.bin\" BINARY\n"
"TRACK 01 MODE2/2352\n"
"TITLE \"Track Title\"\n"
"INDEX 01 00:00:00\n";
CueParser::File cue_file;
Error error;
ASSERT_TRUE(cue_file.Parse(cue, &error));
const CueParser::Track* track = cue_file.GetTrack(1);
ASSERT_NE(track, nullptr);
EXPECT_EQ(track->number, 1);
}

View File

@@ -0,0 +1,392 @@
// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "util/elf_file.h"
#include "common/error.h"
#include <gtest/gtest.h>
namespace {
// Helper to create minimal valid ELF file data
ELFFile::DataArray CreateValidELFData()
{
// Create a minimal valid ELF file (32-bit MIPS)
constexpr size_t elf_size = 768;
ELFFile::DataArray data(elf_size);
std::memset(data.data(), 0, data.size());
// ELF Header
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
ehdr->e_ident[0] = 0x7F; // Magic bytes
ehdr->e_ident[1] = 'E';
ehdr->e_ident[2] = 'L';
ehdr->e_ident[3] = 'F';
ehdr->e_type = ELFFile::ET_EXEC;
ehdr->e_machine = ELFFile::EM_MIPS;
ehdr->e_version = 1;
ehdr->e_entry = 0x80010000; // Entry point
ehdr->e_phoff = sizeof(ELFFile::Elf32_Ehdr); // Program header table right after ELF header
ehdr->e_shoff =
sizeof(ELFFile::Elf32_Ehdr) + 2 * sizeof(ELFFile::Elf32_Phdr); // Section header table after program headers
ehdr->e_flags = 0;
ehdr->e_ehsize = sizeof(ELFFile::Elf32_Ehdr);
ehdr->e_phentsize = sizeof(ELFFile::Elf32_Phdr);
ehdr->e_phnum = 2; // Two program headers
ehdr->e_shentsize = sizeof(ELFFile::Elf32_Shdr);
ehdr->e_shnum = 3; // Three section headers (null + .text + .shstrtab)
ehdr->e_shstrndx = 2; // String table is section 2
// Program Headers
auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
// First program header - loadable segment with code
phdr[0].p_type = ELFFile::PT_LOAD;
phdr[0].p_offset = 0x100; // Start of section data
phdr[0].p_vaddr = 0x80010000; // Virtual address matching entry point
phdr[0].p_filesz = 0x100; // Size in file
phdr[0].p_memsz = 0x100; // Size in memory
phdr[0].p_flags = 5; // Read + execute
phdr[0].p_align = 0x1000; // Page alignment
// Second program header - non-loadable segment
phdr[1].p_type = ELFFile::PT_NOTE;
// Section Headers
auto* shdr = reinterpret_cast<ELFFile::Elf32_Shdr*>(data.data() + ehdr->e_shoff);
// Section 0 - NULL section
shdr[0].sh_name = 0;
shdr[0].sh_type = ELFFile::SHT_NULL;
// Section 1 - .text
shdr[1].sh_name = 1; // Offset in string table
shdr[1].sh_type = ELFFile::SHT_PROGBITS;
shdr[1].sh_flags = 6; // Executable, allocated
shdr[1].sh_addr = 0x80010000;
shdr[1].sh_offset = 0x100; // Start of section data
shdr[1].sh_size = 0x100; // Size
shdr[1].sh_link = 0;
shdr[1].sh_info = 0;
shdr[1].sh_addralign = 4;
shdr[1].sh_entsize = 0;
// Section 2 - .shstrtab (string table)
shdr[2].sh_name = 7; // Offset in string table
shdr[2].sh_type = ELFFile::SHT_STRTAB;
shdr[2].sh_flags = 0;
shdr[2].sh_addr = 0;
shdr[2].sh_offset = 0x200; // String table location
shdr[2].sh_size = 0x20; // String table size
shdr[2].sh_link = 0;
shdr[2].sh_info = 0;
shdr[2].sh_addralign = 1;
shdr[2].sh_entsize = 0;
// String table section
char* strtab = reinterpret_cast<char*>(data.data() + shdr[2].sh_offset);
strtab[0] = '\0'; // Empty string at index 0
strcpy(strtab + 1, ".text"); // Section name at index 1
strcpy(strtab + 7, ".shstrtab"); // Section name at index 7
// Add some fake code to the .text section
u8* text = data.data() + shdr[1].sh_offset;
memset(text, 0xAA, shdr[1].sh_size);
return data;
}
// Helper to create invalid ELF data (wrong magic)
ELFFile::DataArray CreateInvalidELFData()
{
auto data = CreateValidELFData();
data[1] = 'X'; // Corrupt magic number
return data;
}
// Helper to create ELF with wrong machine type
ELFFile::DataArray CreateWrongMachineELFData()
{
auto data = CreateValidELFData();
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
ehdr->e_machine = 0x42; // Not MIPS
return data;
}
// Helper to create ELF with missing entry point
ELFFile::DataArray CreateMissingEntryELFData()
{
auto data = CreateValidELFData();
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
// auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
// Set entry point outside of loadable segment
ehdr->e_entry = 0x90000000;
return data;
}
// Helper to create ELF with out-of-range program header
ELFFile::DataArray CreateOutOfRangeProgramHeaderELFData()
{
auto data = CreateValidELFData();
auto* ehdr = reinterpret_cast<ELFFile::Elf32_Ehdr*>(data.data());
auto* phdr = reinterpret_cast<ELFFile::Elf32_Phdr*>(data.data() + ehdr->e_phoff);
// Set offset and size to be out of file range
phdr[0].p_offset = static_cast<u32>(data.size()) - 10;
phdr[0].p_filesz = 100;
return data;
}
class ELFParserTest : public ::testing::Test
{
protected:
void SetUp() override
{
valid_elf_data = CreateValidELFData();
invalid_elf_data = CreateInvalidELFData();
wrong_machine_elf_data = CreateWrongMachineELFData();
missing_entry_elf_data = CreateMissingEntryELFData();
out_of_range_program_header_elf_data = CreateOutOfRangeProgramHeaderELFData();
}
ELFFile::DataArray valid_elf_data;
ELFFile::DataArray invalid_elf_data;
ELFFile::DataArray wrong_machine_elf_data;
ELFFile::DataArray missing_entry_elf_data;
ELFFile::DataArray out_of_range_program_header_elf_data;
};
} // namespace
TEST_F(ELFParserTest, ValidELFHeader)
{
Error error;
EXPECT_TRUE(ELFFile::IsValidElfHeader(valid_elf_data.cspan(), &error));
EXPECT_FALSE(error.IsValid());
// Test the static header checking method
const auto& header = *reinterpret_cast<const ELFFile::Elf32_Ehdr*>(valid_elf_data.data());
EXPECT_TRUE(ELFFile::IsValidElfHeader(header, &error));
}
TEST_F(ELFParserTest, InvalidELFHeader)
{
Error error;
EXPECT_FALSE(ELFFile::IsValidElfHeader(invalid_elf_data.cspan(), &error));
EXPECT_TRUE(error.IsValid());
}
TEST_F(ELFParserTest, WrongMachineType)
{
Error error;
EXPECT_FALSE(ELFFile::IsValidElfHeader(wrong_machine_elf_data.cspan(), &error));
EXPECT_TRUE(error.IsValid());
}
TEST_F(ELFParserTest, TooSmallBuffer)
{
Error error;
std::span<const u8> small_span = valid_elf_data.cspan(10); // Too small for header
EXPECT_FALSE(ELFFile::IsValidElfHeader(small_span, &error));
EXPECT_TRUE(error.IsValid());
}
TEST_F(ELFParserTest, OpenValidELF)
{
ELFFile elf;
Error error;
EXPECT_TRUE(elf.Open(std::move(valid_elf_data), &error));
EXPECT_FALSE(error.IsValid());
}
TEST_F(ELFParserTest, OpenInvalidELF)
{
ELFFile elf;
Error error;
EXPECT_FALSE(elf.Open(invalid_elf_data, &error));
EXPECT_TRUE(error.IsValid());
}
TEST_F(ELFParserTest, OpenWrongMachineELF)
{
ELFFile elf;
Error error;
EXPECT_FALSE(elf.Open(wrong_machine_elf_data, &error));
EXPECT_TRUE(error.IsValid());
}
TEST_F(ELFParserTest, GetELFHeader)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
const auto& header = elf.GetELFHeader();
EXPECT_EQ(header.e_type, ELFFile::ET_EXEC);
EXPECT_EQ(header.e_machine, ELFFile::EM_MIPS);
EXPECT_EQ(header.e_entry, 0x80010000);
}
TEST_F(ELFParserTest, GetEntryPoint)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
EXPECT_EQ(elf.GetEntryPoint(), 0x80010000);
}
TEST_F(ELFParserTest, GetSectionCount)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
EXPECT_EQ(elf.GetSectionCount(), 3u);
}
TEST_F(ELFParserTest, GetValidSectionHeader)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
// Get .text section
const auto* section = elf.GetSectionHeader(1);
ASSERT_NE(section, nullptr);
EXPECT_EQ(section->sh_type, ELFFile::SHT_PROGBITS);
EXPECT_EQ(section->sh_addr, 0x80010000);
}
TEST_F(ELFParserTest, GetInvalidSectionHeader)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
// Try to get a section with an out-of-bounds index
EXPECT_EQ(elf.GetSectionHeader(99), nullptr);
// Try the special "undefined" section
EXPECT_EQ(elf.GetSectionHeader(ELFFile::SHN_UNDEF), nullptr);
}
TEST_F(ELFParserTest, GetSectionName)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
// Get .text section name
const auto* section = elf.GetSectionHeader(1);
ASSERT_NE(section, nullptr);
EXPECT_EQ(elf.GetSectionName(*section), ".text");
// Get .shstrtab section name
const auto* strtab_section = elf.GetSectionHeader(2);
ASSERT_NE(strtab_section, nullptr);
EXPECT_EQ(elf.GetSectionName(*strtab_section), ".shstrtab");
}
TEST_F(ELFParserTest, GetProgramHeaderCount)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
EXPECT_EQ(elf.GetProgramHeaderCount(), 2u);
}
TEST_F(ELFParserTest, GetValidProgramHeader)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
// Get loadable segment
const auto* phdr = elf.GetProgramHeader(0);
ASSERT_NE(phdr, nullptr);
EXPECT_EQ(phdr->p_type, ELFFile::PT_LOAD);
EXPECT_EQ(phdr->p_vaddr, 0x80010000u);
// Get note segment
const auto* phdr2 = elf.GetProgramHeader(1);
ASSERT_NE(phdr2, nullptr);
EXPECT_EQ(phdr2->p_type, ELFFile::PT_NOTE);
}
TEST_F(ELFParserTest, GetInvalidProgramHeader)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
// Try to get a program header with an out-of-bounds index
EXPECT_EQ(elf.GetProgramHeader(99), nullptr);
}
TEST_F(ELFParserTest, LoadExecutableSections)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(valid_elf_data), &error));
// Track loaded sections
struct LoadedSection
{
std::vector<u8> data;
u32 vaddr;
u32 size;
};
std::vector<LoadedSection> loaded_sections;
bool result = elf.LoadExecutableSections(
[&loaded_sections](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) {
LoadedSection section;
section.data.assign(data.begin(), data.end());
section.vaddr = dest_vaddr;
section.size = dest_size;
loaded_sections.push_back(std::move(section));
return true;
},
&error);
EXPECT_TRUE(result);
EXPECT_FALSE(error.IsValid());
// We should have loaded one section (the loadable segment)
ASSERT_EQ(loaded_sections.size(), 1u);
EXPECT_EQ(loaded_sections[0].vaddr, 0x80010000u);
EXPECT_EQ(loaded_sections[0].size, 0x100u);
EXPECT_EQ(loaded_sections[0].data[0], 0xAAu); // Check first byte of our fake code
}
TEST_F(ELFParserTest, MissingEntryPoint)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(missing_entry_elf_data), &error));
bool result = elf.LoadExecutableSections(
[](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) { return true; }, &error);
EXPECT_FALSE(result);
EXPECT_TRUE(error.IsValid());
}
TEST_F(ELFParserTest, OutOfRangeProgramHeader)
{
ELFFile elf;
Error error;
ASSERT_TRUE(elf.Open(std::move(out_of_range_program_header_elf_data), &error));
bool result = elf.LoadExecutableSections(
[](std::span<const u8> data, u32 dest_vaddr, u32 dest_size, Error* error) { return true; }, &error);
EXPECT_FALSE(result);
EXPECT_TRUE(error.IsValid());
}

View File

@@ -4,6 +4,8 @@
<ItemGroup>
<ClCompile Include="..\..\dep\googletest\src\gtest_main.cc" />
<ClCompile Include="animated_image_tests.cpp" />
<ClCompile Include="cue_parser_tests.cpp" />
<ClCompile Include="elf_parser_tests.cpp" />
<ClCompile Include="image_tests.cpp" />
</ItemGroup>
<ItemGroup>

View File

@@ -8,6 +8,7 @@
#include "common/string_util.h"
#include <cstdarg>
#include <sstream>
LOG_CHANNEL(CueParser);
@@ -71,6 +72,26 @@ bool CueParser::File::Parse(std::FILE* fp, Error* error)
return true;
}
bool CueParser::File::Parse(const std::string& buffer, Error* error)
{
u32 line_number = 1;
std::istringstream ss(buffer);
for (std::string line; std::getline(ss, line);)
{
if (!ParseLine(line.c_str(), line_number, error))
return false;
line_number++;
}
if (!CompleteLastTrack(line_number, error))
return false;
if (!SetTrackLengths(line_number, error))
return false;
return true;
}
void CueParser::File::SetError(u32 line_number, Error* error, const char* format, ...)
{
std::va_list ap;

View File

@@ -2,9 +2,13 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "cd_image.h"
#include "common/types.h"
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -69,6 +73,7 @@ public:
const Track* GetTrack(u32 n) const;
bool Parse(std::FILE* fp, Error* error);
bool Parse(const std::string& buffer, Error* error);
private:
Track* GetMutableTrack(u32 n);

View File

@@ -7,6 +7,7 @@
#include "common/types.h"
#include <functional>
#include <string_view>
class Error;