package de.axone.cache.ng;
import static de.axone.cache.ng.CacheNGAssert.*;
import static de.axone.cache.ng.CacheNGTest_Port_Client.*;
import static org.assertj.core.api.Assertions.*;
import static org.testng.Assert.*;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.testng.annotations.Test;
import de.axone.cache.ng.CacheNG.UniversalAccessor;
import de.axone.cache.ng.CacheNGTestHelpers.TestRealm;
import de.axone.cache.ng.CacheNGTest_Port_Client.TestEntry;
@Test( groups="cacheng.autocache" )
public class CacheNGTest_Port_AutomaticClient {
//private static final Object mutex = new Object();
private static final int NUM_THREADS = 1000;
private static final int NUM_RUNS = 100000;
private static final String X="x", Y="y", Z="z";
private static final TestEntry x=new TestEntry( X ), y=new TestEntry( Y ), z=new TestEntry( Z );
private static TestEntry[] testEntries = new TestEntry[]{ a, b, c, null };
private static TestEntry[] testEntries2 = new TestEntry[]{ x, y, z, null };
private static String [] testEntryKeys = new String[]{ A, B, C, D };
private static String [] testEntryKeys2 = new String[]{ X, Y, Z, D };
private static CacheNG.Cache<String,TestEntry> backend =
new CacheLRUMap<>( new TestRealm<String,TestEntry>( "AutomaticCacheTest" ), 4);
//private static Cache<String,TestEntry> backend = new CacheHashMap<String,TestEntry>();
private static TestDataAccessor acc = new TestDataAccessor();
private static CacheNG.AutomaticClient<String,TestEntry> auto =
new AutomaticClientImpl<String,TestEntry>( backend );
// Second test for multiple frontend on one backend
private static CacheNG.AutomaticClient<String,TestEntry> auto2 =
new AutomaticClientImpl<String,TestEntry>( backend );
//@Test( enabled=false )
public void testAutomaticCache(){
SecureRandom srand = new SecureRandom();
assertEquals( auto.fetch( A, acc ), a );
assertEquals( auto.fetch( B, acc ), b );
assertEquals( auto.fetch( C, acc ), c );
assertEquals( auto.fetch( A, acc ), a );
assertEquals( auto.fetch( B, acc ), b );
assertEquals( auto.fetch( C, acc ), c );
assertEquals( auto.fetch( A, acc ), a );
assertEquals( auto.fetch( B, acc ), b );
assertEquals( auto.fetch( C, acc ), c );
for( int i=0; i<NUM_RUNS; i++ ){
int r = srand.nextInt( 4 );
String k = testEntryKeys[r];
TestEntry e = testEntries[r];
assertThat( auto ).fetch( k, acc ).isEqualTo( e );
}
/*
E.rr( "Auto: " + auto.stats() );
E.rr( backend.info() );
E.rr( acc );
*/
}
//@Test( enabled=false )
public void testAutomaticCacheMulti(){
// Encapsulate so that we don't reverse order of underlying array!
List<String> keysAsList = new ArrayList<String>( Arrays.asList( testEntryKeys ) );
Map<String,TestEntry> result;
result = auto.fetch( keysAsList, acc );
assertThat( result )
.containsEntry( A, a )
.containsEntry( B, b )
.containsEntry( C, c )
.doesNotContainKey( D )
;
Collections.reverse( keysAsList );
result = auto.fetch( keysAsList, acc );
assertThat( result )
.containsEntry( A, a )
.containsEntry( B, b )
.containsEntry( C, c )
.doesNotContainKey( D )
;
}
//@Test( enabled=false )
public void testThreaded() throws Exception {
TestThread [] threads = new TestThread[NUM_THREADS];
for( int i=0; i<threads.length; i++ ){
threads[i] = new TestThread(i, i%4);
threads[i].start();
}
// Wait for threads to finish
for( TestThread t : threads ){
t.join();
}
for( TestThread t : threads ){
assertEquals( t.getState(), Thread.State.TERMINATED );
assertNull( t.error );
}
assertEquals( backend.size(), 4, "Test for some rare synchronization problem" );
/*
E.rr( "Auto: " + auto.stats() );
E.rr( backend.info() );
E.rr( acc );
*/
}
public void testThreadedMulti() throws Exception {
TestThreadMulti [] threads = new TestThreadMulti[NUM_THREADS];
for( int i=0; i<threads.length; i++ ){
threads[i] = new TestThreadMulti(i);
threads[i].start();
}
// Wait for threads to finish
for( TestThreadMulti t : threads ){
t.join();
}
for( TestThreadMulti t : threads ){
assertEquals( t.getState(), Thread.State.TERMINATED );
assertNull( t.error );
}
assertEquals( backend.size(), 4, "Test for some rare synchronization problem" );
/*
E.rr( "Auto: " + auto.stats() );
E.rr( backend.info() );
E.rr( acc );
*/
}
private static class TestThread extends Thread {
final int no;
final int index;
Throwable error;
TestThread( int no, int index ){
this.no=no;
this.index=index;
}
@Override
public void run() {
try {
for( int i=0; i<NUM_RUNS/NUM_THREADS; i++ ){
String k = testEntryKeys[index];
String k2 = testEntryKeys2[index];
TestEntry e = testEntries[index];
TestEntry e2 = testEntries2[index];
assertThat( auto ).fetch( k, acc ).isEqualTo( e );
assertThat( auto2 ).fetch( k2, acc ).isEqualTo( e2 );
synchronized( this ){
try {
this.wait( 1 );
} catch( InterruptedException e1 ) {
e1.printStackTrace();
}
}
}
} catch( Error t ){
this.error = t;
throw t;
}
}
@Override public String toString(){
return "TestThread no. " + no;
}
};
private static class TestThreadMulti extends Thread {
final int no;
final List<String> keysAsList, keysAsList2;
Throwable error;
TestThreadMulti( int no ){
this.no=no;
keysAsList = new ArrayList<String>( Arrays.asList( testEntryKeys ) );
keysAsList2 = new ArrayList<String>( Arrays.asList( testEntryKeys2 ) );
Collections.shuffle( keysAsList );
Collections.shuffle( keysAsList2 );
}
@Override
public void run() {
try {
for( int i=0; i<NUM_RUNS/NUM_THREADS; i++ ){
Map<String,TestEntry> result, result2;
result = auto.fetch( keysAsList, acc );
assertThat( result )
.containsEntry( A, a )
.containsEntry( B, b )
.containsEntry( C, c )
.doesNotContainKey( D );
result2 = auto.fetch( keysAsList2, acc );
assertThat( result2 )
.containsEntry( X, x )
.containsEntry( Y, y )
.containsEntry( Z, z )
.doesNotContainKey( D );
synchronized( this ){
try {
this.wait( 1 );
//Thread.sleep( 1 );
} catch( InterruptedException e1 ) {
e1.printStackTrace();
}
}
}
} catch( Error t ){
this.error = t;
throw t;
}
}
@Override public String toString(){
return "TestThreadMulti no. " + no;
}
};
private static final class TestDataAccessor implements UniversalAccessor<String,TestEntry> {
private int hitcount;
private int acccount;
@Override
public synchronized TestEntry fetch( String key ) {
hitcount++;
acccount++;
TestEntry result = null;
if( "a".equals( key ) ) result = a;
else if( "b".equals( key ) ) result = b;
else if( "c".equals( key ) ) result = c;
else if( "x".equals( key ) ) result = x;
else if( "y".equals( key ) ) result = y;
else if( "z".equals( key ) ) result = z;
else hitcount--;
return result;
}
@Override
public synchronized Map<String, TestEntry> fetch( Collection<String> keys ) {
Map<String, TestEntry> result = new TreeMap<String, TestEntry>();
for( String key : keys ){
result.put( key, fetch( key ) );
}
return result;
}
@Override
public String toString(){
return "Accessor Hitcount: " + hitcount + "/" + acccount + "=" + ((int)(Math.round( 100.0*hitcount/acccount )) + "%" );
}
}
}