INT-3889: Fix AMQP PubSub Channel Test Race Cond.

JIRA: https://jira.spring.io/browse/INT-3889

The test case `pubSubLostConnectionTest` forces the connection closed and
then sends another message. It is testing recovery.

However, since the pub/sub queue is an auto-delete queue, we need to wait until
the new consumer is established and the queue exists, before sending the message after the test.
Otherwise, since the exchange is fanout, the message is simply dropped.

Also set `@DirtiesContext` on other tests.

Also remove non-tests.

I have __not__ removed the diagnostics because I don't believe this addresses AMQP-543.
This commit is contained in:
Gary Russell
2015-11-19 11:14:25 -05:00
committed by Artem Bilan
parent c67cce1563
commit 73bc3fedf5
16 changed files with 61 additions and 290 deletions

View File

@@ -20,12 +20,15 @@ import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -34,6 +37,7 @@ import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.listener.BlockingQueueConsumer;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -74,6 +78,11 @@ public class ChannelTests extends LogAdjustingTestSupport {
super("org.springframework.integration", "org.springframework.integration.amqp", "org.springframework.amqp");
}
@After
public void tearDown() {
new RabbitAdmin(this.factory).deleteExchange("si.fanout.foo");
}
@Test
public void pubSubLostConnectionTest() throws Exception {
final CyclicBarrier latch = new CyclicBarrier(2);
@@ -88,16 +97,36 @@ public class ChannelTests extends LogAdjustingTestSupport {
}
}
});
channel.send(new GenericMessage<String>("foo"));
this.channel.send(new GenericMessage<String>("foo"));
latch.await(10, TimeUnit.SECONDS);
latch.reset();
BlockingQueueConsumer consumer = (BlockingQueueConsumer) TestUtils
.getPropertyValue(this.channel, "container.consumers", Map.class).keySet().iterator().next();
factory.destroy();
channel.send(new GenericMessage<String>("bar"));
waitForNewConsumer(this.channel, consumer);
this.channel.send(new GenericMessage<String>("bar"));
latch.await(10, TimeUnit.SECONDS);
channel.destroy();
this.channel.destroy();
assertEquals(0, TestUtils.getPropertyValue(factory, "connectionListener.delegates", Collection.class).size());
}
private void waitForNewConsumer(PublishSubscribeAmqpChannel channel, BlockingQueueConsumer consumer)
throws Exception {
BlockingQueueConsumer newConsumer = (BlockingQueueConsumer) TestUtils
.getPropertyValue(channel, "container.consumers", Map.class).keySet().iterator().next();
int n = 0;
boolean newConsumerIsConsuming = newConsumer != consumer && TestUtils.getPropertyValue(newConsumer,
"consumerTags", Map.class).size() > 0;
while (n++ < 100 && !newConsumerIsConsuming) {
Thread.sleep(100);
newConsumer = (BlockingQueueConsumer) TestUtils
.getPropertyValue(channel, "container.consumers", Map.class).keySet().iterator().next();
newConsumerIsConsuming = newConsumer != consumer && TestUtils.getPropertyValue(newConsumer,
"consumerTags", Map.class).size() > 0;
}
assertTrue("Failed to restart consumer", n < 100);
}
/*
* Verify queue is declared if not present and not declared if it is already present.
*/
@@ -144,6 +173,10 @@ public class ChannelTests extends LogAdjustingTestSupport {
channelFactoryBean.afterPropertiesSet();
channel = channelFactoryBean.getObject();
assertThat(channel, instanceOf(PublishSubscribeAmqpChannel.class));
RabbitAdmin rabbitAdmin = new RabbitAdmin(this.factory);
rabbitAdmin.deleteQueue("testChannel");
rabbitAdmin.deleteExchange("si.fanout.testChannel");
}
}

View File

