package storm.starter.tools; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import org.jmock.lib.concurrent.Blitzer; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.util.List; import static org.fest.assertions.api.Assertions.assertThat; public class RankingsTest { private static final int ANY_TOPN = 42; private static final Rankable ANY_RANKABLE = new RankableObjectWithFields("someObject", ANY_TOPN); private static final Rankable ZERO = new RankableObjectWithFields("ZERO_COUNT", 0); private static final Rankable A = new RankableObjectWithFields("A", 1); private static final Rankable B = new RankableObjectWithFields("B", 2); private static final Rankable C = new RankableObjectWithFields("C", 3); private static final Rankable D = new RankableObjectWithFields("D", 4); private static final Rankable E = new RankableObjectWithFields("E", 5); private static final Rankable F = new RankableObjectWithFields("F", 6); private static final Rankable G = new RankableObjectWithFields("G", 7); private static final Rankable H = new RankableObjectWithFields("H", 8); @DataProvider public Object[][] illegalTopNData() { return new Object[][]{ { 0 }, { -1 }, { -2 }, { -10 } }; } @Test(expectedExceptions = IllegalArgumentException.class, dataProvider = "illegalTopNData") public void constructorWithNegativeOrZeroTopNShouldThrowIAE(int topN) { new Rankings(topN); } @DataProvider public Object[][] copyRankingsData() { return new Object[][]{ { 5, Lists.newArrayList(A, B, C) }, { 2, Lists.newArrayList(A, B, C, D) }, { 1, Lists.newArrayList() }, { 1, Lists.newArrayList(A) }, { 1, Lists.newArrayList(A, B) } }; } @Test(dataProvider = "copyRankingsData") public void copyConstructorShouldReturnCopy(int topN, List<Rankable> rankables) { // given Rankings rankings = new Rankings(topN); for (Rankable r : rankables) { rankings.updateWith(r); } // when Rankings copy = new Rankings(rankings); // then assertThat(copy.maxSize()).isEqualTo(rankings.maxSize()); assertThat(copy.getRankings()).isEqualTo(rankings.getRankings()); } @DataProvider public Object[][] defensiveCopyRankingsData() { return new Object[][]{ { 5, Lists.newArrayList(A, B, C), Lists.newArrayList(D) }, { 2, Lists.newArrayList(A, B, C, D), Lists.newArrayList(E, F) }, { 1, Lists.newArrayList(), Lists.newArrayList(A) }, { 1, Lists.newArrayList(A), Lists.newArrayList(B) }, { 1, Lists.newArrayList(ZERO), Lists.newArrayList(B) }, { 1, Lists.newArrayList(ZERO), Lists.newArrayList() } }; } @Test(dataProvider = "defensiveCopyRankingsData") public void copyConstructorShouldReturnDefensiveCopy(int topN, List<Rankable> rankables, List<Rankable> changes) { // given Rankings original = new Rankings(topN); for (Rankable r : rankables) { original.updateWith(r); } int expSize = original.size(); List<Rankable> expRankings = original.getRankings(); // when Rankings copy = new Rankings(original); for (Rankable r : changes) { copy.updateWith(r); } // then assertThat(original.size()).isEqualTo(expSize); assertThat(original.getRankings()).isEqualTo(expRankings); } @DataProvider public Object[][] legalTopNData() { return new Object[][]{ { 1 }, { 2 }, { 1000 }, { 1000000 } }; } @Test(dataProvider = "legalTopNData") public void constructorWithPositiveTopNShouldBeOk(int topN) { // given/when Rankings rankings = new Rankings(topN); // then assertThat(rankings.maxSize()).isEqualTo(topN); } @Test public void shouldHaveDefaultConstructor() { new Rankings(); } @Test public void defaultConstructorShouldSetPositiveTopN() { // given/when Rankings rankings = new Rankings(); // then assertThat(rankings.maxSize()).isGreaterThan(0); } @DataProvider public Object[][] rankingsGrowData() { return new Object[][]{ { 2, Lists.newArrayList(new RankableObjectWithFields("A", 1), new RankableObjectWithFields( "B", 2), new RankableObjectWithFields("C", 3)) }, { 2, Lists.newArrayList(new RankableObjectWithFields("A", 1), new RankableObjectWithFields("B", 2), new RankableObjectWithFields("C", 3), new RankableObjectWithFields("D", 4)) } }; } @Test(dataProvider = "rankingsGrowData") public void sizeOfRankingsShouldNotGrowBeyondTopN(int topN, List<Rankable> rankables) { // sanity check of the provided test data assertThat(rankables.size()).overridingErrorMessage( "The supplied test data is not correct: the number of rankables <%d> should be greater than <%d>", rankables.size(), topN).isGreaterThan(topN); // given Rankings rankings = new Rankings(topN); // when for (Rankable r : rankables) { rankings.updateWith(r); } // then assertThat(rankings.size()).isLessThanOrEqualTo(rankings.maxSize()); } @DataProvider public Object[][] simulatedRankingsData() { return new Object[][]{ { Lists.newArrayList(A), Lists.newArrayList(A) }, { Lists.newArrayList(B, D, A, C), Lists.newArrayList(D, C, B, A) }, { Lists.newArrayList(B, F, A, C, D, E), Lists.newArrayList(F, E, D, C, B, A) }, { Lists.newArrayList(G, B, F, A, C, D, E, H), Lists.newArrayList(H, G, F, E, D, C, B, A) } }; } @Test(dataProvider = "simulatedRankingsData") public void shouldCorrectlyRankWhenUpdatedWithRankables(List<Rankable> unsorted, List<Rankable> expSorted) { // given Rankings rankings = new Rankings(unsorted.size()); // when for (Rankable r : unsorted) { rankings.updateWith(r); } // then assertThat(rankings.getRankings()).isEqualTo(expSorted); } @Test(dataProvider = "simulatedRankingsData") public void shouldCorrectlyRankWhenEmptyAndUpdatedWithOtherRankings(List<Rankable> unsorted, List<Rankable> expSorted) { // given Rankings rankings = new Rankings(unsorted.size()); Rankings otherRankings = new Rankings(rankings.maxSize()); for (Rankable r : unsorted) { otherRankings.updateWith(r); } // when rankings.updateWith(otherRankings); // then assertThat(rankings.getRankings()).isEqualTo(expSorted); } @Test(dataProvider = "simulatedRankingsData") public void shouldCorrectlyRankWhenUpdatedWithEmptyOtherRankings(List<Rankable> unsorted, List<Rankable> expSorted) { // given Rankings rankings = new Rankings(unsorted.size()); for (Rankable r : unsorted) { rankings.updateWith(r); } Rankings emptyRankings = new Rankings(ANY_TOPN); // when rankings.updateWith(emptyRankings); // then assertThat(rankings.getRankings()).isEqualTo(expSorted); } @DataProvider public Object[][] simulatedRankingsAndOtherRankingsData() { return new Object[][]{ { Lists.newArrayList(A), Lists.newArrayList(A), Lists.newArrayList(A) }, { Lists.newArrayList(A, C), Lists.newArrayList(B, D), Lists.newArrayList(D, C, B, A) }, { Lists.newArrayList(B, F, A), Lists.newArrayList(C, D, E), Lists.newArrayList(F, E, D, C, B, A) }, { Lists.newArrayList(G, B, F, A, C), Lists.newArrayList(D, E, H), Lists.newArrayList(H, G, F, E, D, C, B, A) } }; } @Test(dataProvider = "simulatedRankingsAndOtherRankingsData") public void shouldCorrectlyRankWhenNotEmptyAndUpdatedWithOtherRankings(List<Rankable> unsorted, List<Rankable> unsortedForOtherRankings, List<Rankable> expSorted) { // given Rankings rankings = new Rankings(expSorted.size()); for (Rankable r : unsorted) { rankings.updateWith(r); } Rankings otherRankings = new Rankings(unsortedForOtherRankings.size()); for (Rankable r : unsortedForOtherRankings) { otherRankings.updateWith(r); } // when rankings.updateWith(otherRankings); // then assertThat(rankings.getRankings()).isEqualTo(expSorted); } @DataProvider public Object[][] duplicatesData() { Rankable A1 = new RankableObjectWithFields("A", 1); Rankable A2 = new RankableObjectWithFields("A", 2); Rankable A3 = new RankableObjectWithFields("A", 3); return new Object[][]{ { Lists.newArrayList(ANY_RANKABLE, ANY_RANKABLE, ANY_RANKABLE) }, { Lists.newArrayList(A1, A2, A3) }, }; } @Test(dataProvider = "duplicatesData") public void shouldNotRankDuplicateObjectsMoreThanOnce(List<Rankable> duplicates) { // given Rankings rankings = new Rankings(duplicates.size()); // when for (Rankable r : duplicates) { rankings.updateWith(r); } // then assertThat(rankings.size()).isEqualTo(1); } @DataProvider public Object[][] removeZeroRankingsData() { return new Object[][]{ { Lists.newArrayList(A, ZERO), Lists.newArrayList(A) }, { Lists.newArrayList(A), Lists.newArrayList(A) }, { Lists.newArrayList(ZERO, A), Lists.newArrayList(A) }, { Lists.newArrayList(ZERO), Lists.newArrayList() }, { Lists.newArrayList(ZERO, new RankableObjectWithFields("ZERO2", 0)), Lists.newArrayList() }, { Lists.newArrayList(B, ZERO, new RankableObjectWithFields("ZERO2", 0), D, new RankableObjectWithFields("ZERO3", 0), new RankableObjectWithFields("ZERO4", 0), C), Lists.newArrayList(D, C, B) }, { Lists.newArrayList(A, ZERO, B), Lists.newArrayList(B, A) } }; } @Test(dataProvider = "removeZeroRankingsData") public void shouldRemoveZeroCounts(List<Rankable> unsorted, List<Rankable> expSorted) { // given Rankings rankings = new Rankings(unsorted.size()); for (Rankable r : unsorted) { rankings.updateWith(r); } // when rankings.pruneZeroCounts(); // then assertThat(rankings.getRankings()).isEqualTo(expSorted); } @Test public void updatingWithNewRankablesShouldBeThreadSafe() throws InterruptedException { // given final List<Rankable> entries = ImmutableList.of(A, B, C, D); final Rankings rankings = new Rankings(entries.size()); // We are capturing exceptions thrown in Blitzer's child threads into this data structure so that we can properly // pass/fail this test. The reason is that Blitzer doesn't report exceptions, which is a known bug in Blitzer // (JMOCK-263). See https://github.com/jmock-developers/jmock-library/issues/22 for more information. final List<Exception> exceptions = Lists.newArrayList(); Blitzer blitzer = new Blitzer(1000); // when blitzer.blitz(new Runnable() { public void run() { for (Rankable r : entries) { try { rankings.updateWith(r); } catch (RuntimeException e) { synchronized(exceptions) { exceptions.add(e); } } } } }); blitzer.shutdown(); // then // if (!exceptions.isEmpty()) { for (Exception e : exceptions) { System.err.println(Throwables.getStackTraceAsString(e)); } } assertThat(exceptions).isEmpty(); } @Test(dataProvider = "copyRankingsData") public void copyShouldReturnCopy(int topN, List<Rankable> rankables) { // given Rankings rankings = new Rankings(topN); for (Rankable r : rankables) { rankings.updateWith(r); } // when Rankings copy = rankings.copy(); // then assertThat(copy.maxSize()).isEqualTo(rankings.maxSize()); assertThat(copy.getRankings()).isEqualTo(rankings.getRankings()); } @Test(dataProvider = "defensiveCopyRankingsData") public void copyShouldReturnDefensiveCopy(int topN, List<Rankable> rankables, List<Rankable> changes) { // given Rankings original = new Rankings(topN); for (Rankable r : rankables) { original.updateWith(r); } int expSize = original.size(); List<Rankable> expRankings = original.getRankings(); // when Rankings copy = original.copy(); for (Rankable r : changes) { copy.updateWith(r); } copy.pruneZeroCounts(); // then assertThat(original.size()).isEqualTo(expSize); assertThat(original.getRankings()).isEqualTo(expRankings); } }