/** * Copyright 2015 StreamSets Inc. * * Licensed under 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 com.streamsets.pipeline.cluster; import com.google.common.base.Throwables; import com.streamsets.pipeline.impl.OffsetAndResult; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class TestProducerConsumer { private ExecutorService executorService; private ControlChannel controlChannel; private DataChannel dataChannel; private Producer producer; private Consumer consumer; @Before public void setup() { executorService = Executors.newCachedThreadPool(); controlChannel = new ControlChannel(); dataChannel = new DataChannel(); producer = new Producer(controlChannel, dataChannel); consumer = new Consumer(controlChannel, dataChannel); } @After public void teardown() { executorService.shutdownNow(); } @Test(timeout = 5000) public void testProducerComplete() throws Exception { Future<?> putFuture = put(1); producer.complete(); Future<List<Map.Entry>> takeFuture = take(true); putFuture.get(); Assert.assertEquals(createBatch(1), takeFuture.get()); Assert.assertNull(consumer.take()); // should not block and should return null } @Test(timeout = 5000, expected = TimeoutException.class) public void testNoCommitTimesOutProducer() throws Exception { Future<?> putFuture = put(1); take(false); putFuture.get(1, TimeUnit.SECONDS); } @Test(timeout = 5000, expected = IllegalStateException.class) public void testNoCommitCausesIllegalStateException() throws Exception { put(1); take(false).get(); Throwables.propagate(getError(take(false))); } @Test(timeout = 5000) public void testConsumerErrorCausesPutToReturn() throws Exception { RuntimeException consumerError = new RuntimeException(); consumer.error(consumerError); Assert.assertSame(consumerError, getError(put(1))); } @Test(timeout = 5000) public void testConsumerErrorPassedToProducer1() throws Exception { RuntimeException consumerError = new RuntimeException(); consumer.error(consumerError); List<ControlChannel.Message> producerMessages = controlChannel.getProducerMessages(); Assert.assertEquals(1, producerMessages.size()); ControlChannel.Message producerMessage = producerMessages.get(0); Assert.assertEquals(ControlChannel.MessageType.CONSUMER_ERROR, producerMessage.getType()); Assert.assertSame(consumerError, producerMessage.getPayload()); } @Test(timeout = 5000) public void testConsumerErrorPassedToProducer2() throws Exception { RuntimeException consumerError = new RuntimeException(); consumer.error(consumerError); Assert.assertSame(consumerError, getError(put(1))); } @Test(timeout = 5000) public void testProducerErrorPassedToConsumer() throws Exception { RuntimeException producerError = new RuntimeException(); controlChannel.producerError(producerError); List<ControlChannel.Message> consumerMessages = controlChannel.getConsumerMessages(); Assert.assertEquals(1, consumerMessages.size()); ControlChannel.Message consumerMessage = consumerMessages.get(0); Assert.assertEquals(ControlChannel.MessageType.PRODUCER_ERROR, consumerMessage.getType()); Assert.assertSame(producerError, consumerMessage.getPayload()); controlChannel.producerError(producerError); Assert.assertSame(producerError, getError(take(true))); Assert.assertSame(producerError, getError(take(true))); } private Throwable getError(Future future) throws InterruptedException { try { future.get(); throw new AssertionError("Future failed to throw expected exception"); } catch (ExecutionException ex) { Throwable result = ex; while (result.getCause() != null) { result = result.getCause(); } return result; } } private Future<List<Map.Entry>> take(final boolean commit) throws Exception { return executorService.submit(new Callable<List<Map.Entry>>() { @Override public List<Map.Entry> call() throws Exception { OffsetAndResult<Map.Entry> result = consumer.take(); if (result == null) { throw new NullPointerException("result"); } if (commit) { consumer.commit(String.valueOf(result.getOffset())); } return result.getResult(); } }); } private Future<?> put(final int size) throws Exception { return executorService.submit(new Callable<Object>() { @Override public Object call() throws Exception { producer.put(new OffsetAndResult<>("123", createBatch(size))); producer.waitForCommit(); return null; } }); } private List<Map.Entry> createBatch(int size) { List<Map.Entry> batch = new ArrayList<>(size); for (int i = 0; i < size; i++) { batch.add(new Record(i)); } return batch; } private static class Record implements Map.Entry { private int i; Record(int i) { this.i = i; } @Override public Object getKey() { return "key-" + i; } @Override public Object getValue() { return "val-" + i; } @Override public Object setValue(Object value) { return null; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Record record = (Record) o; return i == record.i; } @Override public int hashCode() { return i; } } }