package recommender.impl.multiplexer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.junit.Before;
import org.junit.Test;
import recommender.core.database.DBLogic;
import recommender.core.interfaces.RecommenderConnector;
import recommender.core.util.RecommendationResultComparator;
import recommender.impl.test.testutil.DummyDBLogic;
import recommender.impl.test.testutil.DummyPrivacyFilter;
import recommender.impl.test.testutil.DummyRecommendationRenderer;
import recommender.impl.test.testutil.SelectCounter;
import recommender.impl.test.util.DummyRecommendationEntity;
import recommender.impl.test.util.DummyRecommendationResult;
import recommender.impl.test.util.DummyRecommenderImpl;
/**
* This test tests the mechanism of the multiplexer. This is done for recommendation of tags.
* Because of only the mechanism of the multiplexer is tested, this should be representational
* for any generic type arguments.
*
* @author fei
*/
public class MultiplexingRecommenderTest {
private static final int NROFRECOS = 10;
private static final int MSTOWAIT = 1000;
private static final int QUERY_TIMEOUT = 2 * MSTOWAIT;
private static final int MAXSTOREITERATIONS = 100;
private static final int MAXSTORERECOMMENDER = 50;
private static final int MAXSTORENROFTAGS = 5;
//------------------------------------------------------------------------
// test cases
//------------------------------------------------------------------------
public class ResultStoreProducer extends Thread {
private RecommendationResultManager<DummyRecommendationEntity, DummyRecommendationResult> store;
private final int nrOfTags;
private final Long qid;
private final Long sid;
private final long timeout;
public ResultStoreProducer(final RecommendationResultManager<DummyRecommendationEntity, DummyRecommendationResult> store, final Long qid, final Long sid, final int nrOfTags, final long timeout) {
this.store = store;
this.nrOfTags = nrOfTags;
this.qid = qid;
this.sid = sid;
this.timeout = timeout;
}
@Override
public void run() {
final SortedSet<DummyRecommendationResult> result = new TreeSet<DummyRecommendationResult>(new RecommendationResultComparator<DummyRecommendationResult>());
for( int i = 0; i < this.nrOfTags; i++ ) {
result.add(new DummyRecommendationResult("Result_" + i, (1.0 * i) / (this.nrOfTags + 1), 0.5));
}
try {
Thread.sleep(this.timeout);
} catch (final InterruptedException ex) {
// ingonre
}
this.store.addResult(this.qid, this.sid, result);
}
}
/**
* Test the concurrent result cache
*/
@Test
public void testResultStore() {
final Random random = new Random();
random.setSeed(23234214);
// generate a set with different random ids
final Set<Long> queryIDs = new TreeSet<Long>();
while (queryIDs.size() < MAXSTOREITERATIONS) {
queryIDs.add(Long.valueOf(Math.abs(random.nextLong())));
}
// create store to test
final RecommendationResultManager<DummyRecommendationEntity, DummyRecommendationResult> store = new RecommendationResultManager<DummyRecommendationEntity, DummyRecommendationResult>();
//
// spawn MAXSTORERECOMMENDER for each query id
//
final Collection<ResultStoreProducer> producers = new LinkedList<ResultStoreProducer>();
Iterator<Long> iterator = queryIDs.iterator();
for (int i = 0; i < MAXSTOREITERATIONS; i++) {
final Long qid = iterator.next();
store.startQuery(qid);
for (int j = 0; j < MAXSTORERECOMMENDER; j++) {
final ResultStoreProducer producer = new ResultStoreProducer(store, qid, Long.valueOf(j), MAXSTORENROFTAGS, Math.round(Math.random()*1000)+1);
producers.add(producer);
producer.start();
}
}
try {
Thread.sleep(5000);
} catch (final InterruptedException ex) {
throw new RuntimeException(ex);
}
//
// run tests
//
iterator = queryIDs.iterator();
while (iterator.hasNext()) {
final Long qid = iterator.next();
store.stopQuery(qid);
// all producers should have delivered results
assertEquals("Resultstore missed a recommender.", MAXSTORERECOMMENDER, store.getActiveRecommender(qid).size());
// sum up the total number of recommended tags (from all producers in the query)
final Collection<SortedSet<DummyRecommendationResult>> resultsForQuery = store.getResultForQuery(qid);
int counter = 0;
for (final SortedSet<DummyRecommendationResult> result : resultsForQuery) {
counter += result.size();
}
assertEquals("Resultstore missed a result.", MAXSTORERECOMMENDER * MAXSTORENROFTAGS, counter);
// try to add a result to a stopped query
store.addResult(qid, Long.valueOf(42), new TreeSet<DummyRecommendationResult>());
assertEquals("Resultstore added a result after query timed out.", MAXSTORERECOMMENDER, store.getActiveRecommender(qid).size());
}
iterator = queryIDs.iterator();
for (int i = 0; i < MAXSTOREITERATIONS; i++) {
final Long qid = iterator.next();
store.releaseQuery(qid);
// assure that a released query doesn't deliver tags
assertNull("Resultstore delivered result for released query.", store.getResultForQuery(qid));
// assure that a released query is removed
assertEquals("Result store did not release query.", MAXSTOREITERATIONS-(i+1), store.getNrOfCachedQueries());
}
}
private MultiplexingRecommender<DummyRecommendationEntity, DummyRecommendationResult> multi;
private SelectCounter<DummyRecommendationEntity, DummyRecommendationResult> selector;
@Before
public void initMultiplexer() {
final DBLogic<DummyRecommendationEntity, DummyRecommendationResult> dbLogic = new DummyDBLogic<DummyRecommendationEntity, DummyRecommendationResult>();
// create dummy recommenders
final List<RecommenderConnector<DummyRecommendationEntity, DummyRecommendationResult>> recos = new ArrayList<RecommenderConnector<DummyRecommendationEntity, DummyRecommendationResult>>(NROFRECOS);
for (int i = 0; i < NROFRECOS; i++) {
final DummyRecommenderImpl reco = new DummyRecommenderImpl();
reco.setWait(MSTOWAIT);
reco.setId(Integer.valueOf(i));
recos.add(reco);
}
/*
* create recommender counter which counts the number of recommenders
* which delivered tags
*/
selector = new SelectCounter<DummyRecommendationEntity, DummyRecommendationResult>();
selector.setDbLogic(dbLogic);
// create multiplexer
multi = new MultiplexingRecommender<DummyRecommendationEntity, DummyRecommendationResult>();
multi.setDbLogic(dbLogic);
multi.setPrivacyFilter(new DummyPrivacyFilter<DummyRecommendationEntity>());
multi.setRenderer(new DummyRecommendationRenderer<DummyRecommendationEntity, DummyRecommendationResult>());
multi.setResultSelector(selector);
multi.setQueryTimeout(QUERY_TIMEOUT);
// add dummy recommender
multi.setBeanConfiguredRecommenders(recos);
// initialize multiplexer
multi.init();
}
/**
* Test querying a lot of recommender in parallel
*
* @throws Exception
*/
@Test
public void testMultiThreading() throws Exception {
// query recommender
multi.getRecommendationsForUser("", new DummyRecommendationEntity());
// test
assertEquals("Not all recommenders delivered results", NROFRECOS, selector.getRecoCounter());
}
/**
* tests if the query time out works
*/
@Test
public void testQueryTimeout() {
final DummyRecommenderImpl reco = new DummyRecommenderImpl();
multi.addRecommender(reco);
reco.setWait(QUERY_TIMEOUT * 2);
reco.setId(Integer.valueOf(1000));
// query recommender
multi.getRecommendationsForUser("", new DummyRecommendationEntity());
// test
assertEquals("Not all recommenders delivered results", NROFRECOS, selector.getRecoCounter());
}
}