#include "app/rom.h" #include #include #include "absl/status/status.h" #include "absl/status/statusor.h" #include "app/transaction.h" #include "mocks/mock_rom.h" #include "testing.h" namespace yaze { namespace test { using ::testing::_; using ::testing::Return; const static std::vector kMockRomData = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, }; class RomTest : public ::testing::Test { protected: Rom rom_; }; TEST_F(RomTest, Uninitialized) { EXPECT_EQ(rom_.size(), 0); EXPECT_EQ(rom_.data(), nullptr); } TEST_F(RomTest, LoadFromFile) { #if defined(__linux__) GTEST_SKIP(); #endif EXPECT_OK(rom_.LoadFromFile("zelda3.sfc")); EXPECT_EQ(rom_.size(), 0x200000); EXPECT_NE(rom_.data(), nullptr); } TEST_F(RomTest, LoadFromFileInvalid) { EXPECT_THAT(rom_.LoadFromFile("invalid.sfc"), StatusIs(absl::StatusCode::kNotFound)); EXPECT_EQ(rom_.size(), 0); EXPECT_EQ(rom_.data(), nullptr); } TEST_F(RomTest, LoadFromFileEmpty) { EXPECT_THAT(rom_.LoadFromFile(""), StatusIs(absl::StatusCode::kInvalidArgument)); } TEST_F(RomTest, ReadByteOk) { EXPECT_OK(rom_.LoadFromData(kMockRomData, false)); for (size_t i = 0; i < kMockRomData.size(); ++i) { uint8_t byte; ASSERT_OK_AND_ASSIGN(byte, rom_.ReadByte(i)); EXPECT_EQ(byte, kMockRomData[i]); } } TEST_F(RomTest, ReadByteInvalid) { EXPECT_THAT(rom_.ReadByte(0).status(), StatusIs(absl::StatusCode::kFailedPrecondition)); } TEST_F(RomTest, ReadWordOk) { EXPECT_OK(rom_.LoadFromData(kMockRomData, false)); for (size_t i = 0; i < kMockRomData.size(); i += 2) { // Little endian EXPECT_THAT( rom_.ReadWord(i), IsOkAndHolds((kMockRomData[i]) | kMockRomData[i + 1] << 8)); } } TEST_F(RomTest, ReadWordInvalid) { EXPECT_THAT(rom_.ReadWord(0).status(), StatusIs(absl::StatusCode::kFailedPrecondition)); } TEST_F(RomTest, ReadLongOk) { EXPECT_OK(rom_.LoadFromData(kMockRomData, false)); for (size_t i = 0; i < kMockRomData.size(); i += 4) { // Little endian EXPECT_THAT(rom_.ReadLong(i), IsOkAndHolds((kMockRomData[i]) | kMockRomData[i] | kMockRomData[i + 1] << 8 | kMockRomData[i + 2] << 16)); } } TEST_F(RomTest, ReadBytesOk) { EXPECT_OK(rom_.LoadFromData(kMockRomData, false)); std::vector bytes; ASSERT_OK_AND_ASSIGN(bytes, rom_.ReadByteVector(0, kMockRomData.size())); EXPECT_THAT(bytes, ::testing::ContainerEq(kMockRomData)); } TEST_F(RomTest, ReadBytesOutOfRange) { EXPECT_OK(rom_.LoadFromData(kMockRomData, false)); std::vector bytes; EXPECT_THAT(rom_.ReadByteVector(kMockRomData.size() + 1, 1).status(), StatusIs(absl::StatusCode::kOutOfRange)); } TEST_F(RomTest, WriteByteOk) { EXPECT_OK(rom_.LoadFromData(kMockRomData, false)); for (size_t i = 0; i < kMockRomData.size(); ++i) { EXPECT_OK(rom_.WriteByte(i, 0xFF)); uint8_t byte; ASSERT_OK_AND_ASSIGN(byte, rom_.ReadByte(i)); EXPECT_EQ(byte, 0xFF); } } TEST_F(RomTest, WriteWordOk) { EXPECT_OK(rom_.LoadFromData(kMockRomData, false)); for (size_t i = 0; i < kMockRomData.size(); i += 2) { EXPECT_OK(rom_.WriteWord(i, 0xFFFF)); uint16_t word; ASSERT_OK_AND_ASSIGN(word, rom_.ReadWord(i)); EXPECT_EQ(word, 0xFFFF); } } TEST_F(RomTest, WriteLongOk) { EXPECT_OK(rom_.LoadFromData(kMockRomData, false)); for (size_t i = 0; i < kMockRomData.size(); i += 4) { EXPECT_OK(rom_.WriteLong(i, 0xFFFFFF)); uint32_t word; ASSERT_OK_AND_ASSIGN(word, rom_.ReadLong(i)); EXPECT_EQ(word, 0xFFFFFF); } } TEST_F(RomTest, WriteTransactionSuccess) { MockRom mock_rom; EXPECT_OK(mock_rom.LoadFromData(kMockRomData, false)); EXPECT_CALL(mock_rom, WriteHelper(_)) .WillRepeatedly(Return(absl::OkStatus())); EXPECT_OK(mock_rom.WriteTransaction( Rom::WriteAction{0x1000, uint8_t{0xFF}}, Rom::WriteAction{0x1001, uint16_t{0xABCD}}, Rom::WriteAction{0x1002, std::vector{0x12, 0x34}})); } TEST_F(RomTest, WriteTransactionFailure) { MockRom mock_rom; EXPECT_OK(mock_rom.LoadFromData(kMockRomData, false)); EXPECT_CALL(mock_rom, WriteHelper(_)) .WillOnce(Return(absl::OkStatus())) .WillOnce(Return(absl::InternalError("Write failed"))); EXPECT_EQ( mock_rom.WriteTransaction(Rom::WriteAction{0x1000, uint8_t{0xFF}}, Rom::WriteAction{0x1001, uint16_t{0xABCD}}), absl::InternalError("Write failed")); } TEST_F(RomTest, ReadTransactionSuccess) { MockRom mock_rom; EXPECT_OK(mock_rom.LoadFromData(kMockRomData, false)); uint8_t byte_val; uint16_t word_val; EXPECT_OK(mock_rom.ReadTransaction(byte_val, 0x0000, word_val, 0x0001)); EXPECT_EQ(byte_val, 0x00); EXPECT_EQ(word_val, 0x0201); } TEST_F(RomTest, ReadTransactionFailure) { MockRom mock_rom; EXPECT_OK(mock_rom.LoadFromData(kMockRomData, false)); uint8_t byte_val; EXPECT_EQ(mock_rom.ReadTransaction(byte_val, 0x1000), absl::FailedPreconditionError("Offset out of range")); } TEST_F(RomTest, SaveTruncatesExistingFile) { #if defined(__linux__) GTEST_SKIP(); #endif // Prepare ROM data and save to a temp file twice; second save should // overwrite, not append EXPECT_OK(rom_.LoadFromData(kMockRomData, /*z3_load=*/false)); const char* tmp_name = "test_temp_rom.sfc"; yaze::Rom::SaveSettings settings; settings.filename = tmp_name; settings.z3_save = false; // First save EXPECT_OK(rom_.SaveToFile(settings)); // Modify one byte and save again EXPECT_OK(rom_.WriteByte(0, 0xEE)); EXPECT_OK(rom_.SaveToFile(settings)); // Load the saved file and verify size equals original data size and first // byte matches Rom verify; EXPECT_OK(verify.LoadFromFile(tmp_name, /*z3_load=*/false)); EXPECT_EQ(verify.size(), kMockRomData.size()); auto b0 = verify.ReadByte(0); ASSERT_TRUE(b0.ok()); EXPECT_EQ(*b0, 0xEE); } TEST_F(RomTest, TransactionRollbackRestoresOriginals) { EXPECT_OK(rom_.LoadFromData(kMockRomData, /*z3_load=*/false)); // Force an out-of-range write to trigger failure after a successful write yaze::Transaction tx{rom_}; auto status = tx.WriteByte(0x01, 0xAA) // valid .WriteWord(0xFFFF, 0xBBBB) // invalid: should fail and rollback .Commit(); EXPECT_FALSE(status.ok()); auto b1 = rom_.ReadByte(0x01); ASSERT_TRUE(b1.ok()); // Should be restored to original 0x01 value (from kMockRomData) EXPECT_EQ(*b1, kMockRomData[0x01]); } } // namespace test } // namespace yaze