@@ -32,6 +32,7 @@ import org.springframework.integration.support.utils.IntegrationUtils;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -42,6 +43,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class AmqpChannelParserTests {
@Autowired

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -39,6 +39,7 @@ import org.springframework.integration.amqp.inbound.AmqpInboundChannelAdapter;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -50,6 +51,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class AmqpInboundChannelAdapterParserTests {
@Autowired

View File

@@ -49,6 +49,7 @@ import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
@@ -63,6 +64,7 @@ import org.springframework.util.ReflectionUtils;
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class AmqpInboundGatewayParserTests {
@Autowired

View File

@@ -329,7 +329,7 @@ public class AmqpOutboundGatewayParserTests {
public void testInt2971HeaderMapperAndMappedHeadersExclusivity() {
try {
new ClassPathXmlApplicationContext("AmqpOutboundGatewayParserTests-headerMapper-fail-context.xml",
this.getClass()).close();;
this.getClass()).close();
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: The 'header-mapper' attribute " +

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:amqp="http://www.springframework.org/schema/integration/amqp"
xmlns:console="http://www.springframework.org/schema/integration/stream"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<console:stdin-channel-adapter channel="rabbitChannel">
<poller fixed-delay="1000" max-messages-per-poll="1" />
</console:stdin-channel-adapter>
<amqp:channel id="rabbitChannel" />
<logging-channel-adapter channel="rabbitChannel" expression="'1:' + payload" logger-name="test.p2p.1"/>
<logging-channel-adapter channel="rabbitChannel" expression="'2:' + payload" logger-name="test.p2p.2"/>
<logging-channel-adapter channel="rabbitChannel" expression="'3:' + payload" logger-name="test.p2p.3"/>
<rabbit:connection-factory id="rabbitConnectionFactory" />
</beans:beans>

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2002-2011 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.integration.amqp.config;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Mark Fisher
* @since 2.1
*/
public class ChannelSample {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("ChannelSample-context.xml", ChannelSample.class);
}
}

View File

@@ -1,49 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:amqp="http://www.springframework.org/schema/integration/amqp"
xmlns:console="http://www.springframework.org/schema/integration/stream" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- From STDIN To RabbitMQ -->
<console:stdin-channel-adapter channel="toRabbit">
<poller fixed-delay="1000" max-messages-per-poll="1" />
</console:stdin-channel-adapter>
<channel id="toRabbit" />
<amqp:outbound-channel-adapter channel="toRabbit"
exchange-name="si.test.exchange"
routing-key="si.test.binding"
amqp-template="amqpTemplate" />
<!-- From RabbitMQ To STDOUT -->
<amqp:inbound-channel-adapter channel="fromRabbit"
queue-names="si.test.queue"
connection-factory="connectionFactory" />
<logging-channel-adapter id="fromRabbit" log-full-message="true" logger-name="test.logger"/>
<!-- <console:stdout-channel-adapter id="fromRabbit" append-newline="true" /> -->
<!-- Infrastructure -->
<rabbit:connection-factory id="connectionFactory" />
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory" />
<rabbit:admin connection-factory="connectionFactory" />
<rabbit:queue name="si.test.queue" />
<rabbit:direct-exchange name="si.test.exchange">
<rabbit:bindings>
<rabbit:binding queue="si.test.queue" key="si.test.binding" />
</rabbit:bindings>
</rabbit:direct-exchange>
</beans:beans>

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2002-2011 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.integration.amqp.config;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Mark Fisher
* @since 2.1
*/
public class EchoSample {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("EchoSample-context.xml", EchoSample.class);
}
}

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:amqp="http://www.springframework.org/schema/integration/amqp"
xmlns:console="http://www.springframework.org/schema/integration/stream" xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- From STDIN To RabbitMQ -->
<console:stdin-channel-adapter id="outboundGatewayRequests">
<poller fixed-delay="1000" max-messages-per-poll="1" />
</console:stdin-channel-adapter>
<amqp:outbound-gateway request-channel="outboundGatewayRequests"
reply-channel="outboundGatewayReplies"
routing-key="si.test.gateway.echo"
amqp-template="amqpTemplate" />
<!-- From RabbitMQ To STDOUT -->
<amqp:inbound-gateway request-channel="inboundGatewayRequests" queue-names="si.test.gateway.echo" connection-factory="connectionFactory" />
<transformer input-channel="inboundGatewayRequests" expression="payload.toString().toUpperCase()"/>
<!-- <logging-channel-adapter id="outboundGatewayReplies" log-full-message="true"/> -->
<console:stdout-channel-adapter id="outboundGatewayReplies" append-newline="true" />
<!-- Infrastructure -->
<rabbit:connection-factory id="connectionFactory"/>
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:queue name="si.test.gateway.echo"/>
</beans:beans>

View File

@@ -1,37 +0,0 @@
/*
* Copyright 2002-2011 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.integration.amqp.config;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Mark Fisher
* @since 2.1
*/
public class GatewayEchoTests {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("GatewayEchoTests-context.xml", GatewayEchoTests.class);
}
@Test
/**
* Dummy test to satisfy maven in some environments.
*/
public void test() {}
}

View File

@@ -12,11 +12,11 @@
<rabbit:admin connection-factory="connectionFactory" />
<rabbit:queue name="si.test.queue" />
<rabbit:queue name="si.ob.gw.integration" auto-delete="true" />
<rabbit:direct-exchange name="si.test.exchange">
<rabbit:direct-exchange name="si.ob.gw.integration.ex" auto-delete="true">
<rabbit:bindings>
<rabbit:binding queue="si.test.queue" key="si.test.binding"/>
<rabbit:binding queue="si.ob.gw.integration" key="si.ob.gw.integration"/>
</rabbit:bindings>
</rabbit:direct-exchange>
@@ -28,13 +28,13 @@
<amqp:outbound-gateway request-channel="toRabbit"
reply-channel="fromRabbit"
exchange-name="si.test.exchange"
routing-key="si.test.binding"
exchange-name="si.ob.gw.integration.ex"
routing-key="si.ob.gw.integration"
amqp-template="amqpTemplate"/>
<amqp:inbound-gateway request-channel="amqpIn"
connection-factory="connectionFactory"
queue-names="si.test.queue"/>
queue-names="si.ob.gw.integration"/>
<transformer input-channel="amqpIn" expression="payload.toUpperCase()"/>

View File

@@ -29,6 +29,7 @@ import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@@ -40,10 +41,11 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class OutboundGatewayIntegrationTests {
@ClassRule
public static final BrokerRunning brokerIsRunning = BrokerRunning.isRunningWithEmptyQueues("si.test.queue");
public static final BrokerRunning brokerIsRunning = BrokerRunning.isRunning();
@Autowired
private MessageChannel toRabbit;

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 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.
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.After;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
@@ -37,9 +38,9 @@ import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
import org.springframework.integration.config.IntegrationEvaluationContextFactoryBean;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.handler.MessageProcessor;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.util.ClassUtils;
@@ -56,6 +57,11 @@ public class OutboundGatewayTests {
private final ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext(getClass().getSimpleName() + "-context.xml", getClass());
@After
public void tearDown() {
context.close();
}
@Test
public void testVanillaConfiguration() throws Exception {
assertTrue(context.getBeanFactory().containsBeanDefinition("vanilla"));

View File

@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:amqp="http://www.springframework.org/schema/integration/amqp"
xmlns:console="http://www.springframework.org/schema/integration/stream"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="http://www.springframework.org/schema/integration/amqp http://www.springframework.org/schema/integration/amqp/spring-integration-amqp.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<console:stdin-channel-adapter channel="rabbitChannel">
<poller fixed-delay="1000" max-messages-per-poll="1" />
</console:stdin-channel-adapter>
<amqp:publish-subscribe-channel id="rabbitChannel" />
<logging-channel-adapter channel="rabbitChannel" expression="'1:' + payload" logger-name="test.pubsub.1"/>
<logging-channel-adapter channel="rabbitChannel" expression="'2:' + payload" logger-name="test.pubsub.2"/>
<logging-channel-adapter channel="rabbitChannel" expression="'3:' + payload" logger-name="test.pubsub.3"/>
<rabbit:connection-factory id="rabbitConnectionFactory" />
</beans:beans>

View File

@@ -1,31 +0,0 @@
/*
* Copyright 2002-2011 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.integration.amqp.config;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author Mark Fisher
* @since 2.1
*/
public class PubSubChannelSample {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("PubSubChannelSample-context.xml", PubSubChannelSample.class);
}
}