/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 gobblin.writer.test;
import java.io.Closeable;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nonnull;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import gobblin.writer.test.TestingEventBuses.Event;
import lombok.AllArgsConstructor;
/**
* A wrapper around an EventBus created with {@link TestingEventBuses} that implements various
* asserts on the incoming messages.
*
* <p><b>Important:</b> This class must be instantiated before any messages are sent on the bus or
* it won't detect them.
*/
public class TestingEventBusAsserter implements Closeable {
private final BlockingDeque<TestingEventBuses.Event> _events = new LinkedBlockingDeque<>();
private final EventBus _eventBus;
private long _defaultTimeoutValue = 1;
private TimeUnit _defaultTimeoutUnit = TimeUnit.SECONDS;
@AllArgsConstructor
public static class StaticMessage implements Function<TestingEventBuses.Event, String> {
private final String message;
@Override public String apply(Event input) {
return this.message;
}
}
public TestingEventBusAsserter(String eventBusId) {
_eventBus = TestingEventBuses.getEventBus(eventBusId);
_eventBus.register(this);
}
@Subscribe public void processEvent(TestingEventBuses.Event e) {
_events.offer(e);
}
@Override public void close() throws IOException {
_eventBus.unregister(this);
}
public BlockingDeque<TestingEventBuses.Event> getEvents() {
return _events;
}
public void clear() {
_events.clear();
}
/** Sets timeout for all subsequent blocking asserts. */
public TestingEventBusAsserter withTimeout(long timeout, TimeUnit unit) {
_defaultTimeoutValue = timeout;
_defaultTimeoutUnit = unit;
return this;
}
/** Gets the next event from the queue and validates that it satisfies a given predicate. Blocking
* assert. The event is removed from the internal queue regardless if the predicate has been
* satisfied.
* @param predicate the predicate to apply on the next event
* @param assert error message generator
* @return the event if the predicate is satisfied
* @throws AssertionError if the predicate is not satisfied
*/
public TestingEventBuses.Event assertNext(final Predicate<TestingEventBuses.Event> predicate,
Function<TestingEventBuses.Event, String> messageGen
) throws InterruptedException, TimeoutException {
TestingEventBuses.Event nextEvent = _events.pollFirst(_defaultTimeoutValue, _defaultTimeoutUnit);
if (null == nextEvent) {
throw new TimeoutException();
}
if (!predicate.apply(nextEvent)) {
throw new AssertionError(messageGen.apply(nextEvent));
}
return nextEvent;
}
/**
* Variation on {@link #assertNext(Predicate, Function)} with a constant message.
*/
public TestingEventBuses.Event assertNext(final Predicate<TestingEventBuses.Event> predicate,
final String message) throws InterruptedException, TimeoutException {
return assertNext(predicate, new StaticMessage(message));
}
/** Similar to {@link #assertNext(Predicate, Function)} but predicate is on the value directly. */
public <T> TestingEventBuses.Event assertNextValue(final Predicate<T> predicate ,
Function<TestingEventBuses.Event, String> messageGen)
throws InterruptedException, TimeoutException {
return assertNext(new Predicate<TestingEventBuses.Event>() {
@Override public boolean apply(@Nonnull Event input) {
return predicate.apply(input.<T>getTypedValue());
}
}, messageGen);
}
/** Similar to {@link #assertNext(Predicate, String)} but predicate is on the value directly. */
public <T> TestingEventBuses.Event assertNextValue(final Predicate<T> predicate, String message)
throws InterruptedException, TimeoutException {
return assertNext(new Predicate<TestingEventBuses.Event>() {
@Override public boolean apply(@Nonnull Event input) {
return predicate.apply(input.<T>getTypedValue());
}
}, message);
}
public <T> TestingEventBuses.Event assertNextValueEq(final T expected)
throws InterruptedException, TimeoutException {
return assertNextValue(Predicates.equalTo(expected),
new Function<TestingEventBuses.Event, String>() {
@Override public String apply(@Nonnull Event input) {
return "Event value mismatch: " + input.getValue() + " != " + expected;
}
});
}
/**
* Verify that all next several values are a permutation of the expected collection of event
* values. This method allows for testing that certain values are produced in some random order.
* Blocking assert. */
public <T> void assertNextValuesEq(final Collection<T> expected)
throws InterruptedException, TimeoutException {
final Set<T> remainingExpectedValues = new HashSet<>(expected);
final Predicate<T> checkInRemainingAndRemove = new Predicate<T>() {
@Override public boolean apply(@Nonnull T input) {
if (! remainingExpectedValues.contains(input)) {
return false;
}
remainingExpectedValues.remove(input);
return true;
}
};
while (remainingExpectedValues.size() > 0) {
assertNextValue(checkInRemainingAndRemove,
new Function<TestingEventBuses.Event, String>() {
@Override public String apply(@Nonnull Event input) {
return "Event value " + input.getValue() + " not in set " + remainingExpectedValues;
}
});
}
}
}