package com.lambdaworks.redis.pubsub;
import static com.google.code.tempusfugit.temporal.Duration.millis;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Fail.fail;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.lambdaworks.Delay;
import com.lambdaworks.Wait;
import com.lambdaworks.redis.AbstractRedisClientTest;
import com.lambdaworks.redis.FastShutdown;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.api.rx.Success;
import com.lambdaworks.redis.internal.LettuceFactories;
import com.lambdaworks.redis.internal.LettuceLists;
import com.lambdaworks.redis.pubsub.api.rx.ChannelMessage;
import com.lambdaworks.redis.pubsub.api.rx.PatternMessage;
import com.lambdaworks.redis.pubsub.api.rx.RedisPubSubReactiveCommands;
import com.lambdaworks.redis.pubsub.api.sync.RedisPubSubCommands;
import rx.Observable;
import rx.Subscription;
import rx.observables.BlockingObservable;
/**
* @author Mark Paluch
*/
public class PubSubRxTest extends AbstractRedisClientTest implements RedisPubSubListener<String, String> {
private RedisPubSubReactiveCommands<String, String> pubsub;
private RedisPubSubReactiveCommands<String, String> pubsub2;
private BlockingQueue<String> channels;
private BlockingQueue<String> patterns;
private BlockingQueue<String> messages;
private BlockingQueue<Long> counts;
private String channel = "channel0";
private String pattern = "channel*";
private String message = "msg!";
@Before
public void openPubSubConnection() throws Exception {
pubsub = client.connectPubSub().reactive();
pubsub2 = client.connectPubSub().reactive();
pubsub.addListener(this);
channels = LettuceFactories.newBlockingQueue();
patterns = LettuceFactories.newBlockingQueue();
messages = LettuceFactories.newBlockingQueue();
counts = LettuceFactories.newBlockingQueue();
}
@After
public void closePubSubConnection() throws Exception {
pubsub.close();
pubsub2.close();
}
@Test
public void observeChannels() throws Exception {
block(pubsub.subscribe(channel));
BlockingQueue<ChannelMessage<String, String>> channelMessages = LettuceFactories.newBlockingQueue();
Subscription subscription = pubsub.observeChannels().doOnNext(channelMessages::add).subscribe();
redis.publish(channel, message);
redis.publish(channel, message);
redis.publish(channel, message);
Wait.untilEquals(3, () -> channelMessages.size()).waitOrTimeout();
assertThat(channelMessages).hasSize(3);
subscription.unsubscribe();
redis.publish(channel, message);
Delay.delay(millis(500));
assertThat(channelMessages).hasSize(3);
ChannelMessage<String, String> channelMessage = channelMessages.take();
assertThat(channelMessage.getChannel()).isEqualTo(channel);
assertThat(channelMessage.getMessage()).isEqualTo(message);
}
@Test
public void observeChannelsUnsubscribe() throws Exception {
block(pubsub.subscribe(channel));
BlockingQueue<ChannelMessage<String, String>> channelMessages = LettuceFactories.newBlockingQueue();
pubsub.observeChannels().doOnNext(channelMessages::add).subscribe().unsubscribe();
block(redis.getStatefulConnection().reactive().publish(channel, message));
block(redis.getStatefulConnection().reactive().publish(channel, message));
Delay.delay(millis(500));
assertThat(channelMessages).isEmpty();
}
@Test
public void observePatterns() throws Exception {
block(pubsub.psubscribe(pattern));
BlockingQueue<PatternMessage<String, String>> patternMessages = LettuceFactories.newBlockingQueue();
pubsub.observePatterns().doOnNext(patternMessages::add).subscribe();
redis.publish(channel, message);
redis.publish(channel, message);
redis.publish(channel, message);
Wait.untilTrue(() -> patternMessages.size() == 3).waitOrTimeout();
assertThat(patternMessages).hasSize(3);
PatternMessage<String, String> patternMessage = patternMessages.take();
assertThat(patternMessage.getChannel()).isEqualTo(channel);
assertThat(patternMessage.getMessage()).isEqualTo(message);
assertThat(patternMessage.getPattern()).isEqualTo(pattern);
}
@Test
public void observePatternsWithUnsubscribe() throws Exception {
block(pubsub.psubscribe(pattern));
BlockingQueue<PatternMessage<String, String>> patternMessages = LettuceFactories.newBlockingQueue();
Subscription subscription = pubsub.observePatterns().doOnNext(patternMessages::add).subscribe();
redis.publish(channel, message);
redis.publish(channel, message);
redis.publish(channel, message);
Wait.untilTrue(() -> patternMessages.size() == 3).waitOrTimeout();
assertThat(patternMessages).hasSize(3);
subscription.unsubscribe();
redis.publish(channel, message);
redis.publish(channel, message);
redis.publish(channel, message);
Delay.delay(millis(500));
assertThat(patternMessages).hasSize(3);
}
@Test(timeout = 2000)
public void message() throws Exception {
block(pubsub.subscribe(channel));
assertThat(channels.take()).isEqualTo(channel);
redis.publish(channel, message);
assertThat(channels.take()).isEqualTo(channel);
assertThat(messages.take()).isEqualTo(message);
}
@Test(timeout = 2000)
public void pmessage() throws Exception {
block(pubsub.psubscribe(pattern));
assertThat(patterns.take()).isEqualTo(pattern);
redis.publish(channel, message);
assertThat(patterns.take()).isEqualTo(pattern);
assertThat(channels.take()).isEqualTo(channel);
assertThat(messages.take()).isEqualTo(message);
redis.publish("channel2", "msg 2!");
assertThat(patterns.take()).isEqualTo(pattern);
assertThat(channels.take()).isEqualTo("channel2");
assertThat(messages.take()).isEqualTo("msg 2!");
}
@Test(timeout = 2000)
public void psubscribe() throws Exception {
Success sucess = first(pubsub.psubscribe(pattern));
assertThat(sucess).isEqualTo(Success.Success);
assertThat(patterns.take()).isEqualTo(pattern);
assertThat((long) counts.take()).isEqualTo(1);
}
@Test(expected = IllegalArgumentException.class)
public void pubsubEmptyChannels() throws Exception {
pubsub.subscribe();
fail("Missing IllegalArgumentException: channels must not be empty");
}
@Test
public void pubsubChannels() throws Exception {
block(pubsub.subscribe(channel));
List<String> result = first(pubsub2.pubsubChannels().toList());
assertThat(result).contains(channel);
}
@Test
public void pubsubMultipleChannels() throws Exception {
block(pubsub.subscribe(channel, "channel1", "channel3"));
List<String> result = first(pubsub2.pubsubChannels().toList());
assertThat(result).contains(channel, "channel1", "channel3");
}
@Test
public void pubsubChannelsWithArg() throws Exception {
pubsub.subscribe(channel).subscribe();
Wait.untilTrue(() -> first(pubsub2.pubsubChannels(pattern).filter(s -> channel.equals(s))) != null).waitOrTimeout();
String result = first(pubsub2.pubsubChannels(pattern).filter(s -> channel.equals(s)));
assertThat(result).isEqualToIgnoringCase(channel);
}
@Test
public void pubsubNumsub() throws Exception {
pubsub.subscribe(channel).subscribe();
Wait.untilEquals(1, () -> first(pubsub2.pubsubNumsub(channel).toList()).size()).waitOrTimeout();
Map<String, Long> result = first(pubsub2.pubsubNumsub(channel));
assertThat(result).hasSize(1);
assertThat(result.get(channel)).isGreaterThan(0);
}
@Test
public void pubsubNumpat() throws Exception {
Wait.untilEquals(0L, () -> first(pubsub2.pubsubNumpat())).waitOrTimeout();
pubsub.psubscribe(pattern).subscribe();
Wait.untilEquals(1L, () -> redis.pubsubNumpat()).waitOrTimeout();
Long result = first(pubsub2.pubsubNumpat());
assertThat(result.longValue()).isGreaterThan(0);
}
@Test(timeout = 2000)
public void punsubscribe() throws Exception {
pubsub.punsubscribe(pattern).subscribe();
assertThat(patterns.take()).isEqualTo(pattern);
assertThat((long) counts.take()).isEqualTo(0);
}
@Test(timeout = 2000)
public void subscribe() throws Exception {
pubsub.subscribe(channel).subscribe();
assertThat(channels.take()).isEqualTo(channel);
assertThat((long) counts.take()).isGreaterThan(0);
}
@Test(timeout = 2000)
public void unsubscribe() throws Exception {
pubsub.unsubscribe(channel).subscribe();
assertThat(channels.take()).isEqualTo(channel);
assertThat((long) counts.take()).isEqualTo(0);
block(pubsub.unsubscribe());
assertThat(channels).isEmpty();
assertThat(patterns).isEmpty();
}
@Test
public void pubsubCloseOnClientShutdown() throws Exception {
RedisClient redisClient = RedisClient.create(RedisURI.Builder.redis(host, port).build());
RedisPubSubCommands<String, String> connection = redisClient.connectPubSub().sync();
FastShutdown.shutdown(redisClient);
assertThat(connection.isOpen()).isFalse();
}
@Test(timeout = 2000)
public void utf8Channel() throws Exception {
String channel = "channelλ";
String message = "αβγ";
block(pubsub.subscribe(channel));
assertThat(channels.take()).isEqualTo(channel);
pubsub2.publish(channel, message).subscribe();
assertThat(channels.take()).isEqualTo(channel);
assertThat(messages.take()).isEqualTo(message);
}
@Test(timeout = 2000)
public void resubscribeChannelsOnReconnect() throws Exception {
pubsub.subscribe(channel).subscribe();
assertThat(channels.take()).isEqualTo(channel);
assertThat((long) counts.take()).isEqualTo(1);
block(pubsub.quit());
assertThat(channels.take()).isEqualTo(channel);
assertThat((long) counts.take()).isEqualTo(1);
Wait.untilTrue(pubsub::isOpen).waitOrTimeout();
redis.publish(channel, message);
assertThat(channels.take()).isEqualTo(channel);
assertThat(messages.take()).isEqualTo(message);
}
@Test(timeout = 2000)
public void resubscribePatternsOnReconnect() throws Exception {
pubsub.psubscribe(pattern).subscribe();
assertThat(patterns.take()).isEqualTo(pattern);
assertThat((long) counts.take()).isEqualTo(1);
block(pubsub.quit());
assertThat(patterns.take()).isEqualTo(pattern);
assertThat((long) counts.take()).isEqualTo(1);
Wait.untilTrue(pubsub::isOpen).waitOrTimeout();
pubsub2.publish(channel, message).subscribe();
assertThat(channels.take()).isEqualTo(channel);
assertThat(messages.take()).isEqualTo(message);
}
@Test(timeout = 2000)
public void adapter() throws Exception {
final BlockingQueue<Long> localCounts = LettuceFactories.newBlockingQueue();
RedisPubSubAdapter<String, String> adapter = new RedisPubSubAdapter<String, String>() {
@Override
public void subscribed(String channel, long count) {
super.subscribed(channel, count);
localCounts.add(count);
}
@Override
public void unsubscribed(String channel, long count) {
super.unsubscribed(channel, count);
localCounts.add(count);
}
};
pubsub.addListener(adapter);
pubsub.subscribe(channel).subscribe();
pubsub.psubscribe(pattern).subscribe();
assertThat((long) localCounts.take()).isEqualTo(1L);
pubsub2.publish(channel, message).subscribe();
pubsub.punsubscribe(pattern).subscribe();
pubsub.unsubscribe(channel).subscribe();
assertThat((long) localCounts.take()).isEqualTo(0L);
}
@Test(timeout = 2000)
public void removeListener() throws Exception {
pubsub.subscribe(channel).subscribe();
assertThat(channels.take()).isEqualTo(channel);
pubsub2.publish(channel, message).subscribe();
assertThat(channels.take()).isEqualTo(channel);
assertThat(messages.take()).isEqualTo(message);
pubsub.removeListener(this);
pubsub2.publish(channel, message).subscribe();
assertThat(channels.poll(10, TimeUnit.MILLISECONDS)).isNull();
assertThat(messages.poll(10, TimeUnit.MILLISECONDS)).isNull();
}
// RedisPubSubListener implementation
@Override
public void message(String channel, String message) {
channels.add(channel);
messages.add(message);
}
@Override
public void message(String pattern, String channel, String message) {
patterns.add(pattern);
channels.add(channel);
messages.add(message);
}
@Override
public void subscribed(String channel, long count) {
channels.add(channel);
counts.add(count);
}
@Override
public void psubscribed(String pattern, long count) {
patterns.add(pattern);
counts.add(count);
}
@Override
public void unsubscribed(String channel, long count) {
channels.add(channel);
counts.add(count);
}
@Override
public void punsubscribed(String pattern, long count) {
patterns.add(pattern);
counts.add(count);
}
protected <T> void block(Observable<T> observable) {
observable.toBlocking().last();
}
protected <T> T first(Observable<T> observable) {
BlockingObservable<T> blocking = observable.toBlocking();
Iterator<T> iterator = blocking.getIterator();
if (iterator.hasNext()) {
return iterator.next();
}
return null;
}
protected <T> List<T> all(Observable<T> observable) {
BlockingObservable<T> blocking = observable.toBlocking();
Iterator<T> iterator = blocking.getIterator();
return LettuceLists.newList(iterator);
}
}