|
27 | 27 | import com.rabbitmq.stream.MessageBuilder; |
28 | 28 | import com.rabbitmq.stream.StreamException; |
29 | 29 | import com.rabbitmq.stream.impl.Client.Broker; |
| 30 | +import com.rabbitmq.stream.impl.Client.ClientParameters; |
30 | 31 | import com.rabbitmq.stream.impl.Client.StreamMetadata; |
31 | 32 | import io.netty.channel.EventLoopGroup; |
32 | 33 | import io.netty.channel.nio.NioEventLoopGroup; |
|
39 | 40 | import java.lang.annotation.Retention; |
40 | 41 | import java.lang.annotation.RetentionPolicy; |
41 | 42 | import java.lang.annotation.Target; |
| 43 | +import java.lang.reflect.AnnotatedElement; |
42 | 44 | import java.lang.reflect.Field; |
43 | 45 | import java.lang.reflect.Method; |
44 | 46 | import java.nio.charset.StandardCharsets; |
|
47 | 49 | import java.util.Collections; |
48 | 50 | import java.util.List; |
49 | 51 | import java.util.Map; |
| 52 | +import java.util.Optional; |
50 | 53 | import java.util.Set; |
51 | 54 | import java.util.UUID; |
52 | 55 | import java.util.concurrent.ConcurrentHashMap; |
|
71 | 74 | import org.junit.jupiter.api.extension.ExtensionContext; |
72 | 75 | import org.mockito.invocation.InvocationOnMock; |
73 | 76 | import org.mockito.stubbing.Answer; |
| 77 | +import org.slf4j.LoggerFactory; |
74 | 78 |
|
75 | 79 | public final class TestUtils { |
76 | 80 |
|
@@ -363,6 +367,15 @@ static Map<String, StreamMetadata> metadata(Broker leader, List<Broker> replicas |
363 | 367 | @ExtendWith(DisabledIfTlsNotEnabledCondition.class) |
364 | 368 | public @interface DisabledIfTlsNotEnabled {} |
365 | 369 |
|
| 370 | + @Target({ElementType.TYPE, ElementType.METHOD}) |
| 371 | + @Retention(RetentionPolicy.RUNTIME) |
| 372 | + @Documented |
| 373 | + @ExtendWith(BrokerVersionAtLeastCondition.class) |
| 374 | + public @interface BrokerVersionAtLeast { |
| 375 | + |
| 376 | + String value(); |
| 377 | + } |
| 378 | + |
366 | 379 | interface TaskWithException { |
367 | 380 |
|
368 | 381 | void run(Object context) throws Exception; |
@@ -398,7 +411,7 @@ private static ExtensionContext.Store store(ExtensionContext extensionContext) { |
398 | 411 | return extensionContext.getRoot().getStore(NAMESPACE); |
399 | 412 | } |
400 | 413 |
|
401 | | - private static EventLoopGroup eventLoopGroup(ExtensionContext context) { |
| 414 | + static EventLoopGroup eventLoopGroup(ExtensionContext context) { |
402 | 415 | return (EventLoopGroup) store(context).get("nettyEventLoopGroup"); |
403 | 416 | } |
404 | 417 |
|
@@ -570,6 +583,96 @@ public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext con |
570 | 583 | } |
571 | 584 | } |
572 | 585 |
|
| 586 | + static class BrokerVersionAtLeastCondition implements ExecutionCondition { |
| 587 | + |
| 588 | + @Override |
| 589 | + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { |
| 590 | + Optional<AnnotatedElement> element = context.getElement(); |
| 591 | + // String expectedVersion = annotation.get().getAnnotation(BrokerVersionAtLeast.class); |
| 592 | + BrokerVersionAtLeast annotation = element.get().getAnnotation(BrokerVersionAtLeast.class); |
| 593 | + if (annotation == null) { |
| 594 | + return ConditionEvaluationResult.enabled("No broker version requirement"); |
| 595 | + } else { |
| 596 | + EventLoopGroup eventLoopGroup = StreamTestInfrastructureExtension.eventLoopGroup(context); |
| 597 | + if (eventLoopGroup == null) { |
| 598 | + throw new IllegalStateException( |
| 599 | + "The event loop group must be in the test context to use " |
| 600 | + + BrokerVersionAtLeast.class.getSimpleName() |
| 601 | + + ", use the " |
| 602 | + + StreamTestInfrastructureExtension.class.getSimpleName() |
| 603 | + + " extension in the test"); |
| 604 | + } |
| 605 | + Client client = new Client(new ClientParameters().eventLoopGroup(eventLoopGroup)); |
| 606 | + String expectedVersion = annotation.value(); |
| 607 | + String brokerVersion = client.brokerVersion(); |
| 608 | + if (atLeastVersion(expectedVersion, brokerVersion)) { |
| 609 | + return ConditionEvaluationResult.enabled( |
| 610 | + "Broker version requirement met, expected " |
| 611 | + + expectedVersion |
| 612 | + + ", actual " |
| 613 | + + brokerVersion); |
| 614 | + } else { |
| 615 | + return ConditionEvaluationResult.disabled( |
| 616 | + "Broker version requirement not met, expected " |
| 617 | + + expectedVersion |
| 618 | + + ", actual " |
| 619 | + + brokerVersion); |
| 620 | + } |
| 621 | + } |
| 622 | + } |
| 623 | + } |
| 624 | + |
| 625 | + private static String currentVersion(String currentVersion) { |
| 626 | + // versions built from source: 3.7.0+rc.1.4.gedc5d96 |
| 627 | + if (currentVersion.contains("+")) { |
| 628 | + currentVersion = currentVersion.substring(0, currentVersion.indexOf("+")); |
| 629 | + } |
| 630 | + // alpha (snapshot) versions: 3.7.0~alpha.449-1 |
| 631 | + if (currentVersion.contains("~")) { |
| 632 | + currentVersion = currentVersion.substring(0, currentVersion.indexOf("~")); |
| 633 | + } |
| 634 | + // alpha (snapshot) versions: 3.7.1-alpha.40 |
| 635 | + if (currentVersion.contains("-")) { |
| 636 | + currentVersion = currentVersion.substring(0, currentVersion.indexOf("-")); |
| 637 | + } |
| 638 | + return currentVersion; |
| 639 | + } |
| 640 | + |
| 641 | + static boolean atLeastVersion(String expectedVersion, String currentVersion) { |
| 642 | + if (currentVersion.contains("alpha-stream")) { |
| 643 | + return true; |
| 644 | + } |
| 645 | + try { |
| 646 | + currentVersion = currentVersion(currentVersion); |
| 647 | + return "0.0.0".equals(currentVersion) || versionCompare(currentVersion, expectedVersion) >= 0; |
| 648 | + } catch (RuntimeException e) { |
| 649 | + LoggerFactory.getLogger(TestUtils.class) |
| 650 | + .warn("Unable to parse broker version {}", currentVersion, e); |
| 651 | + throw e; |
| 652 | + } |
| 653 | + } |
| 654 | + |
| 655 | + /** |
| 656 | + * https://stackoverflow.com/questions/6701948/efficient-way-to-compare-version-strings-in-java |
| 657 | + */ |
| 658 | + static int versionCompare(String str1, String str2) { |
| 659 | + String[] vals1 = str1.split("\\."); |
| 660 | + String[] vals2 = str2.split("\\."); |
| 661 | + int i = 0; |
| 662 | + // set index to first non-equal ordinal or length of shortest version string |
| 663 | + while (i < vals1.length && i < vals2.length && vals1[i].equals(vals2[i])) { |
| 664 | + i++; |
| 665 | + } |
| 666 | + // compare first non-equal ordinal number |
| 667 | + if (i < vals1.length && i < vals2.length) { |
| 668 | + int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i])); |
| 669 | + return Integer.signum(diff); |
| 670 | + } |
| 671 | + // the strings are equal or one string is a substring of the other |
| 672 | + // e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4" |
| 673 | + return Integer.signum(vals1.length - vals2.length); |
| 674 | + } |
| 675 | + |
573 | 676 | static class CountDownLatchAssert implements AssertDelegateTarget { |
574 | 677 |
|
575 | 678 | private static final Duration TIMEOUT = Duration.ofSeconds(10); |
|
0 commit comments