/*
* #!
* %
* Copyright (C) 2014 - 2016 Humboldt-Universität zu Berlin
* %
* 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 de.hub.cs.dbis.aeolus.utils;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.modules.junit4.PowerMockRunner;
import backtype.storm.task.GeneralTopologyContext;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.TupleImpl;
import backtype.storm.tuple.Values;
import de.hub.cs.dbis.aeolus.testUtils.TimestampComperator;
/**
* @author mjsax
*/
@RunWith(PowerMockRunner.class)
public class StreamMergerTest {
private long seed;
private Random r;
@Before
public void prepare() {
this.seed = System.currentTimeMillis();
this.r = new Random(this.seed);
System.out.println("Test seed: " + this.seed);
}
@Test
public void testEmpty() {
@SuppressWarnings({"unchecked", "rawtypes"})
StreamMerger merger = new StreamMerger(Arrays.asList(new Integer(0)), 0);
Assert.assertNull(merger.getNextTuple());
}
@Test
public void testTuple() {
StreamMerger<Tuple> merger = new StreamMerger<Tuple>(Arrays.asList(new Integer(0)), "ts");
GeneralTopologyContext contextMock = mock(GeneralTopologyContext.class);
when(contextMock.getComponentId(0)).thenReturn("bolt1");
when(contextMock.getComponentId(1)).thenReturn("bolt2");
when(contextMock.getComponentId(2)).thenReturn("bolt3");
when(contextMock.getComponentOutputFields(eq("bolt1"), anyString())).thenReturn(new Fields("ts"));
when(contextMock.getComponentOutputFields(eq("bolt2"), anyString())).thenReturn(new Fields("x", "ts", "y"));
when(contextMock.getComponentOutputFields(eq("bolt3"), anyString())).thenReturn(new Fields("a", "b", "ts"));
Tuple t = new TupleImpl(contextMock, new Values(new Long(0)), 0, "");
merger.addTuple(new Integer(0), t);
Assert.assertSame(t, merger.getNextTuple());
Assert.assertNull(merger.getNextTuple());
Tuple t2 = new TupleImpl(contextMock, new Values(mock(Object.class), new Long(0), mock(Object.class)), 1, "");
merger.addTuple(new Integer(0), t2);
t = new TupleImpl(contextMock, new Values(mock(Object.class), mock(Object.class), new Long(0)), 2, "");
merger.addTuple(new Integer(0), t);
Assert.assertSame(t2, merger.getNextTuple());
Assert.assertSame(t, merger.getNextTuple());
Assert.assertNull(merger.getNextTuple());
}
@Test
public void testValues() {
StreamMerger<Values> merger = new StreamMerger<Values>(Arrays.asList(new Integer(0)), 0);
Values t = new Values(new Long(0));
merger.addTuple(new Integer(0), t);
Assert.assertSame(t, merger.getNextTuple());
Assert.assertNull(merger.getNextTuple());
Values t2 = new Values(new Long(0), mock(Object.class), mock(Object.class));
merger.addTuple(new Integer(0), t2);
t = new Values(new Long(0), mock(Object.class), mock(Object.class));
merger.addTuple(new Integer(0), t);
Assert.assertSame(t2, merger.getNextTuple());
Assert.assertSame(t, merger.getNextTuple());
Assert.assertNull(merger.getNextTuple());
}
@Test
public void testMultiplePartitions() {
StreamMerger<Values> merger = new StreamMerger<Values>(Arrays.asList(new Integer(0), new Integer(1)), 0);
Values t1 = new Values(new Long(0));
merger.addTuple(new Integer(0), t1);
Assert.assertNull(merger.getNextTuple());
Values t2 = new Values(new Long(0), mock(Object.class), mock(Object.class));
merger.addTuple(new Integer(1), t2);
Values res = merger.getNextTuple();
Assert.assertTrue(res.equals(t1) || res.equals(t2));
if(res.equals(t1)) {
res = merger.getNextTuple();
Assert.assertSame(t2, res);
} else {
res = merger.getNextTuple();
Assert.assertSame(t1, res);
}
t1 = new Values(new Long(0));
merger.addTuple(new Integer(this.r.nextInt(1)), t1);
Assert.assertSame(t1, merger.getNextTuple());
t1 = new Values(new Long(1));
merger.addTuple(new Integer(1), t1);
Assert.assertNull(merger.getNextTuple());
t2 = new Values(new Long(2));
merger.addTuple(new Integer(0), t2);
Assert.assertSame(t1, merger.getNextTuple());
Assert.assertNull(merger.getNextTuple());
t1 = new Values(new Long(2));
merger.addTuple(new Integer(0), t1);
Assert.assertNull(merger.getNextTuple());
Values t3 = new Values(new Long(2));
merger.addTuple(new Integer(1), t3);
Values t4 = new Values(new Long(3));
merger.addTuple(new Integer(0), t4);
Values t5 = new Values(new Long(3));
merger.addTuple(new Integer(1), t5);
res = merger.getNextTuple();
Assert.assertTrue(res.equals(t2) || res.equals(t3));
if(res.equals(t2)) {
res = merger.getNextTuple();
Assert.assertTrue(res.equals(t1) || res.equals(t3));
if(res.equals(t1)) {
Assert.assertSame(t3, merger.getNextTuple());
} else {
Assert.assertSame(t1, merger.getNextTuple());
}
} else {
Assert.assertSame(t2, merger.getNextTuple());
Assert.assertSame(t1, merger.getNextTuple());
}
res = merger.getNextTuple();
Assert.assertTrue(res.equals(t4) || res.equals(t5));
if(res.equals(t4)) {
Assert.assertSame(t5, merger.getNextTuple());
} else {
Assert.assertSame(t4, merger.getNextTuple());
}
}
@Test
public void testRandomValues() {
final int numberOfPartitions = 2 + this.r.nextInt(8);
double duplicatesFraction = this.r.nextDouble();
if(this.r.nextBoolean()) {
duplicatesFraction = 0;
}
final int numberOfTuples = numberOfPartitions * 10
+ this.r.nextInt(numberOfPartitions * (1 + this.r.nextInt(10)));
final boolean doFlush = this.r.nextBoolean();
ArrayList<Integer> partitionIds = new ArrayList<Integer>(numberOfPartitions);
int[] currentTs = new int[numberOfPartitions];
for(int i = 0; i < numberOfPartitions; ++i) {
partitionIds.add(new Integer(i));
currentTs[i] = 1;
}
StreamMerger<Values> merger = new StreamMerger<Values>(partitionIds, 0);
LinkedList<Values> expectedResult = new LinkedList<Values>();
LinkedList<Values> result = new LinkedList<Values>();
int counter = 0;
while(counter < numberOfTuples) {
int inserts = this.r.nextInt(2 * numberOfPartitions);
for(int i = 0; i < inserts; ++i) {
int partitionId = partitionIds.get(this.r.nextInt(numberOfPartitions)).intValue();
Values t = new Values();
Long ts = new Long(currentTs[partitionId] - 1);
t.add(ts);
for(int j = 0; j < 9; ++j) {
t.add(new Character((char)(32 + this.r.nextInt(95))));
}
expectedResult.add(t);
merger.addTuple(new Integer(partitionId), t);
if(++counter == numberOfTuples) {
break;
}
if(1 - this.r.nextDouble() > duplicatesFraction) {
++currentTs[partitionId];
}
}
Values res;
while((res = merger.getNextTuple()) != null) {
result.add(res);
}
}
Collections.sort(expectedResult, new TimestampComperator());
if(doFlush) {
while(merger.getNumberOpenPartitions() > 0) {
boolean closeSuccess = false;
for(Integer p : partitionIds) {
try {
closeSuccess |= merger.closePartition(p);
} catch(NullPointerException e) {
// ignore; happens for alread closed partitions
}
}
assert (closeSuccess);
Values res;
while((res = merger.getNextTuple()) != null) {
result.add(res);
}
}
} else {
List<Object> lastRemoved = null;
while(expectedResult.size() > result.size()) {
lastRemoved = expectedResult.removeLast();
}
if(lastRemoved != null) {
while(expectedResult.size() > 0
&& ((Long)lastRemoved.get(0)).longValue() == ((Long)expectedResult.getLast().get(0)).longValue()) {
expectedResult.removeLast();
}
}
}
while(expectedResult.size() > 0) {
Set<List<Object>> expectedSubset = new HashSet<List<Object>>();
Set<List<Object>> resultSubset = new HashSet<List<Object>>();
List<Object> first;
do {
first = expectedResult.removeFirst();
expectedSubset.add(first);
resultSubset.add(result.removeFirst());
if(expectedResult.size() == 0) {
break;
}
} while(((Long)expectedResult.getFirst().get(0)).longValue() == ((Long)first.get(0)).longValue());
Assert.assertEquals(expectedSubset, resultSubset);
}
}
@Test
public void testFlush() {
StreamMerger<Tuple> merger = new StreamMerger<Tuple>(Arrays.asList(new Integer(0), new Integer(1)), 0);
GeneralTopologyContext contextMock = mock(GeneralTopologyContext.class);
when(contextMock.getComponentOutputFields(anyString(), anyString())).thenReturn(new Fields("ts"));
Tuple t = new TupleImpl(contextMock, new Values(new Long(0)), 0, "");
merger.addTuple(new Integer(0), t);
Assert.assertNull(merger.getNextTuple());
Tuple t2 = new TupleImpl(contextMock, new Values(new Long(1)), 1, TimestampMerger.FLUSH_STREAM_ID);
merger.addTuple(new Integer(1), t2);
Assert.assertSame(t, merger.getNextTuple());
Assert.assertNull(merger.getNextTuple());
}
}