From 9eaf5e00e4c3953c526f00ac0df2980859c7b5a3 Mon Sep 17 00:00:00 2001 From: Timothy Bish Date: Wed, 7 Aug 2024 18:24:45 -0400 Subject: [PATCH] PROTON-2845 Improve output of failed match when validating messages When checking on scripted transfer payload contents the output should provide more information to make it clear what failed in the contents matching. --- .../transport/TransferMessageMatcher.java | 38 +- .../test/driver/util/StringUtils.java | 327 ++++++++++++++++++ 2 files changed, 348 insertions(+), 17 deletions(-) create mode 100644 protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/util/StringUtils.java diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/transport/TransferMessageMatcher.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/transport/TransferMessageMatcher.java index 25cb5a80..decd74ff 100644 --- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/transport/TransferMessageMatcher.java +++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/transport/TransferMessageMatcher.java @@ -33,6 +33,7 @@ import org.apache.qpid.protonj2.test.driver.matchers.types.EncodedAmqpTypeMatcher; import org.apache.qpid.protonj2.test.driver.matchers.types.EncodedAmqpValueMatcher; import org.apache.qpid.protonj2.test.driver.matchers.types.EncodedDataMatcher; +import org.apache.qpid.protonj2.test.driver.util.StringUtils; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.StringDescription; @@ -88,8 +89,9 @@ protected boolean matchesSafely(ByteBuffer receivedBinary) { bytesConsumed += headersMatcher.getInnerMatcher().verify(receivedSlice.slice()); receivedSlice.position(bytesConsumed); } catch (Throwable t) { - headerMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to MessageHeaderMatcher: " + receivedSlice; - headerMatcherFailureDescription += "\nMessageHeaderMatcher generated throwable: " + t; + headerMatcherFailureDescription = "\nBuffer of bytes passed to Header Matcher: " + receivedSlice; + headerMatcherFailureDescription += "\nActual encoded form of remaining bytes passed: " + StringUtils.toQuotedString(receivedSlice); + headerMatcherFailureDescription += "\nHeader Matcher generated throwable: " + t.getMessage(); return false; } @@ -101,9 +103,9 @@ protected boolean matchesSafely(ByteBuffer receivedBinary) { bytesConsumed += deliveryAnnotationsMatcher.getInnerMatcher().verify(receivedSlice.slice()); receivedSlice.position(bytesConsumed); } catch (Throwable t) { - deliveryAnnotationsMatcherFailureDescription = "\nActual encoded form of remaining bytes passed " + - "to DeliveryAnnotationsMatcher: " + receivedSlice; - deliveryAnnotationsMatcherFailureDescription += "\nDeliveryAnnotationsMatcher generated throwable: " + t; + deliveryAnnotationsMatcherFailureDescription = "\nBuffer of bytes passed to Delivery Annotations Matcher: " + receivedSlice; + deliveryAnnotationsMatcherFailureDescription += "\nActual encoded form of remaining bytes passed: " + StringUtils.toQuotedString(receivedSlice); + deliveryAnnotationsMatcherFailureDescription += "\nDelivery Annotations Matcher generated throwable: " + t.getMessage(); return false; } @@ -115,9 +117,9 @@ protected boolean matchesSafely(ByteBuffer receivedBinary) { bytesConsumed += messageAnnotationsMatcher.getInnerMatcher().verify(receivedSlice.slice()); receivedSlice.position(bytesConsumed); } catch (Throwable t) { - messageAnnotationsMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to " + - "MessageAnnotationsMatcher: " + receivedSlice; - messageAnnotationsMatcherFailureDescription += "\nMessageAnnotationsMatcher generated throwable: " + t; + messageAnnotationsMatcherFailureDescription = "\nBuffer of bytes passed to Message Annotations Matcher: " + receivedSlice; + messageAnnotationsMatcherFailureDescription += "\nActual encoded form of remaining bytes passed: " + StringUtils.toQuotedString(receivedSlice); + messageAnnotationsMatcherFailureDescription += "\nMessage Annotations Matcher generated throwable: " + t.getMessage(); return false; } @@ -129,9 +131,9 @@ protected boolean matchesSafely(ByteBuffer receivedBinary) { bytesConsumed += propertiesMatcher.getInnerMatcher().verify(receivedSlice.slice()); receivedSlice.position(bytesConsumed); } catch (Throwable t) { - propertiesMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to " + - "PropertiesMatcher: " + receivedSlice; - propertiesMatcherFailureDescription += "\nPropertiesMatcher generated throwable: " + t; + propertiesMatcherFailureDescription = "\nBuffer of bytes passed to Properties Matcher: " + receivedSlice; + propertiesMatcherFailureDescription += "\nActual encoded form of remaining bytes passed: " + StringUtils.toQuotedString(receivedSlice); + propertiesMatcherFailureDescription += "\nProperties Matcher generated throwable: " + t.getMessage(); return false; } @@ -143,9 +145,9 @@ protected boolean matchesSafely(ByteBuffer receivedBinary) { bytesConsumed += applicationPropertiesMatcher.getInnerMatcher().verify(receivedSlice.slice()); receivedSlice.position(bytesConsumed); } catch (Throwable t) { - applicationPropertiesMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to " + - "ApplicationPropertiesMatcher: " + receivedSlice; - applicationPropertiesMatcherFailureDescription += "\nApplicationPropertiesMatcher generated throwable: " + t; + applicationPropertiesMatcherFailureDescription = "\nBuffer of bytes passed to Application Properties Matcher: " + receivedSlice; + applicationPropertiesMatcherFailureDescription += "\nActual encoded form of remaining bytes passed: " + StringUtils.toQuotedString(receivedSlice); + applicationPropertiesMatcherFailureDescription += "\nApplication Properties Matcher generated throwable: " + t.getMessage(); return false; } @@ -161,9 +163,11 @@ protected boolean matchesSafely(ByteBuffer receivedBinary) { if (!contentMatches) { Description desc = new StringDescription(); msgContentMatcher.describeTo(desc); - msgContentMatcher.describeMismatch(receivedSlice, desc); + msgContentMatcher.describeMismatch(slicedMsgContext, desc); - msgContentMatcherFailureDescription = "\nMessageContentMatcher mismatch Description:"; + msgContentMatcherFailureDescription = "\nBuffer of bytes passed to message contents Matcher: " + slicedMsgContext; + msgContentMatcherFailureDescription += "\nActual encoded form of remaining bytes passed: " + StringUtils.toQuotedString(receivedSlice); + msgContentMatcherFailureDescription += "\nMessageContentMatcher mismatch Description:"; msgContentMatcherFailureDescription += desc.toString(); return false; @@ -181,7 +185,7 @@ protected boolean matchesSafely(ByteBuffer receivedBinary) { } catch (Throwable t) { footerMatcherFailureDescription = "\nActual encoded form of remaining bytes passed to " + "FooterMatcher: " + receivedSlice; - footerMatcherFailureDescription += "\nFooterMatcher generated throwable: " + t; + footerMatcherFailureDescription += "\nFooterMatcher generated throwable: " + t.getMessage(); return false; } diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/util/StringUtils.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/util/StringUtils.java new file mode 100644 index 00000000..ee5ac18a --- /dev/null +++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/util/StringUtils.java @@ -0,0 +1,327 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ +package org.apache.qpid.protonj2.test.driver.util; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.qpid.protonj2.test.driver.codec.primitives.Binary; +import org.apache.qpid.protonj2.test.driver.codec.primitives.Symbol; + +/** + * Set of {@link String} utilities used in the proton code. + */ +public class StringUtils { + + private static final int DEFAULT_QUOTED_STRING_LIMIT = 64; + + /** + * Converts the given String[] into a Symbol[] array. + * + * @param stringArray + * The given String[] to convert. + * + * @return a new Symbol array that contains Symbol versions of the input Strings. + */ + public static Symbol[] toSymbolArray(String[] stringArray) { + Symbol[] result = null; + + if (stringArray != null) { + result = new Symbol[stringArray.length]; + for (int i = 0; i < stringArray.length; ++i) { + result[i] = Symbol.valueOf(stringArray[i]); + } + } + + return result; + } + + /** + * Converts the given Symbol[] into a String[] array. + * + * @param symbolArray + * The given Symbol[] to convert. + * + * @return a new String array that contains String versions of the input Symbol. + */ + public static String[] toStringArray(Symbol[] symbolArray) { + String[] result = null; + + if (symbolArray != null) { + result = new String[symbolArray.length]; + for (int i = 0; i < symbolArray.length; ++i) { + result[i] = symbolArray[i].toString(); + } + } + + return result; + } + + /** + * Converts the given String keyed {@link Map} into a matching Symbol keyed {@link Map}. + * + * @param stringsMap + * The given String keyed {@link Map} to convert. + * + * @return a new Symbol keyed {@link Map} that contains Symbol versions of the input String keys. + */ + public static Map toSymbolKeyedMap(Map stringsMap) { + final Map result; + + if (stringsMap != null) { + result = new HashMap<>(stringsMap.size()); + stringsMap.forEach((key, value) -> { + result.put(Symbol.valueOf(key), value); + }); + } else { + result = null; + } + + return result; + } + + /** + * Converts the given Symbol keyed {@link Map} into a matching String keyed {@link Map}. + * + * @param symbolMap + * The given String keyed {@link Map} to convert. + * + * @return a new String keyed {@link Map} that contains String versions of the input Symbol keys. + */ + public static Map toStringKeyedMap(Map symbolMap) { + Map result; + + if (symbolMap != null) { + result = new LinkedHashMap<>(symbolMap.size()); + symbolMap.forEach((key, value) -> { + result.put(key.toString(), value); + }); + } else { + result = null; + } + + return result; + } + + /** + * Converts the given String {@link Collection} into a Symbol array. + * + * @param stringsSet + * The given String {@link Collection} to convert. + * + * @return a new Symbol array that contains String versions of the input Symbols. + */ + public static Symbol[] toSymbolArray(Collection stringsSet) { + final Symbol[] result; + + if (stringsSet != null) { + result = new Symbol[stringsSet.size()]; + int index = 0; + for (String entry : stringsSet) { + result[index++] = Symbol.valueOf(entry); + } + } else { + result = null; + } + + return result; + } + + /** + * Converts the given String {@link Collection} into a matching Symbol {@link Set}. + * + * @param stringsSet + * The given String {@link Collection} to convert. + * + * @return a new Symbol {@link Set} that contains String versions of the input Symbols. + */ + public static Set toSymbolSet(Collection stringsSet) { + final Set result; + + if (stringsSet != null) { + result = new LinkedHashSet<>(stringsSet.size()); + stringsSet.forEach((entry) -> { + result.add(Symbol.valueOf(entry)); + }); + } else { + result = null; + } + + return result; + } + + /** + * Converts the given Symbol array into a matching String {@link Set}. + * + * @param symbols + * The given Symbol array to convert. + * + * @return a new String {@link Set} that contains String versions of the input Symbols. + */ + public static Set toStringSet(Symbol[] symbols) { + Set result; + + if (symbols != null) { + result = new LinkedHashSet<>(symbols.length); + for (Symbol symbol : symbols) { + result.add(symbol.toString()); + } + } else { + result = null; + } + + return result; + } + + /** + * Converts the Binary to a quoted string using a default max length before truncation value and + * appends a truncation indication if the string required truncation. + * + * @param buffer + * the {@link Binary} to convert into String format. + * + * @return the converted string + */ + public static String toQuotedString(final Binary buffer) { + return toQuotedString(buffer, DEFAULT_QUOTED_STRING_LIMIT, true); + } + + /** + * Converts the Binary to a quoted string using a default max length before truncation value. + * + * @param buffer + * the {@link Binary} to convert into String format. + * @param appendIfTruncated + * appends "...(truncated)" if not all of the payload is present in the string + * + * @return the converted string + */ + public static String toQuotedString(final Binary buffer, final boolean appendIfTruncated) { + return toQuotedString(buffer, DEFAULT_QUOTED_STRING_LIMIT, appendIfTruncated); + } + + /** + * Converts the Binary to a quoted string. + * + * @param buffer + * the {@link Binary} to convert into String format. + * @param stringLength + * the maximum length of stringified content (excluding the quotes, and truncated indicator) + * @param appendIfTruncated + * appends "...(truncated)" if not all of the payload is present in the string + * + * @return the converted string + */ + public static String toQuotedString(final Binary buffer, final int stringLength, final boolean appendIfTruncated) { + if (buffer == null) { + return "\"\""; + } else { + return toQuotedString(buffer.asByteBuffer(), stringLength, appendIfTruncated); + } + } + + /** + * Converts the ProtonBuffer to a quoted string using a default max length before truncation value and + * appends a truncation indication if the string required truncation. + * + * @param buffer + * the {@link ByteBuffer} to convert into String format. + * + * @return the converted string + */ + public static String toQuotedString(final ByteBuffer buffer) { + return toQuotedString(buffer, DEFAULT_QUOTED_STRING_LIMIT, true); + } + + /** + * Converts the ProtonBuffer to a quoted string using a default max length before truncation value. + * + * @param buffer + * the {@link ByteBuffer} to convert into String format. + * @param appendIfTruncated + * appends "...(truncated)" if not all of the payload is present in the string + * + * @return the converted string + */ + public static String toQuotedString(final ByteBuffer buffer, final boolean appendIfTruncated) { + return toQuotedString(buffer, DEFAULT_QUOTED_STRING_LIMIT, appendIfTruncated); + } + + /** + * Converts the ProtonBuffer to a quoted string. + * + * @param buffer + * the {@link ByteBuffer} to convert into String format. + * @param stringLength + * the maximum length of stringified content (excluding the quotes, and truncated indicator) + * @param appendIfTruncated + * appends "...(truncated)" if not all of the payload is present in the string + * + * @return the converted string + */ + public static String toQuotedString(final ByteBuffer buffer, final int stringLength, final boolean appendIfTruncated) { + if (buffer == null) { + return "\"\""; + } + + StringBuilder str = new StringBuilder(); + str.append("\""); + + final int byteToRead = buffer.remaining(); + int size = 0; + boolean truncated = false; + + for (int i = 0; i < byteToRead; ++i) { + byte c = buffer.get(i); + + if (c > 31 && c < 127 && c != '\\') { + if (size + 1 <= stringLength) { + size += 1; + str.append((char) c); + } else { + truncated = true; + break; + } + } else { + if (size + 4 <= stringLength) { + size += 4; + str.append(String.format("\\x%02x", c)); + } else { + truncated = true; + break; + } + } + } + + str.append("\""); + + if (truncated && appendIfTruncated) { + str.append("...(truncated)"); + } + + return str.toString(); + } +}