/*
* JBoss, Home of Professional Open Source
* Copyright 2017, Red Hat, Inc., and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.jboss.weld.environment.se.test.event.options.timeout;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.enterprise.event.NotificationOptions;
import org.jboss.arquillian.container.se.api.ClassPath;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.BeanArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;
import org.jboss.weld.events.WeldNotificationOptions;
import org.jboss.weld.test.util.Utils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
*
* @author <a href="mailto:manovotn@redhat.com">Matej Novotny</a>
*/
@RunWith(Arquillian.class)
public class NotificationTimeoutTest {
// has to be CLEARED in the beginning of each test method where you use it
public static final List<String> SUCCESSION_OF_EVENTS = new CopyOnWriteArrayList<String>();
@Deployment
public static Archive<?> createTestArchive() {
return ClassPath.builder().add(ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(NotificationTimeoutTest.class))
.addPackage(NotificationTimeoutTest.class.getPackage()).addClass(IncompleteCustomExecutorServices.class)).build();
}
@Test
public void testMultipleObserverNotificationTimeout() throws InterruptedException {
SUCCESSION_OF_EVENTS.clear();
try (WeldContainer container = new Weld().initialize()) {
BlockingQueue<Throwable> synchronizer = new LinkedBlockingQueue<>();
CountDownLatch countdown = new CountDownLatch(2);
container.event().select(CountDownLatch.class)
.fireAsync(countdown, NotificationOptions.of(WeldNotificationOptions.TIMEOUT, 2000l))
.exceptionally((Throwable t) -> {
// handle TimeoutException we got here
SUCCESSION_OF_EVENTS.add("Timeout");
synchronizer.add(t);
return null;
});
Throwable fromSynchronizer = synchronizer.poll(5, TimeUnit.SECONDS);
Assert.assertNotNull(fromSynchronizer);
Assert.assertTrue(fromSynchronizer.getCause().toString().contains(TimeoutException.class.getSimpleName()));
// wait for the observers to finish their jobs
countdown.await();
// assert contents AND order of events
Assert.assertTrue(SUCCESSION_OF_EVENTS.equals(getExpectedListOfEvents()));
}
}
@Test
public void testMultipleObserverParallelNotificationTimeout() throws InterruptedException {
SUCCESSION_OF_EVENTS.clear();
try (WeldContainer container = new Weld().initialize()) {
BlockingQueue<Throwable> synchronizer = new LinkedBlockingQueue<>();
CountDownLatch countdown = new CountDownLatch(2);
NotificationOptions options = NotificationOptions.builder().set(WeldNotificationOptions.TIMEOUT, "2000")
.set(WeldNotificationOptions.MODE, WeldNotificationOptions.NotificationMode.PARALLEL).build();
container.event().select(CountDownLatch.class)
.fireAsync(countdown, options)
.exceptionally((Throwable t) -> {
// handle TimeoutException we got here
SUCCESSION_OF_EVENTS.add("Timeout");
synchronizer.add(t);
return null;
});
Throwable fromSynchronizer = synchronizer.poll(5, TimeUnit.SECONDS);
Assert.assertNotNull(fromSynchronizer);
Assert.assertTrue(fromSynchronizer.getCause().toString().contains(TimeoutException.class.getSimpleName()));
// wait for the observers to finish their jobs
countdown.await();
// with parallel execution, we can only assert that all observers were notified (the order might differ)
Assert.assertTrue(SUCCESSION_OF_EVENTS.size() == 3);
Assert.assertTrue(SUCCESSION_OF_EVENTS.containsAll(getExpectedListOfEvents()));
}
}
@Test
public void testBadTimeoutInputThrowsException() throws InterruptedException {
SUCCESSION_OF_EVENTS.clear();
CountDownLatch latch = new CountDownLatch(1);
try (WeldContainer container = new Weld().initialize()) {
container.event().select(CountDownLatch.class)
.fireAsync(latch, NotificationOptions.of(WeldNotificationOptions.TIMEOUT, 1.2345));
Assert.fail("Bad input valut should throw IllegalArgumentException.");
} catch (IllegalArgumentException iae) {
// expected, should throw IAE
// assert that no observers were notified
Assert.assertFalse(latch.await(500, TimeUnit.MILLISECONDS));
Assert.assertTrue(SUCCESSION_OF_EVENTS.isEmpty());
}
}
@Test
public void testTimeoutNotReached() throws InterruptedException {
// this deployment omits the SLOW observer so as to AVOID timeout - e.g. this deployment has only one observer
final String success = "Success";
Weld weld = new Weld().disableDiscovery().addBeanClasses(HardworkingObserver.class);
try (WeldContainer container = weld.initialize()) {
BlockingQueue<String> synchronizer = new LinkedBlockingQueue<>();
CountDownLatch countdown = new CountDownLatch(1);
container.event().select(CountDownLatch.class)
.fireAsync(countdown, NotificationOptions.of(WeldNotificationOptions.TIMEOUT, new Long(2000l)))
.thenRun(() -> synchronizer.add(success))
.exceptionally((Throwable t) -> { // only executes if the notification ended with exception
synchronizer.add("Exception");
return null;
});
// wait for the observer to finish the job
countdown.await();
// assert that BlockingQueue has exactly one item indicating success
String fromSynchronizer = synchronizer.poll(3, TimeUnit.SECONDS);
Assert.assertNotNull(fromSynchronizer);
Assert.assertTrue(synchronizer.isEmpty());
Assert.assertEquals(success, fromSynchronizer);
}
}
@Test
public void testSingleObserverNotificationTimeout() throws InterruptedException {
SUCCESSION_OF_EVENTS.clear();
// this deployment omits the FAST observer so as to FORCE timeout - e.g. this deployment has only one observer
Weld weld = new Weld().disableDiscovery().addBeanClasses(LazyObserver.class);
try (WeldContainer container = weld.initialize()) {
BlockingQueue<Throwable> synchronizer = new LinkedBlockingQueue<>();
CountDownLatch countdown = new CountDownLatch(1);
container.event().select(CountDownLatch.class)
.fireAsync(countdown, NotificationOptions.of(WeldNotificationOptions.TIMEOUT, new Long(1000l)))
.exceptionally((Throwable t) -> {
SUCCESSION_OF_EVENTS.add("Timeout");
synchronizer.add(t);
return null;
});
Throwable fromSynchronizer = synchronizer.poll(5, TimeUnit.SECONDS);
Assert.assertNotNull(fromSynchronizer);
Assert.assertTrue(fromSynchronizer.getCause().toString().contains(TimeoutException.class.getSimpleName()));
// wait for the observer to finish the jobs
countdown.await();
// assert that two events happened and timeout came first
Assert.assertTrue(SUCCESSION_OF_EVENTS.size() == 2);
Assert.assertTrue(SUCCESSION_OF_EVENTS.get(0).equals("Timeout"));
}
}
@Test(expected = UnsupportedOperationException.class)
public void testCustomExecutorServiceNotImplementingTimerExecutor() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
Weld weld = new Weld().disableDiscovery().addBeanClasses(HardworkingObserver.class).addServices(new IncompleteCustomExecutorServices());
try (WeldContainer container = weld.initialize()) {
try {
container.event().select(CountDownLatch.class).fireAsync(latch, NotificationOptions.of(WeldNotificationOptions.TIMEOUT, "2000"));
} catch (IllegalArgumentException e) {
// expected, should throw IAE
Assert.assertFalse(latch.await(500, TimeUnit.MILLISECONDS));
}
}
}
private List<String> getExpectedListOfEvents() {
List<String> expected = new ArrayList<String>();
expected.add("Work");
expected.add("Timeout");
expected.add("Coffee");
return expected;
}
}