From 38f38c4d35b27d3f190a848f4e24b6ed0a3c6aa1 Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Thu, 16 Mar 2017 13:47:00 -0400 Subject: [PATCH] AMQP-728: Make BrokerRunning More Configurable JIRA: https://jira.spring.io/browse/AMQP-728 Resolves: #592 Add more options to configure the `BrokerRunning` JUnit `@Rule`. Fix missing exclude from build.gradle for the junit subproject. Polishing - PR Comments More Polishing **Cherry-pick to 1.7.x** --- build.gradle | 5 +- .../amqp/rabbit/junit/BrokerRunning.java | 189 +++++++++++++++++- .../amqp/rabbit/junit/BrokerRunningTests.java | 81 ++++++++ .../EnableRabbitIntegrationTests.java | 10 +- .../rabbit/connection/SSLConnectionTests.java | 3 +- src/reference/asciidoc/testing.adoc | 74 ++++++- 6 files changed, 347 insertions(+), 15 deletions(-) create mode 100644 spring-rabbit-junit/src/test/java/org/springframework/amqp/rabbit/junit/BrokerRunningTests.java diff --git a/build.gradle b/build.gradle index e1211d86..2b9a7f1f 100644 --- a/build.gradle +++ b/build.gradle @@ -244,7 +244,10 @@ project('spring-rabbit-junit') { compile "org.springframework:spring-core:$springVersion" compile "junit:junit:$junitVersion" compile "com.rabbitmq:amqp-client:$rabbitmqVersion" - compile "com.rabbitmq:http-client:$rabbitmqHttpClientVersion" + compile ("com.rabbitmq:http-client:$rabbitmqHttpClientVersion") { + exclude group: 'org.springframework', module: 'spring-web' + } + compile "org.springframework:spring-web:$springVersion" } diff --git a/spring-rabbit-junit/src/main/java/org/springframework/amqp/rabbit/junit/BrokerRunning.java b/spring-rabbit-junit/src/main/java/org/springframework/amqp/rabbit/junit/BrokerRunning.java index a6b746df..8cc0345d 100644 --- a/spring-rabbit-junit/src/main/java/org/springframework/amqp/rabbit/junit/BrokerRunning.java +++ b/spring-rabbit-junit/src/main/java/org/springframework/amqp/rabbit/junit/BrokerRunning.java @@ -79,17 +79,33 @@ import com.rabbitmq.http.client.Client; */ public final class BrokerRunning extends TestWatcher { + public static final String BROKER_ADMIN_URI = "RABBITMQ_TEST_ADMIN_URI"; + + public static final String BROKER_HOSTNAME = "RABBITMQ_TEST_HOSTNAME"; + + public static final String BROKER_PORT = "RABBITMQ_TEST_PORT"; + + public static final String BROKER_USER = "RABBITMQ_TEST_USER"; + + public static final String BROKER_PW = "RABBITMQ_TEST_PASSWORD"; + + public static final String BROKER_ADMIN_USER = "RABBITMQ_TEST_ADMIN_USER"; + + public static final String BROKER_ADMIN_PW = "RABBITMQ_TEST_ADMIN_PASSWORD"; + public static final String BROKER_REQUIRED = "RABBITMQ_SERVER_REQUIRED"; private static final String DEFAULT_QUEUE_NAME = BrokerRunning.class.getName(); - private static Log logger = LogFactory.getLog(BrokerRunning.class); + private static final Log logger = LogFactory.getLog(BrokerRunning.class); // Static so that we only test once on failure: speeds up test suite - private static Map brokerOnline = new HashMap(); + private static final Map brokerOnline = new HashMap(); // Static so that we only test once on failure - private static Map brokerOffline = new HashMap(); + private static final Map brokerOffline = new HashMap(); + + private static final Map environmentOverrides = new HashMap<>(); private final boolean assumeOnline; @@ -99,14 +115,57 @@ public final class BrokerRunning extends TestWatcher { private final String[] queues; - private final int defaultPort = BrokerTestUtils.getPort(); + private final int defaultPort = fromEnvironment(BROKER_PORT, null) == null ? BrokerTestUtils.getPort() + : Integer.valueOf(fromEnvironment(BROKER_PORT, null)); private int port; - private String hostName = null; + private String hostName = fromEnvironment(BROKER_HOSTNAME, "localhost"); + + private String adminUri = fromEnvironment(BROKER_ADMIN_URI, null); private ConnectionFactory connectionFactory; + private String user = fromEnvironment(BROKER_USER, "guest"); + + private String password = fromEnvironment(BROKER_PW, "guest"); + + private String adminUser = fromEnvironment(BROKER_ADMIN_USER, "guest"); + + private String adminPassword = fromEnvironment(BROKER_ADMIN_PW, "guest"); + + private String fromEnvironment(String key, String defaultValue) { + String environmentValue = environmentOverrides.get(key); + if (!StringUtils.hasText(environmentValue)) { + environmentValue = System.getenv(key); + } + if (StringUtils.hasText(environmentValue)) { + return environmentValue; + } + else { + return defaultValue; + } + } + + /** + * Set environment variable overrides for host, port etc. Will override any real + * environment variables, if present. + *

The variables will only apply to rule instances that are created after this + * method is called. + * The overrides will remain until + * @param environmentVariables the variables. + */ + public static void setEnvironmentVariableOverrides(Map environmentVariables) { + environmentOverrides.putAll(environmentVariables); + } + + /** + * Clear any environment variable overrides set in {@link #setEnvironmentVariableOverrides(Map)}. + */ + public static void clearEnvironmentVariableOverrides() { + environmentOverrides.clear(); + } + /** * Ensure the broker is running and has a empty queue(s) with the specified name(s) in the * default exchange. @@ -192,6 +251,105 @@ public final class BrokerRunning extends TestWatcher { this.hostName = hostName; } + /** + * Set the user for the amqp connection default "guest". + * @param user the user. + * @since 1.7.2 + */ + public void setUser(String user) { + this.user = user; + } + + /** + * Set the password for the amqp connection default "guest". + * @param password the password. + * @since 1.7.2 + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Set the uri for the REST API. + * @param adminUri the uri. + * @since 1.7.2 + */ + public void setAdminUri(String adminUri) { + this.adminUri = adminUri; + } + + /** + * Set the user for the management REST API connection default "guest". + * @param user the user. + * @since 1.7.2 + */ + public void setAdminUser(String user) { + this.adminUser = user; + } + + /** + * Set the password for the management REST API connection default "guest". + * @param password the password. + * @since 1.7.2 + */ + public void setAdminPassword(String password) { + this.adminPassword = password; + } + + /** + * Return the port. + * @return the port. + * @since 1.7.2 + */ + public int getPort() { + return this.port; + } + + /** + * Return the port. + * @return the port. + * @since 1.7.2 + */ + public String getHostName() { + return this.hostName; + } + + /** + * Return the user. + * @return the user. + * @since 1.7.2 + */ + public String getUser() { + return this.user; + } + + /** + * Return the password. + * @return the password. + * @since 1.7.2 + */ + public String getPassword() { + return this.password; + } + + /** + * Return the admin user. + * @return the user. + * @since 1.7.2 + */ + public String getAdminUser() { + return this.adminUser; + } + + /** + * Return the admin password. + * @return the password. + * @since 1.7.2 + */ + public String getAdminPassword() { + return this.adminPassword; + } + @Override public Statement apply(Statement base, Description description) { @@ -235,7 +393,7 @@ public final class BrokerRunning extends TestWatcher { } if (this.management) { - Client client = new Client("http://localhost:15672/api/", "guest", "guest"); + Client client = new Client(getAdminUri(), this.adminUser, this.adminPassword); if (!client.alivenessTest("/")) { throw new RuntimeException("Aliveness test failed for localhost:15672 guest/quest; " + "management not available"); @@ -389,10 +547,29 @@ public final class BrokerRunning extends TestWatcher { this.connectionFactory.setHost("localhost"); } this.connectionFactory.setPort(this.port); + this.connectionFactory.setUsername(this.user); + this.connectionFactory.setPassword(this.password); } return this.connectionFactory; } + /** + * Return the admin uri. + * @return the uri. + * @since 1.7.2 + */ + public String getAdminUri() { + if (!StringUtils.hasText(this.adminUri)) { + if (!StringUtils.hasText(this.hostName)) { + this.adminUri = "http://localhost:15672/api/"; + } + else { + this.adminUri = "http://" + this.hostName + ":15672/api/"; + } + } + return this.adminUri; + } + private void closeResources(Connection connection, Channel channel) { if (channel != null) { try { diff --git a/spring-rabbit-junit/src/test/java/org/springframework/amqp/rabbit/junit/BrokerRunningTests.java b/spring-rabbit-junit/src/test/java/org/springframework/amqp/rabbit/junit/BrokerRunningTests.java new file mode 100644 index 00000000..c2565eb3 --- /dev/null +++ b/spring-rabbit-junit/src/test/java/org/springframework/amqp/rabbit/junit/BrokerRunningTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed 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.springframework.amqp.rabbit.junit; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import org.springframework.beans.DirectFieldAccessor; + +import com.rabbitmq.client.ConnectionFactory; + +/** + * @author Gary Russell + * @since 1.7.2 + * + */ +public class BrokerRunningTests { + + @Test + public void testVars() { + BrokerRunning brokerRunning = BrokerRunning.isBrokerAndManagementRunning(); + brokerRunning.setAdminPassword("foo"); + brokerRunning.setAdminUser("bar"); + brokerRunning.setHostName("baz"); + brokerRunning.setPassword("qux"); + brokerRunning.setPort(1234); + brokerRunning.setUser("fiz"); + + assertEquals("http://baz:15672/api/", brokerRunning.getAdminUri()); + ConnectionFactory connectionFactory = brokerRunning.getConnectionFactory(); + assertEquals("baz", connectionFactory.getHost()); + assertEquals(1234, connectionFactory.getPort()); + assertEquals("fiz", connectionFactory.getUsername()); + assertEquals("qux", connectionFactory.getPassword()); + } + + @Test + public void testEnvironmentVars() { + Map vars = new HashMap<>(); + vars.put("RABBITMQ_TEST_ADMIN_PASSWORD", "FOO"); + vars.put("RABBITMQ_TEST_ADMIN_URI", "http://foo/bar"); + vars.put("RABBITMQ_TEST_ADMIN_USER", "BAR"); + vars.put("RABBITMQ_TEST_HOSTNAME", "BAZ"); + vars.put("RABBITMQ_TEST_PASSWORD", "QUX"); + vars.put("RABBITMQ_TEST_PORT", "2345"); + vars.put("RABBITMQ_TEST_USER", "FIZ"); + BrokerRunning.setEnvironmentVariableOverrides(vars); + BrokerRunning brokerRunning = BrokerRunning.isBrokerAndManagementRunning(); + + assertEquals("http://foo/bar", brokerRunning.getAdminUri()); + ConnectionFactory connectionFactory = brokerRunning.getConnectionFactory(); + assertEquals("BAZ", connectionFactory.getHost()); + assertEquals(2345, connectionFactory.getPort()); + assertEquals("FIZ", connectionFactory.getUsername()); + assertEquals("QUX", connectionFactory.getPassword()); + DirectFieldAccessor dfa = new DirectFieldAccessor(brokerRunning); + assertEquals("BAR", dfa.getPropertyValue("adminUser")); + assertEquals("FOO", dfa.getPropertyValue("adminPassword")); + + BrokerRunning.clearEnvironmentVariableOverrides(); + } + +} diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/EnableRabbitIntegrationTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/EnableRabbitIntegrationTests.java index c9683997..413c0113 100644 --- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/EnableRabbitIntegrationTests.java +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/annotation/EnableRabbitIntegrationTests.java @@ -1165,7 +1165,10 @@ public class EnableRabbitIntegrationTests { @Bean public ConnectionFactory rabbitConnectionFactory() { CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); - connectionFactory.setHost("localhost"); + connectionFactory.setHost(brokerRunning.getHostName()); + connectionFactory.setPort(brokerRunning.getPort()); + connectionFactory.setUsername(brokerRunning.getUser()); + connectionFactory.setPassword(brokerRunning.getPassword()); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setThreadNamePrefix("rabbitClientThread-"); executor.afterPropertiesSet(); @@ -1386,7 +1389,10 @@ public class EnableRabbitIntegrationTests { @Bean public ConnectionFactory rabbitConnectionFactory() { CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); - connectionFactory.setHost("localhost"); + connectionFactory.setHost(brokerRunning.getHostName()); + connectionFactory.setPort(brokerRunning.getPort()); + connectionFactory.setUsername(brokerRunning.getUser()); + connectionFactory.setPassword(brokerRunning.getPassword()); return connectionFactory; } diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/SSLConnectionTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/SSLConnectionTests.java index 9395c47f..a1635028 100644 --- a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/SSLConnectionTests.java +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/connection/SSLConnectionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2016 the original author or authors. + * Copyright 2014-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,6 @@ import org.apache.commons.logging.Log; import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; - import org.mockito.Mockito; import org.springframework.amqp.utils.test.TestUtils; diff --git a/src/reference/asciidoc/testing.adoc b/src/reference/asciidoc/testing.adoc index a877c2b2..a2edd8a6 100644 --- a/src/reference/asciidoc/testing.adoc +++ b/src/reference/asciidoc/testing.adoc @@ -266,9 +266,11 @@ to suspend the test thread. Spring AMQP _version 1.7_ provides an additional jar `spring-rabbit-junit`; this jar contains a couple of utility `@Rule` s for use when running JUnit tests. -`BrokerRunning` provides a mechanism to allow tests to succeed when a broker is not running on `localhost`. +===== BrokerRunning -It also has utility methods to delete queues and exchanges. +`BrokerRunning` provides a mechanism to allow tests to succeed when a broker is not running (on `localhost`, by default). + +It also has utility methods to initialize/empty queues, and delete queues and exchanges. Usage: @@ -284,10 +286,74 @@ public static void tearDown() { } ---- -Of course, there are times when you want tests to fail if there is no broker, such as a nightly CI build. +There are several `isRunning...` static methods such as `isBrokerAndManagementRunning()` which verifies the broker has the management plugin enabled. + +====== Configuring the Rule + +There are times when you want tests to fail if there is no broker, such as a nightly CI build. To disable the rule at runtime, set an environment variable `RABBITMQ_SERVER_REQUIRED` to `true`. -There are several `isRunning...` static methods such as `isBrokerAndManagementRunning()` which verifies the broker has the management plugin enabled. +You can override the broker properties, such as hostname in several ways: + +- Setters + +[source, java] +---- + +@ClassRule +public static BrokerRunning brokerRunning = BrokerRunning.isRunningWithEmptyQueues("foo", "bar"); + +static { + brokerRunning.setHostName("10.0.0.1") +} + +@AfterClass +public static void tearDown() { + brokerRunning.removeTestQueues("some.other.queue.too") // removes foo, bar as well +} +---- + +- Environment Variables + +The following environment variables are provided: + +[source, java] +---- +public static final String BROKER_ADMIN_URI = "RABBITMQ_TEST_ADMIN_URI"; +public static final String BROKER_HOSTNAME = "RABBITMQ_TEST_HOSTNAME"; +public static final String BROKER_PORT = "RABBITMQ_TEST_PORT"; +public static final String BROKER_USER = "RABBITMQ_TEST_USER"; +public static final String BROKER_PW = "RABBITMQ_TEST_PASSWORD"; +public static final String BROKER_ADMIN_USER = "RABBITMQ_TEST_ADMIN_USER"; +public static final String BROKER_ADMIN_PW = "RABBITMQ_TEST_ADMIN_PASSWORD"; +---- + +These will override the default settings (`localhost:5672` for amqp and `http://localhost:15672/api/` for the management REST API). + +Changing the host name affects both the amqp and management REST API connection (unless the admin uri is explicitly set). + +`BrokerRunning` also provides a `static` method: `setEnvironmentVariableOverrides` where you can pass in a map containing these variables; they override system environment variables. +This might be useful if you wish to use different configuration for tests in multiple test suites. +IMPORTANT: The method must be called before invoking any of the `isRunning()` static methods that create the rule instance. +Variable values will be applied to all instances created after this. +Invoke `clearEnvironmentVariableOverrides()` to reset the rule to use defaults (including any actual environment variables). + +In your test cases, you can use those properties when creating the connection factory: + +[source, java] +---- +@Bean +public ConnectionFactory rabbitConnectionFactory() { + CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); + connectionFactory.setHost(brokerRunning.getHostName()); + connectionFactory.setPort(brokerRunning.getPort()); + connectionFactory.setUsername(brokerRunning.getUser()); + connectionFactory.setPassword(brokerRunning.getPassword()); + return connectionFactory; +} +---- + +===== LongRunningIntegrationTest `LongRunningIntegrationTest` is a rule that disables long running tests; you might want to use this on a developer system but ensure that the rule is disabled on, for example, nightly CI builds.