Skip to main content
Glama
InstructionPatternSearchServiceTest.java11.7 kB
package com.ghidramcp.services; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; /** * Unit tests for InstructionPatternSearchService. * * These tests verify parameter validation, regex pattern handling, * and error message formatting for instruction pattern search. * * Note: Full integration tests with Ghidra Program objects would require * the Ghidra test framework and are beyond the scope of unit tests. */ class InstructionPatternSearchServiceTest { /** * Test that search pattern is required */ @Test @DisplayName("Should require search pattern") void testSearchPatternRequired() { String expectedError = "Error: Search pattern is required"; assertTrue(expectedError.contains("required"), "Should indicate search pattern is required"); assertTrue(expectedError.contains("Search pattern"), "Should mention search pattern"); } /** * Test that invalid regex patterns are caught */ @Test @DisplayName("Should validate regex pattern syntax") void testInvalidRegexPattern() { String expectedError = "Error: Invalid regex pattern - "; assertTrue(expectedError.contains("Invalid regex pattern"), "Should indicate invalid regex"); assertTrue(expectedError.endsWith(" - "), "Should have separator for specific error message"); } /** * Test segment name validation */ @Test @DisplayName("Should validate segment name") void testSegmentNameValidation() { String segmentName = "CODE_70"; String expectedError = "Error: Segment not found: " + segmentName; assertTrue(expectedError.contains("Segment not found"), "Should indicate segment was not found"); assertTrue(expectedError.contains(segmentName), "Should include the segment name"); } /** * Test address range validation */ @Test @DisplayName("Should validate address range order") void testAddressRangeValidation() { String expectedError = "Error: Start address must be less than or equal to end address"; assertTrue(expectedError.contains("Start address"), "Should mention start address"); assertTrue(expectedError.contains("end address"), "Should mention end address"); } /** * Test invalid address formats */ @ParameterizedTest @DisplayName("Should handle invalid addresses") @ValueSource(strings = {"Invalid", "not_an_address", "xyz123"}) void testInvalidAddressFormats(String address) { String errorPrefix = "Error: Invalid"; assertTrue(errorPrefix.contains("Error"), "Should indicate error"); assertTrue(errorPrefix.contains("Invalid"), "Should indicate validation failure"); } /** * Test result format with disassembly */ @Test @DisplayName("Should format results with disassembly") void testResultFormat() { String address = "0x401000"; String disassembly = "mov eax, 0x1234"; String segment = "CODE_70"; String expectedFormat = address + ": " + disassembly + " (segment: " + segment + ")"; assertTrue(expectedFormat.contains(address), "Should contain address"); assertTrue(expectedFormat.contains(disassembly), "Should contain disassembly"); assertTrue(expectedFormat.contains("segment:"), "Should contain segment label"); assertTrue(expectedFormat.contains(segment), "Should contain segment name"); } /** * Test result summary format */ @ParameterizedTest @DisplayName("Should format result summary") @CsvSource({ "10, move, 0, 10, 1, 10", "100, jsr, 0, 50, 1, 50", "5, tst, 0, 10, 1, 5", "20, bsr, 10, 10, 11, 20" }) void testResultSummaryFormat(int totalResults, String pattern, int offset, int limit, int expectedStart, int expectedEnd) { String summaryLine1 = String.format("Found %d matches for pattern: %s", totalResults, pattern); String summaryLine2 = String.format("Showing results %d to %d", offset + 1, Math.min(offset + limit, totalResults)); assertTrue(summaryLine1.contains("matches"), "Should indicate match count"); assertTrue(summaryLine1.contains("pattern:"), "Should mention pattern"); assertTrue(summaryLine1.contains(pattern), "Should include the pattern"); assertTrue(summaryLine2.contains("Showing results"), "Should indicate which results are shown"); } /** * Test no matches scenario */ @Test @DisplayName("Should handle no matches") void testNoMatches() { String pattern = "nonexistent"; String expectedMessage = "No matches found for pattern: " + pattern; assertTrue(expectedMessage.contains("No matches"), "Should indicate no matches found"); assertTrue(expectedMessage.contains("pattern:"), "Should mention pattern"); assertTrue(expectedMessage.contains(pattern), "Should include the pattern"); } /** * Test more results indicator */ @Test @DisplayName("Should indicate when more results are available") void testMoreResultsIndicator() { int totalResults = 100; int shown = 50; int remaining = totalResults - shown; String moreResultsMsg = String.format("... %d more match(es). Use offset parameter to see more.", remaining); assertTrue(moreResultsMsg.contains("more match(es)"), "Should indicate more matches available"); assertTrue(moreResultsMsg.contains("offset parameter"), "Should mention offset parameter"); assertTrue(moreResultsMsg.contains(String.valueOf(remaining)), "Should show number of remaining results"); } /** * Test regex pattern examples */ @ParameterizedTest @DisplayName("Should recognize valid regex patterns") @ValueSource(strings = { "move\\.b", "[jb]sr", "0x[0-9a-fA-F]+", "tst\\.l.*A4", ".*\\(.*,.*\\)" }) void testRegexPatternExamples(String pattern) { assertNotNull(pattern, "Pattern should not be null"); assertFalse(pattern.isEmpty(), "Pattern should not be empty"); // These patterns should be valid regex (compilation test would happen at runtime) assertTrue(pattern.length() > 0, "Pattern should have content"); } /** * Test instruction format components */ @Test @DisplayName("Should format instruction with mnemonic and operands") void testInstructionFormat() { String mnemonic = "move.b"; String operand1 = "(0x3932,A4)"; String operand2 = "D0"; String expectedFormat = mnemonic + " " + operand1 + "," + operand2; assertTrue(expectedFormat.startsWith(mnemonic), "Should start with mnemonic"); assertTrue(expectedFormat.contains(operand1), "Should contain first operand"); assertTrue(expectedFormat.contains(operand2), "Should contain second operand"); assertTrue(expectedFormat.contains(","), "Should separate operands with comma"); } /** * Test pagination edge cases */ @ParameterizedTest @DisplayName("Should handle pagination correctly") @CsvSource({ "0, 10, 100", // First page "10, 10, 100", // Second page "90, 10, 100", // Last page "100, 10, 100", // Beyond last page "0, 100, 50" // Limit exceeds total }) void testPagination(int offset, int limit, int totalResults) { int endIndex = Math.min(offset + limit, totalResults); assertTrue(offset >= 0, "Offset should be non-negative"); assertTrue(limit > 0, "Limit should be positive"); assertTrue(endIndex <= totalResults, "End index should not exceed total"); assertTrue(endIndex >= offset, "End index should be >= offset"); } /** * Test address parsing errors */ @Test @DisplayName("Should handle address parsing errors") void testAddressParsingErrors() { String errorMessage = "Error parsing addresses: Invalid address format"; assertTrue(errorMessage.contains("Error parsing addresses"), "Should indicate address parsing error"); assertTrue(errorMessage.contains(":"), "Should separate error type from message"); } /** * Test search ranges */ @ParameterizedTest @DisplayName("Should support different search ranges") @CsvSource({ "0x1000, 0x2000, segment", // Address range "null, null, CODE_70", // Segment only "null, null, null" // Entire program }) void testSearchRanges(String start, String end, String segment) { // Verify that different combinations are valid boolean hasAddressRange = !"null".equals(start) && !"null".equals(end); boolean hasSegment = !"null".equals(segment); boolean hasNoRestriction = !hasAddressRange && !hasSegment; assertTrue(hasAddressRange || hasSegment || hasNoRestriction, "Should support address range, segment, or no restriction"); } /** * Test common instruction patterns */ @ParameterizedTest @DisplayName("Should match common instruction patterns") @CsvSource({ "move\\.b, move.b (0x3932,A4)", "[jb]sr, jsr FUN_00401234", "tst, tst.l D0", "0x3932, move.b (0x3932,A4)" }) void testCommonInstructionMatches(String pattern, String disassembly) { // These test expected matches (actual matching happens at runtime) assertNotNull(pattern, "Pattern should not be null"); assertNotNull(disassembly, "Disassembly should not be null"); assertTrue(pattern.length() > 0, "Pattern should have content"); assertTrue(disassembly.length() > 0, "Disassembly should have content"); } /** * Test case insensitive pattern matching */ @ParameterizedTest @DisplayName("Should match patterns case insensitively") @CsvSource({ "mov, MOV eax, 0x1234", "MOV, mov eax, 0x1234", "jsr, JSR FUN_00401234", "JSR, jsr FUN_00401234", "tst, TST.L D0", "TST, tst.l D0" }) void testCaseInsensitiveMatching(String pattern, String disassembly) { // These verify that patterns match regardless of case assertNotNull(pattern, "Pattern should not be null"); assertNotNull(disassembly, "Disassembly should not be null"); assertTrue(pattern.length() > 0, "Pattern should have content"); assertTrue(disassembly.length() > 0, "Disassembly should have content"); // Patterns should match case insensitively (actual matching happens at runtime) String lowerPattern = pattern.toLowerCase(); String upperPattern = pattern.toUpperCase(); assertNotEquals(lowerPattern, upperPattern, "Test should verify different cases"); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HK47196/GhidraMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server