Skip to main content
Glama
ProgramAnalyzerDataTest.java22.5 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.ValueSource; import static org.junit.jupiter.api.Assertions.*; /** * Unit tests for data retrieval functionality in ProgramAnalyzer. * * These tests verify address format validation and error message formatting * for the getDataByAddress functionality. * * Note: Full integration tests with Ghidra Program objects would require * the Ghidra test framework and are beyond the scope of unit tests. */ class ProgramAnalyzerDataTest { /** * Test that various valid address formats are recognized */ @ParameterizedTest @DisplayName("Should recognize valid address formats") @ValueSource(strings = { "5356:3cd8", // Segment:offset format "0x1400010a0", // Hex format with 0x prefix "1400010a0", // Hex format without prefix "4592:000e", // Segment:offset with leading zeros "0x0", // Minimal hex address "0:0", // Minimal segment:offset "FFFF:FFFF" // Max values }) void testValidAddressFormats(String address) { // These should all be valid address strings assertNotNull(address, "Address should not be null"); assertFalse(address.isEmpty(), "Address should not be empty"); // Validate format patterns assertTrue( address.matches("^(0x)?[0-9a-fA-F]+$") || // Hex format address.matches("^[0-9a-fA-F]+:[0-9a-fA-F]+$"), // Segment:offset format "Address should match valid format: " + address ); } /** * Test that error messages contain required information */ @Test @DisplayName("Should format error messages properly") void testErrorMessageFormatting() { String address = "invalid:address"; String errorPrefix = "Error: "; // Error messages should start with "Error: " String errorMessage = errorPrefix + "Invalid address format: " + address; assertTrue(errorMessage.startsWith("Error: "), "Error message should start with 'Error: '"); assertTrue(errorMessage.contains(address), "Error message should contain the problematic address"); } /** * Test that null/empty address handling messages are correct */ @Test @DisplayName("Should validate null and empty address inputs") void testNullEmptyAddressValidation() { // Expected error for null/empty addresses String expectedError = "Error: Address is required"; assertTrue(expectedError.startsWith("Error: "), "Should return error message for invalid input"); assertTrue(expectedError.contains("required"), "Should indicate address is required"); } /** * Test response format structure */ @Test @DisplayName("Should format response with expected fields") void testResponseFormatStructure() { // Expected format: // Address: <address> // Name: <name> // Type: <type> // Value: <value> // Size: <size> bytes String[] expectedFields = { "Address:", "Name:", "Type:", "Value:", "Size:", "bytes" }; // Validate that all expected fields are present in the format for (String field : expectedFields) { assertNotNull(field, "Field should be defined: " + field); assertFalse(field.isEmpty(), "Field should not be empty: " + field); } } /** * Test segment:offset format parsing */ @ParameterizedTest @DisplayName("Should parse segment:offset addresses correctly") @ValueSource(strings = { "5356:3cd8", "4592:000e", "1234:5678", "ABCD:EF01" }) void testSegmentOffsetParsing(String address) { // Should contain exactly one colon long colonCount = address.chars().filter(ch -> ch == ':').count(); assertEquals(1, colonCount, "Segment:offset should have exactly one colon"); // Should split into two parts String[] parts = address.split(":"); assertEquals(2, parts.length, "Should split into segment and offset"); // Both parts should be valid hex String segment = parts[0]; String offset = parts[1]; assertTrue(segment.matches("[0-9a-fA-F]+"), "Segment should be valid hex: " + segment); assertTrue(offset.matches("[0-9a-fA-F]+"), "Offset should be valid hex: " + offset); } /** * Test hex format parsing */ @ParameterizedTest @DisplayName("Should parse hex addresses correctly") @ValueSource(strings = { "0x1400010a0", "0xdeadbeef", "0x0", "0xFFFFFFFF" }) void testHexAddressParsing(String address) { // Should start with 0x assertTrue(address.startsWith("0x"), "Hex address should start with 0x prefix"); // After 0x, should be valid hex digits String hexPart = address.substring(2); assertTrue(hexPart.matches("[0-9a-fA-F]+"), "Should contain only hex digits after 0x: " + hexPart); } /** * Test that unnamed data is handled correctly */ @Test @DisplayName("Should handle unnamed data correctly") void testUnnamedDataHandling() { String unnamedLabel = "(unnamed)"; assertTrue(unnamedLabel.startsWith("("), "Unnamed indicator should be in parentheses"); assertTrue(unnamedLabel.endsWith(")"), "Unnamed indicator should be in parentheses"); assertEquals("(unnamed)", unnamedLabel, "Should use standard unnamed indicator"); } /** * Test data size formatting */ @ParameterizedTest @DisplayName("Should format data sizes correctly") @ValueSource(ints = {1, 2, 4, 8, 16, 32, 64, 128, 256}) void testDataSizeFormatting(int size) { String sizeString = size + " bytes"; assertTrue(sizeString.endsWith(" bytes"), "Size should include 'bytes' unit"); assertTrue(sizeString.startsWith(String.valueOf(size)), "Size should start with numeric value"); } /** * Test output field order consistency */ @Test @DisplayName("Should maintain consistent field order in output") void testOutputFieldOrder() { String[] expectedOrder = { "Address:", "Name:", "Type:", "Value:", "Size:" }; // Verify the expected order is defined assertEquals(5, expectedOrder.length, "Should have 5 output fields"); assertEquals("Address:", expectedOrder[0], "Address should be first"); assertEquals("Name:", expectedOrder[1], "Name should be second"); assertEquals("Type:", expectedOrder[2], "Type should be third"); assertEquals("Value:", expectedOrder[3], "Value should be fourth"); assertEquals("Size:", expectedOrder[4], "Size should be fifth"); } /** * Test error message for missing data */ @Test @DisplayName("Should return appropriate error when no data at address") void testNoDataErrorMessage() { String address = "5356:3cd8"; String expectedError = "Error: No data defined at address " + address; assertTrue(expectedError.startsWith("Error: "), "Should be formatted as error message"); assertTrue(expectedError.contains("No data"), "Should indicate no data was found"); assertTrue(expectedError.contains(address), "Should include the address in error message"); } /** * Test that response handles special characters */ @Test @DisplayName("Should handle special characters in data values") void testSpecialCharacterHandling() { // Common special characters that might appear in data values String[] specialChars = {"\n", "\t", "\r", "\\", "\""}; for (String specialChar : specialChars) { assertNotNull(specialChar, "Special character should be defined"); // These should be escaped or handled properly in output // The escapeNonAscii utility should handle these } } /** * Test error messages with "Error:" prefix */ @ParameterizedTest @DisplayName("Should format standard error messages with 'Error:' prefix") @ValueSource(strings = { "Error: Address is required", "Error: Invalid address format:", "Error: No data defined at address" }) void testStandardErrorMessages(String errorMessage) { assertTrue(errorMessage.startsWith("Error: "), "Standard errors should start with 'Error: ' prefix"); assertFalse(errorMessage.isEmpty(), "Error message should not be empty"); } /** * Test error messages for exception scenarios */ @Test @DisplayName("Should format exception error messages correctly") void testExceptionErrorMessages() { // Exception errors have format: "Error getting data at address <addr>: <message>" String exceptionError = "Error getting data at address 5356:3cd8: Invalid format"; assertTrue(exceptionError.startsWith("Error getting data at address"), "Exception errors should describe the operation"); assertTrue(exceptionError.contains(":"), "Should include exception message after colon"); assertFalse(exceptionError.isEmpty(), "Error message should not be empty"); } /** * Test address format validation patterns */ @Test @DisplayName("Should distinguish between valid and invalid address formats") void testAddressFormatValidation() { // Valid formats String[] validAddresses = { "5356:3cd8", "0x1400010a0", "FFFF:0000" }; // Invalid formats String[] invalidAddresses = { "", "not:an:address", "0x", ":", ":::", "invalid" }; // Valid addresses should match expected patterns for (String addr : validAddresses) { boolean isValid = addr.matches("^(0x)?[0-9a-fA-F]+$") || addr.matches("^[0-9a-fA-F]+:[0-9a-fA-F]+$"); assertTrue(isValid, "Should recognize as valid: " + addr); } // Invalid addresses should not match for (String addr : invalidAddresses) { if (addr.isEmpty()) continue; // Empty is handled separately boolean isValid = addr.matches("^(0x)?[0-9a-fA-F]+$") || addr.matches("^[0-9a-fA-F]+:[0-9a-fA-F]+$"); assertFalse(isValid, "Should recognize as invalid: " + addr); } } // =========================================================================================== // Tests for getDataInRange functionality // =========================================================================================== /** * Test that valid address range pairs are recognized */ @Test @DisplayName("Should recognize valid address range pairs") void testValidAddressRanges() { // Valid address range pairs (start, end) String[][] validRanges = { {"0x00231fec", "0x00232100"}, // Hex format {"5356:3cd8", "5356:3d00"}, // Segment:offset format {"0x401000", "0x401020"}, // Small range {"4592:0000", "4592:00ff"} // Segment:offset range }; for (String[] range : validRanges) { String start = range[0]; String end = range[1]; assertNotNull(start, "Start address should not be null"); assertNotNull(end, "End address should not be null"); assertFalse(start.isEmpty(), "Start address should not be empty"); assertFalse(end.isEmpty(), "End address should not be empty"); // Both should match valid address formats boolean startValid = start.matches("^(0x)?[0-9a-fA-F]+$") || start.matches("^[0-9a-fA-F]+:[0-9a-fA-F]+$"); boolean endValid = end.matches("^(0x)?[0-9a-fA-F]+$") || end.matches("^[0-9a-fA-F]+:[0-9a-fA-F]+$"); assertTrue(startValid, "Start address should be valid: " + start); assertTrue(endValid, "End address should be valid: " + end); } } /** * Test error messages for missing required parameters */ @Test @DisplayName("Should validate required start and end addresses") void testDataInRangeRequiredParameters() { String missingStartError = "Error: Start address is required"; String missingEndError = "Error: End address is required"; assertTrue(missingStartError.startsWith("Error: "), "Should return error for missing start address"); assertTrue(missingStartError.contains("Start address"), "Should mention start address in error"); assertTrue(missingStartError.contains("required"), "Should indicate parameter is required"); assertTrue(missingEndError.startsWith("Error: "), "Should return error for missing end address"); assertTrue(missingEndError.contains("End address"), "Should mention end address in error"); } /** * Test error message for invalid address formats */ @Test @DisplayName("Should handle invalid address formats in range") void testDataInRangeInvalidAddressFormats() { String startAddress = "invalid_start"; String endAddress = "0x401020"; String expectedError = "Error: Invalid start address format: " + startAddress; assertTrue(expectedError.startsWith("Error: "), "Should return error for invalid address"); assertTrue(expectedError.contains("Invalid"), "Should indicate invalid format"); assertTrue(expectedError.contains(startAddress), "Should include the invalid address"); } /** * Test error message when start address is greater than end address */ @Test @DisplayName("Should validate start address <= end address") void testDataInRangeAddressOrder() { String expectedError = "Error: Start address must be less than or equal to end address"; assertTrue(expectedError.startsWith("Error: "), "Should return error for reversed addresses"); assertTrue(expectedError.contains("less than or equal"), "Should describe the ordering requirement"); } /** * Test response header format for data in range */ @Test @DisplayName("Should format response header with address range and include_undefined flag") void testDataInRangeResponseHeader() { String startAddr = "0x00231fec"; String endAddr = "0x00232100"; boolean includeUndefined = false; String expectedHeader = String.format("Data items from %s to %s (include_undefined=%s):", startAddr, endAddr, includeUndefined); assertTrue(expectedHeader.startsWith("Data items from"), "Header should describe what is being listed"); assertTrue(expectedHeader.contains(startAddr), "Header should include start address"); assertTrue(expectedHeader.contains(endAddr), "Header should include end address"); assertTrue(expectedHeader.contains("include_undefined="), "Header should show include_undefined flag"); assertTrue(expectedHeader.endsWith(":"), "Header should end with colon"); } /** * Test data item format in range output */ @Test @DisplayName("Should format data items with address, label, type, size, and value") void testDataInRangeItemFormat() { // Expected format: address: label [type, size bytes] = value String address = "0x00231fec"; String label = "stack_array"; String type = "byte[20]"; int size = 20; String value = "[0x00, 0x01, ...]"; String expectedFormat = String.format("%s: %s [%s, %d bytes] = %s", address, label, type, size, value); assertTrue(expectedFormat.contains(address), "Item should include address"); assertTrue(expectedFormat.contains(label), "Item should include label"); assertTrue(expectedFormat.contains(type), "Item should include type"); assertTrue(expectedFormat.contains(size + " bytes"), "Item should include size with bytes unit"); assertTrue(expectedFormat.contains("="), "Item should include equals sign before value"); assertTrue(expectedFormat.contains(value), "Item should include value"); } /** * Test include_undefined parameter handling */ @ParameterizedTest @DisplayName("Should handle include_undefined parameter correctly") @ValueSource(booleans = {true, false}) void testDataInRangeIncludeUndefinedParameter(boolean includeUndefined) { String headerPart = String.format("include_undefined=%s", includeUndefined); assertTrue(headerPart.contains("include_undefined="), "Should format parameter name"); assertTrue(headerPart.contains(String.valueOf(includeUndefined)), "Should include boolean value"); } /** * Test empty range result message */ @Test @DisplayName("Should return appropriate message when no data found in range") void testDataInRangeNoDataFound() { String expectedMessage = "No data items found in the specified range"; assertFalse(expectedMessage.startsWith("Error: "), "Should not be formatted as error (it's a valid empty result)"); assertTrue(expectedMessage.contains("No data"), "Should indicate no data was found"); assertTrue(expectedMessage.contains("range"), "Should mention the range"); } /** * Test total count format in output */ @ParameterizedTest @DisplayName("Should format total item count correctly") @ValueSource(ints = {0, 1, 2, 5, 10, 100}) void testDataInRangeTotalCountFormat(int count) { String totalLine = String.format("Total: %d item(s)", count); assertTrue(totalLine.startsWith("Total: "), "Total line should start with 'Total: '"); assertTrue(totalLine.contains(String.valueOf(count)), "Should include the count number"); assertTrue(totalLine.contains("item(s)"), "Should include 'item(s)' unit"); } /** * Test unnamed data handling in range output */ @Test @DisplayName("Should handle unnamed data items in range correctly") void testDataInRangeUnnamedData() { String unnamedLabel = "(unnamed)"; String address = "0x401004"; String type = "undefined"; String value = "??"; String itemFormat = String.format("%s: %s [%s, 1 bytes] = %s", address, unnamedLabel, type, value); assertTrue(itemFormat.contains("(unnamed)"), "Should use standard unnamed indicator"); assertTrue(itemFormat.contains(type), "Should show undefined type"); } /** * Test mixed address formats in same range */ @Test @DisplayName("Should accept consistent address format in range") void testDataInRangeMixedFormats() { // Both addresses should use same format String[][] consistentRanges = { {"0x401000", "0x401100"}, // Both hex {"5356:0000", "5356:0100"} // Both segment:offset }; for (String[] range : consistentRanges) { String start = range[0]; String end = range[1]; boolean startIsHex = start.startsWith("0x"); boolean endIsHex = end.startsWith("0x"); boolean startIsSegOff = start.contains(":"); boolean endIsSegOff = end.contains(":"); // Both should be same format assertEquals(startIsHex, endIsHex, "Addresses should use consistent format: " + start + " and " + end); assertEquals(startIsSegOff, endIsSegOff, "Addresses should use consistent format: " + start + " and " + end); } } /** * Test error handling for exception scenarios */ @Test @DisplayName("Should format exception errors correctly for getDataInRange") void testDataInRangeExceptionErrorFormat() { String exceptionError = "Error getting data in range: Some error message"; assertTrue(exceptionError.startsWith("Error getting data in range:"), "Exception errors should describe the operation"); assertTrue(exceptionError.contains(":"), "Should separate operation from error message with colon"); } /** * Test that output includes proper item formatting for various data types */ @Test @DisplayName("Should format different data types correctly in range output") void testDataInRangeDifferentDataTypes() { String[][] dataTypes = { {"word", "2"}, {"dword", "4"}, {"byte", "1"}, {"qword", "8"}, {"string", "12"}, {"byte[20]", "20"}, {"int[16]", "64"} }; for (String[] typeInfo : dataTypes) { String type = typeInfo[0]; String size = typeInfo[1]; String itemPart = String.format("[%s, %s bytes]", type, size); assertTrue(itemPart.startsWith("["), "Type info should be in brackets"); assertTrue(itemPart.contains(type), "Should include type name"); assertTrue(itemPart.contains(size + " bytes"), "Should include size with bytes unit"); assertTrue(itemPart.endsWith("]"), "Type info should close with bracket"); } } }

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