/** * @file visual_analysis_tool_test.cc * @brief Unit tests for visual analysis tools */ #include "cli/service/agent/tools/visual_analysis_tool.h" #include #include #include namespace yaze { namespace cli { namespace agent { namespace tools { namespace { // Test fixture for VisualAnalysisBase helper functions class VisualAnalysisBaseTest : public ::testing::Test { protected: // Create a test subclass to access protected methods class TestableVisualAnalysis : public VisualAnalysisBase { public: std::string GetName() const override { return "test-visual-analysis"; } std::string GetUsage() const override { return "test usage"; } // Expose protected methods for testing using VisualAnalysisBase::ComputePixelDifference; using VisualAnalysisBase::ComputeStructuralSimilarity; using VisualAnalysisBase::IsRegionEmpty; using VisualAnalysisBase::FormatMatchesAsJson; using VisualAnalysisBase::FormatRegionsAsJson; protected: absl::Status ValidateArgs( const resources::ArgumentParser& /*parser*/) override { return absl::OkStatus(); } absl::Status Execute(Rom* /*rom*/, const resources::ArgumentParser& /*parser*/, resources::OutputFormatter& /*formatter*/) override { return absl::OkStatus(); } }; TestableVisualAnalysis tool_; }; // ============================================================================= // ComputePixelDifference Tests // ============================================================================= TEST_F(VisualAnalysisBaseTest, PixelDifference_IdenticalTiles_Returns100) { std::vector tile(64, 0x42); // All pixels same value double similarity = tool_.ComputePixelDifference(tile, tile); EXPECT_DOUBLE_EQ(similarity, 100.0); } TEST_F(VisualAnalysisBaseTest, PixelDifference_CompletelyDifferent_Returns0) { std::vector tile_a(64, 0x00); // All black std::vector tile_b(64, 0xFF); // All white double similarity = tool_.ComputePixelDifference(tile_a, tile_b); EXPECT_DOUBLE_EQ(similarity, 0.0); } TEST_F(VisualAnalysisBaseTest, PixelDifference_HalfDifferent_Returns50) { std::vector tile_a(64, 0x00); std::vector tile_b(64, 0x00); // Make half the pixels maximally different for (int i = 0; i < 32; ++i) { tile_b[i] = 0xFF; } double similarity = tool_.ComputePixelDifference(tile_a, tile_b); EXPECT_NEAR(similarity, 50.0, 0.01); } TEST_F(VisualAnalysisBaseTest, PixelDifference_EmptyTiles_Returns0) { std::vector empty; double similarity = tool_.ComputePixelDifference(empty, empty); EXPECT_DOUBLE_EQ(similarity, 0.0); } TEST_F(VisualAnalysisBaseTest, PixelDifference_DifferentSizes_Returns0) { std::vector tile_a(64, 0x42); std::vector tile_b(32, 0x42); double similarity = tool_.ComputePixelDifference(tile_a, tile_b); EXPECT_DOUBLE_EQ(similarity, 0.0); } // ============================================================================= // ComputeStructuralSimilarity Tests // ============================================================================= TEST_F(VisualAnalysisBaseTest, StructuralSimilarity_IdenticalTiles_Returns100) { std::vector tile(64, 0x42); double similarity = tool_.ComputeStructuralSimilarity(tile, tile); EXPECT_GE(similarity, 99.0); // Should be very close to 100 } TEST_F(VisualAnalysisBaseTest, StructuralSimilarity_DifferentTiles_ReturnsLow) { std::vector tile_a(64, 0x00); std::vector tile_b(64, 0xFF); double similarity = tool_.ComputeStructuralSimilarity(tile_a, tile_b); EXPECT_LT(similarity, 50.0); } TEST_F(VisualAnalysisBaseTest, StructuralSimilarity_SimilarPattern_ReturnsHigh) { // Create two tiles with similar structure but slightly different values std::vector tile_a(64); std::vector tile_b(64); for (int i = 0; i < 64; ++i) { tile_a[i] = i % 16; // Pattern 0-15 repeated tile_b[i] = (i % 16) + 1; // Same pattern, shifted by 1 } double similarity = tool_.ComputeStructuralSimilarity(tile_a, tile_b); EXPECT_GT(similarity, 80.0); // Should be high due to similar structure } TEST_F(VisualAnalysisBaseTest, StructuralSimilarity_EmptyTiles_Returns0) { std::vector empty; double similarity = tool_.ComputeStructuralSimilarity(empty, empty); EXPECT_DOUBLE_EQ(similarity, 0.0); } // ============================================================================= // IsRegionEmpty Tests // ============================================================================= TEST_F(VisualAnalysisBaseTest, IsRegionEmpty_AllZeros_ReturnsTrue) { std::vector data(64, 0x00); EXPECT_TRUE(tool_.IsRegionEmpty(data)); } TEST_F(VisualAnalysisBaseTest, IsRegionEmpty_AllFF_ReturnsTrue) { std::vector data(64, 0xFF); EXPECT_TRUE(tool_.IsRegionEmpty(data)); } TEST_F(VisualAnalysisBaseTest, IsRegionEmpty_MostlyZeros_ReturnsTrue) { std::vector data(100, 0x00); data[0] = 0x01; // Only 1% non-zero EXPECT_TRUE(tool_.IsRegionEmpty(data)); // >95% zeros } TEST_F(VisualAnalysisBaseTest, IsRegionEmpty_HalfFilled_ReturnsFalse) { std::vector data(64, 0x00); for (int i = 0; i < 32; ++i) { data[i] = 0x42; } EXPECT_FALSE(tool_.IsRegionEmpty(data)); } TEST_F(VisualAnalysisBaseTest, IsRegionEmpty_CompletelyFilled_ReturnsFalse) { std::vector data(64, 0x42); EXPECT_FALSE(tool_.IsRegionEmpty(data)); } TEST_F(VisualAnalysisBaseTest, IsRegionEmpty_EmptyVector_ReturnsTrue) { std::vector data; EXPECT_TRUE(tool_.IsRegionEmpty(data)); } // ============================================================================= // JSON Formatting Tests // ============================================================================= TEST_F(VisualAnalysisBaseTest, FormatMatchesAsJson_EmptyList_ReturnsValidJson) { std::vector matches; std::string json = tool_.FormatMatchesAsJson(matches); EXPECT_TRUE(json.find("\"matches\": []") != std::string::npos); EXPECT_TRUE(json.find("\"total_matches\": 0") != std::string::npos); } TEST_F(VisualAnalysisBaseTest, FormatMatchesAsJson_SingleMatch_ReturnsValidJson) { std::vector matches = { {.tile_id = 42, .similarity_score = 95.5, .sheet_index = 1, .x_position = 16, .y_position = 8} }; std::string json = tool_.FormatMatchesAsJson(matches); EXPECT_TRUE(json.find("\"tile_id\": 42") != std::string::npos); EXPECT_TRUE(json.find("\"similarity_score\": 95.50") != std::string::npos); EXPECT_TRUE(json.find("\"sheet_index\": 1") != std::string::npos); EXPECT_TRUE(json.find("\"total_matches\": 1") != std::string::npos); } TEST_F(VisualAnalysisBaseTest, FormatRegionsAsJson_EmptyList_ReturnsValidJson) { std::vector regions; std::string json = tool_.FormatRegionsAsJson(regions); EXPECT_TRUE(json.find("\"unused_regions\": []") != std::string::npos); EXPECT_TRUE(json.find("\"total_regions\": 0") != std::string::npos); EXPECT_TRUE(json.find("\"total_free_tiles\": 0") != std::string::npos); } TEST_F(VisualAnalysisBaseTest, FormatRegionsAsJson_SingleRegion_ReturnsValidJson) { std::vector regions = { {.sheet_index = 5, .x = 0, .y = 0, .width = 16, .height = 8, .tile_count = 2} }; std::string json = tool_.FormatRegionsAsJson(regions); EXPECT_TRUE(json.find("\"sheet_index\": 5") != std::string::npos); EXPECT_TRUE(json.find("\"width\": 16") != std::string::npos); EXPECT_TRUE(json.find("\"tile_count\": 2") != std::string::npos); EXPECT_TRUE(json.find("\"total_free_tiles\": 2") != std::string::npos); } // ============================================================================= // TileSimilarityMatch Struct Tests // ============================================================================= TEST(TileSimilarityMatchTest, DefaultInitialization) { TileSimilarityMatch match = {}; EXPECT_EQ(match.tile_id, 0); EXPECT_DOUBLE_EQ(match.similarity_score, 0.0); EXPECT_EQ(match.sheet_index, 0); EXPECT_EQ(match.x_position, 0); EXPECT_EQ(match.y_position, 0); } // ============================================================================= // UnusedRegion Struct Tests // ============================================================================= TEST(UnusedRegionTest, DefaultInitialization) { UnusedRegion region = {}; EXPECT_EQ(region.sheet_index, 0); EXPECT_EQ(region.x, 0); EXPECT_EQ(region.y, 0); EXPECT_EQ(region.width, 0); EXPECT_EQ(region.height, 0); EXPECT_EQ(region.tile_count, 0); } // ============================================================================= // PaletteUsageStats Struct Tests // ============================================================================= TEST(PaletteUsageStatsTest, DefaultInitialization) { PaletteUsageStats stats = {}; EXPECT_EQ(stats.palette_index, 0); EXPECT_EQ(stats.usage_count, 0); EXPECT_DOUBLE_EQ(stats.usage_percentage, 0.0); EXPECT_TRUE(stats.used_by_maps.empty()); } // ============================================================================= // TileUsageEntry Struct Tests // ============================================================================= TEST(TileUsageEntryTest, DefaultInitialization) { TileUsageEntry entry = {}; EXPECT_EQ(entry.tile_id, 0); EXPECT_EQ(entry.usage_count, 0); EXPECT_DOUBLE_EQ(entry.usage_percentage, 0.0); EXPECT_TRUE(entry.locations.empty()); } // ============================================================================= // Constants Tests // ============================================================================= TEST(VisualAnalysisConstantsTest, TileConstants) { EXPECT_EQ(VisualAnalysisBase::kTileWidth, 8); EXPECT_EQ(VisualAnalysisBase::kTileHeight, 8); EXPECT_EQ(VisualAnalysisBase::kTilePixels, 64); EXPECT_EQ(VisualAnalysisBase::kSheetWidth, 128); EXPECT_EQ(VisualAnalysisBase::kSheetHeight, 32); EXPECT_EQ(VisualAnalysisBase::kTilesPerRow, 16); EXPECT_EQ(VisualAnalysisBase::kMaxSheets, 223); } // ============================================================================= // Edge Case Tests // ============================================================================= TEST_F(VisualAnalysisBaseTest, PixelDifference_LargeTile_HandlesCorrectly) { // Test with a larger tile (256 pixels = 16x16) std::vector tile_a(256, 0x80); std::vector tile_b(256, 0x80); tile_b[0] = 0x00; // Single pixel difference double similarity = tool_.ComputePixelDifference(tile_a, tile_b); // (255/256 pixels same) = 99.6% similar after accounting for intensity diff EXPECT_GT(similarity, 99.0); } TEST_F(VisualAnalysisBaseTest, StructuralSimilarity_GradientPattern_MatchesWell) { // Create gradient patterns std::vector tile_a(64); std::vector tile_b(64); for (int i = 0; i < 64; ++i) { tile_a[i] = i * 4; // Gradient 0-252 tile_b[i] = i * 4 + 2; // Same gradient, offset by 2 } double similarity = tool_.ComputeStructuralSimilarity(tile_a, tile_b); EXPECT_GT(similarity, 90.0); // Same structure } } // namespace } // namespace tools } // namespace agent } // namespace cli } // namespace yaze