/* * Copyright 2015-2016 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.dsl.test.reactivestreams; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.Matchers.anyLong; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.spy; import java.lang.reflect.Method; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.mockito.stubbing.Answer; import org.reactivestreams.Publisher; import org.reactivestreams.tck.PublisherVerification; import org.reactivestreams.tck.TestEnvironment; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.springframework.beans.DirectFieldAccessor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.Lifecycle; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.integration.test.util.TestUtils; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.GenericMessage; import org.springframework.mock.env.MockEnvironment; /** * @author Artem Bilan * @since 1.1 */ public abstract class AbstractPublisherIntegrationFlowVerification extends PublisherVerification<Message<?>> { private static final String ELEMENTS = "publisher.elements"; private static final String FAILED_PUBLISHER = "failed.publisher"; private static final String REQUEST_COMPLETE = "request.complete"; private static final String TEST_METHOD = "test.method"; protected final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor(); protected volatile AnnotationConfigApplicationContext applicationContext; protected volatile TestEnvironment originalEnvironment; protected boolean completionSignalRequired; protected String testName; public AbstractPublisherIntegrationFlowVerification() { super(new TestEnvironment(2000), 1000); this.originalEnvironment = TestUtils.getPropertyValue(this, "env", TestEnvironment.class); } @BeforeMethod public void setUp(Method method) throws Exception { this.setUp(); this.testName = method.getName(); DirectFieldAccessor dfa = new DirectFieldAccessor(this); dfa.setPropertyValue("env", this.originalEnvironment); this.completionSignalRequired = false; if (!testName.equals("required_spec313_cancelMustMakeThePublisherEventuallyDropAllReferencesToTheSubscriber")) { if (testName.equals("stochastic_spec103_mustSignalOnMethodsSequentially")) { this.completionSignalRequired = true; } else { TestEnvironment env = new TestEnvironment(2000) { @Override public <T> ManualSubscriber<T> newManualSubscriber(Publisher<T> pub, long timeoutMillis) throws InterruptedException { ManualSubscriber<T> subscriber = spy(super.newManualSubscriber(pub, timeoutMillis)); Answer<Object> onCompleteAnswer = invocation -> { applicationContext.getBean("publisher", Lifecycle.class).stop(); return invocation.callRealMethod(); }; willAnswer(onCompleteAnswer).given(subscriber).expectCompletion(anyLong(), anyString()); willAnswer(onCompleteAnswer).given(subscriber).requestEndOfStream(anyLong(), anyString()); willAnswer(invocation -> { if (applicationContext.containsBean("input")) { MessageChannel input = applicationContext.getBean("input", MessageChannel.class); final Long n = (Long) invocation.getArguments()[0]; scheduledExecutor.schedule(() -> { for (int i = 0; i < n; i++) { input.send(new GenericMessage<>(Math.random())); } }, 100, TimeUnit.MILLISECONDS); } return invocation.callRealMethod(); }).given(subscriber) .request(anyLong()); return subscriber; } }; dfa.setPropertyValue("env", env); } } } @AfterMethod public void teardown() { if (this.applicationContext != null) { this.applicationContext.close(); } } @Override public Publisher<Message<?>> createPublisher(long elements) { MockEnvironment environment = new MockEnvironment() .withProperty(ELEMENTS, "" + elements) .withProperty(TEST_METHOD, this.testName) .withProperty(REQUEST_COMPLETE, "" + this.completionSignalRequired); return doCreatePublisher(environment); } @Override public Publisher<Message<?>> createFailedPublisher() { MockEnvironment environment = new MockEnvironment() .withProperty(FAILED_PUBLISHER, "true"); return doCreatePublisher(environment); } @SuppressWarnings("unchecked") private Publisher<Message<?>> doCreatePublisher(ConfigurableEnvironment environment) { AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(); applicationContext.register(getConfigClass()); applicationContext.setEnvironment(environment); applicationContext.refresh(); this.applicationContext = applicationContext; return this.applicationContext.getBean(Publisher.class); } protected abstract Class<?> getConfigClass(); public static class PublisherConfiguration { protected final AtomicBoolean invoked = new AtomicBoolean(); @Value("${" + ELEMENTS + ":" + Long.MAX_VALUE + "}") protected long elements; @Value("${" + FAILED_PUBLISHER + ":false}") protected boolean failedPublisher; @Value("${" + REQUEST_COMPLETE + ":false}") protected boolean completionSignalRequired; @Value("${" + TEST_METHOD + ":}") protected String testMethod; @Autowired protected ConfigurableBeanFactory beanFactory; @Bean public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } } }