This is an automated email from the ASF dual-hosted git repository. tabish pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/qpid-protonj2.git
commit 7ae9aa3ef24d8e15f578c52ec8aabedaf2363175 Author: Timothy Bish <tabish...@gmail.com> AuthorDate: Thu Aug 22 16:41:38 2024 -0400 PROTON-2845 Add transfer expectation API for any valid body section Allows writing tests where any valid body section is fine in cases where the test is only looking at basic structure of the message or only looking at annotations, properties etc --- .../matchers/transport/TransferMessageMatcher.java | 33 +++++- .../matchers/types/EncodedAmqpTypeMatcher.java | 3 +- .../types/EncodedAnyBodySectionMatcher.java | 113 +++++++++++++++++++++ .../matchers/types/EncodedBodySectionMatcher.java | 33 ++++++ .../protonj2/test/driver/SenderHandlingTest.java | 52 ++++++++++ 5 files changed, 230 insertions(+), 4 deletions(-) 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 decd74ff..66972c20 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 @@ -30,8 +30,9 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedInteger; import org.apache.qpid.protonj2.test.driver.codec.transport.Transfer; import org.apache.qpid.protonj2.test.driver.expectations.TransferExpectation; import org.apache.qpid.protonj2.test.driver.matchers.types.EncodedAmqpSequenceMatcher; -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.EncodedAnyBodySectionMatcher; +import org.apache.qpid.protonj2.test.driver.matchers.types.EncodedBodySectionMatcher; import org.apache.qpid.protonj2.test.driver.matchers.types.EncodedDataMatcher; import org.apache.qpid.protonj2.test.driver.util.StringUtils; import org.hamcrest.Description; @@ -53,7 +54,7 @@ public class TransferMessageMatcher extends TypeSafeMatcher<ByteBuffer> { private MessageAnnotationsMatcher messageAnnotationsMatcher; private PropertiesMatcher propertiesMatcher; private ApplicationPropertiesMatcher applicationPropertiesMatcher; - private List<EncodedAmqpTypeMatcher> bodySectionMatchers = new ArrayList<>(); + private List<EncodedBodySectionMatcher> bodySectionMatchers = new ArrayList<>(); private FooterMatcher footersMatcher; // String buckets for mismatch error descriptions. @@ -378,6 +379,34 @@ public class TransferMessageMatcher extends TypeSafeMatcher<ByteBuffer> { return this; } + public TransferMessageMatcher withValidBodySection() { + final EncodedAnyBodySectionMatcher matcher = new EncodedAnyBodySectionMatcher(footersMatcher != null); + + if (headersMatcher != null) { + headersMatcher.getInnerMatcher().setAllowTrailingBytes(true); + } + if (deliveryAnnotationsMatcher != null) { + deliveryAnnotationsMatcher.getInnerMatcher().setAllowTrailingBytes(true); + } + if (messageAnnotationsMatcher != null) { + messageAnnotationsMatcher.getInnerMatcher().setAllowTrailingBytes(true); + } + if (propertiesMatcher != null) { + propertiesMatcher.getInnerMatcher().setAllowTrailingBytes(true); + } + if (applicationPropertiesMatcher != null) { + applicationPropertiesMatcher.getInnerMatcher().setAllowTrailingBytes(true); + } + + if (!bodySectionMatchers.isEmpty()) { + bodySectionMatchers.get(bodySectionMatchers.size() - 1).setAllowTrailingBytes(true); + } + + bodySectionMatchers.add(matcher); + + return this; + } + public FooterMatcher withFooters() { if (footersMatcher == null) { footersMatcher = new FooterMatcher(this); diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAmqpTypeMatcher.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAmqpTypeMatcher.java index 0eaf4de0..63d4fa3f 100644 --- a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAmqpTypeMatcher.java +++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAmqpTypeMatcher.java @@ -33,9 +33,8 @@ import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.Matchers; -import org.hamcrest.TypeSafeMatcher; -public abstract class EncodedAmqpTypeMatcher extends TypeSafeMatcher<ByteBuffer> { +public abstract class EncodedAmqpTypeMatcher extends EncodedBodySectionMatcher { private final Symbol descriptorSymbol; private final UnsignedLong descriptorCode; diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAnyBodySectionMatcher.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAnyBodySectionMatcher.java new file mode 100644 index 00000000..0265d7a6 --- /dev/null +++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedAnyBodySectionMatcher.java @@ -0,0 +1,113 @@ +/* + * 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.matchers.types; + +import java.nio.ByteBuffer; + +import org.apache.qpid.protonj2.test.driver.codec.Codec; +import org.apache.qpid.protonj2.test.driver.codec.primitives.DescribedType; +import org.apache.qpid.protonj2.test.driver.codec.primitives.Symbol; +import org.apache.qpid.protonj2.test.driver.codec.primitives.UnsignedLong; +import org.hamcrest.Description; + +/** + * Encoded type matcher that validates that the encoded AMQP type is a valid body + * section in the standard AMQP message format set of allowed sections, Data, AmqpValue + * or AmqpSequence. + */ +public class EncodedAnyBodySectionMatcher extends EncodedBodySectionMatcher { + + private static final Symbol AMQP_VALUE_DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:amqp-value:*"); + private static final UnsignedLong AMQP_VALUE_DESCRIPTOR_CODE = UnsignedLong.valueOf(0x0000000000000077L); + private static final Symbol AMQP_SEQUENCE_DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:amqp-sequence:list"); + private static final UnsignedLong AMQP_SEQUENCE_DESCRIPTOR_CODE = UnsignedLong.valueOf(0x0000000000000076L); + private static final Symbol DATA_DESCRIPTOR_SYMBOL = Symbol.valueOf("amqp:data:binary"); + private static final UnsignedLong DATA_DESCRIPTOR_CODE = UnsignedLong.valueOf(0x0000000000000075L); + + private boolean allowTrailingBytes; + private DescribedType decodedDescribedType; + private boolean unexpectedTrailingBytes; + + public EncodedAnyBodySectionMatcher() { + this(false); + } + + public EncodedAnyBodySectionMatcher(boolean allowTrailingBytes) { + this.allowTrailingBytes = allowTrailingBytes; + } + + @Override + public void setAllowTrailingBytes(boolean allowTrailingBytes) { + this.allowTrailingBytes = allowTrailingBytes; + } + + @Override + public boolean isAllowTrailngBytes() { + return allowTrailingBytes; + } + + @Override + protected boolean matchesSafely(ByteBuffer receivedBinary) { + int length = receivedBinary.remaining(); + Codec data = Codec.Factory.create(); + long decoded = data.decode(receivedBinary); + decodedDescribedType = data.getDescribedType(); + Object descriptor = decodedDescribedType.getDescriptor(); + + if (!AMQP_VALUE_DESCRIPTOR_CODE.equals(descriptor) && + !AMQP_VALUE_DESCRIPTOR_SYMBOL.equals(descriptor) && + !AMQP_SEQUENCE_DESCRIPTOR_CODE.equals(descriptor) && + !AMQP_SEQUENCE_DESCRIPTOR_SYMBOL.equals(descriptor) && + !DATA_DESCRIPTOR_CODE.equals(descriptor) && + !DATA_DESCRIPTOR_SYMBOL.equals(descriptor)) { + + return false; + } + + // We are strict and require the encoding to have a value. + if (decodedDescribedType.getDescribed() == null) { + return false; + } + + if (decoded < length && !allowTrailingBytes) { + unexpectedTrailingBytes = true; + return false; + } + + return true; + } + + @Override + public void describeTo(Description description) { + description.appendText("a body section of any AMQP Value, Sequence or Data section."); + } + + @Override + protected void describeMismatchSafely(ByteBuffer item, Description mismatchDescription) { + mismatchDescription.appendText("\nActual encoded form: ").appendValue(item); + + if (decodedDescribedType != null) { + mismatchDescription.appendText("\nExpected descriptor of AMQP Value, Sequence or Data section: "); + mismatchDescription.appendText("\nActual described type: ").appendValue(decodedDescribedType); + } + + if (unexpectedTrailingBytes) { + mismatchDescription.appendText("\nUnexpected trailing bytes in provided bytes after decoding!"); + } + } +} diff --git a/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedBodySectionMatcher.java b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedBodySectionMatcher.java new file mode 100644 index 00000000..839e3686 --- /dev/null +++ b/protonj2-test-driver/src/main/java/org/apache/qpid/protonj2/test/driver/matchers/types/EncodedBodySectionMatcher.java @@ -0,0 +1,33 @@ +/* + * 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.matchers.types; + +import java.nio.ByteBuffer; + +import org.hamcrest.TypeSafeMatcher; + +/** + * Base marker type for any Body Section type matcher. + */ +public abstract class EncodedBodySectionMatcher extends TypeSafeMatcher<ByteBuffer> { + + public abstract void setAllowTrailingBytes(boolean allowTrailingBytes); + + public abstract boolean isAllowTrailngBytes(); + +} diff --git a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/SenderHandlingTest.java b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/SenderHandlingTest.java index b4858625..3ea157aa 100644 --- a/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/SenderHandlingTest.java +++ b/protonj2-test-driver/src/test/java/org/apache/qpid/protonj2/test/driver/SenderHandlingTest.java @@ -1006,4 +1006,56 @@ class SenderHandlingTest extends TestPeerTestsBase { peer.waitForScriptToComplete(5, TimeUnit.SECONDS); } } + + @Test + public void testMatchAnyBodySectionMatcherExpectation() throws Exception { + try (ProtonTestServer peer = new ProtonTestServer(); + ProtonTestClient client = new ProtonTestClient()) { + + peer.expectAMQPHeader().respondWithAMQPHeader(); + peer.expectOpen().respond(); + peer.expectBegin().respond(); + peer.expectAttach().ofSender().respond().withHandle(42); + peer.remoteFlow().withLinkCredit(1).queue(); + // Script a full message using the inject API + peer.expectTransfer().withMessage().withMessageFormat(1) + .withProperties().withCorrelationId("test").and() + .withDeliveryAnnotations().also() + .withApplicationProperties().and() + .withMessageAnnotations().also() + .withValidBodySection() + .withHeader().withDurability(true).and() + .withFooters(); + peer.start(); + + URI remoteURI = peer.getServerURI(); + + LOG.info("Test started, peer listening on: {}", remoteURI); + + client.connect(remoteURI.getHost(), remoteURI.getPort()); + client.expectAMQPHeader(); + client.expectOpen(); + client.expectBegin(); + client.expectAttach().ofReceiver().withHandle(42); + client.expectFlow().withLinkCredit(1).withHandle(42); + client.remoteTransfer().withMessage().withMessageFormat(1) + .withHeader().withDurability(true).also() + .withApplicationProperties().withProperty("ap", "pa").also() + .withDeliveryAnnotations().withAnnotation("da", "ad").also() + .withProperties().withCorrelationId("test").also() + .withMessageAnnotations().withAnnotation("ma", "am").also() + .withFooter().withFooter("footer", "1").also() + .withBody().withData(new byte[] {0, 1, 2}).also() + .queue(); + + // Now start and then await the remote grant of credit and out send of a transfer + client.remoteHeader(AMQPHeader.getAMQPHeader()).now(); + client.remoteOpen().now(); + client.remoteBegin().now(); + client.remoteAttach().ofSender().withHandle(2).now(); + + client.waitForScriptToComplete(5, TimeUnit.SECONDS); + peer.waitForScriptToComplete(5, TimeUnit.SECONDS); + } + } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org