ClassloaderRepositoryTest.java

package org.aspectj.apache.bcel.classfile.tests;

import java.net.URL;
import java.net.URLClassLoader;

import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.util.ClassLoaderRepository;

import junit.framework.TestCase;

/*
 * Tests create a simple classloader repository configuration and check sharing of information.
 * 
 * @author Andy Clement
 */
public class ClassloaderRepositoryTest extends TestCase {

	private ClassLoaderRepository rep1,rep2;

	public void setUp() throws Exception {
		super.setUp();
		ClassLoader cl = Thread.currentThread().getContextClassLoader();
		ClassLoader cl1 = new URLClassLoader(new URL[]{},cl);
		ClassLoader cl2 = new URLClassLoader(new URL[]{},cl);
		rep1 = new ClassLoaderRepository(cl1);
		rep2 = new ClassLoaderRepository(cl2);
	}

	// Retrieve string 5 times from same repository, 4 hits should be from local cache
	public void testLocalCacheWorks() throws ClassNotFoundException {
		ClassLoaderRepository.useSharedCache=false;
		JavaClass jc = rep1.loadClass("java.lang.String");
		jc = rep1.loadClass("java.lang.String");
		jc = rep1.loadClass("java.lang.String");
		jc = rep1.loadClass("java.lang.String");
		jc = rep1.loadClass("java.lang.String");
		assertTrue("Should have used local cache 4 times: "+reportLocalCacheHits(rep1),reportLocalCacheHits(rep1)==4);
	}

	// Retrieve string 5 times from same repository, 4 hits should be from local cache
	public void testSharedCacheWorksOnOne() throws ClassNotFoundException {
		ClassLoaderRepository.useSharedCache=true;
		JavaClass jc = rep1.loadClass("java.lang.String");
		jc = rep1.loadClass("java.lang.String");
		jc = rep1.loadClass("java.lang.String");
		jc = rep1.loadClass("java.lang.String");
		jc = rep1.loadClass("java.lang.String");
		assertTrue("Should have used local cache 4 times: "+reportSharedCacheHits(rep1),reportSharedCacheHits(rep1)==4);
	}

	// Retrieve String through one repository then load again through another, should be shared cache hit
	public void testSharedCacheWorks() throws ClassNotFoundException {
		ClassLoaderRepository.useSharedCache=true;
		JavaClass jc = rep1.loadClass("java.lang.String");
		jc = rep2.loadClass("java.lang.String");
		assertTrue("Should have retrieved String from shared cache: "+reportSharedCacheHits(rep1),
				reportSharedCacheHits(rep1)==1);
	}

	// Shared cache OFF, shouldn't get a shared cache hit
	public void testSharedCacheCanBeDeactivated() throws ClassNotFoundException {
		try {
			ClassLoaderRepository.useSharedCache=false;
			JavaClass jc = rep1.loadClass("java.lang.String");
			jc = rep2.loadClass("java.lang.String");
			assertTrue("Should not have retrieved String from shared cache: "+
					reportSharedCacheHits(rep1),
				    reportSharedCacheHits(rep1)==0);
		} finally {
			ClassLoaderRepository.useSharedCache=true;
		}
	}

	// ClassLoaderRepository.ignoreCacheClearRequests
	public void testIgnoreCacheClearRequests() throws Exception {
		ClassLoaderRepository.useSharedCache = false;
		try {
			// the 'normal' flow with ignore in default of false
			ClassLoaderRepository.ignoreCacheClearRequests = false;
			try {
				ClassLoaderRepository repository = setupRepository();
				repository.loadClass("java.lang.String");
				long localCacheHits = repository.reportStats()[5];
				assertEquals(0, localCacheHits);
				repository.clear();
				repository.loadClass("java.lang.String");
				localCacheHits = repository.reportStats()[5];
				assertEquals(0, localCacheHits); // cache was cleared, so no hit
			} finally {
				ClassLoaderRepository.ignoreCacheClearRequests = false;
			}
			// with ignore cache clear turned on
			ClassLoaderRepository.ignoreCacheClearRequests = true;
			try {
				ClassLoaderRepository repository = setupRepository();
				repository.loadClass("java.lang.String");
				long localCacheHits = repository.reportStats()[5];
				assertEquals(0, localCacheHits);
				repository.clear();
				repository.loadClass("java.lang.String");
				localCacheHits = repository.reportStats()[5];
				assertEquals(1, localCacheHits);
			} finally {
				ClassLoaderRepository.ignoreCacheClearRequests = false;
			}
		} finally {
			ClassLoaderRepository.useSharedCache = true;
		}
	}

	// ClassLoaderRepository.useUnavailableClassesCache
	public void testUnavailableClassesCache() throws Exception {
		ClassLoaderRepository.useUnavailableClassesCache = false;
		try {
			ClassLoaderRepository repository = setupRepository();
			attemptLoadThatWillFail(repository);
			for (int i = 0; i < 1000; i++) {
				attemptLoadThatWillFail(repository);
			}
			assertEquals(0, repository.reportStats()[8]);
		} finally {
			ClassLoaderRepository.useUnavailableClassesCache = false; // back to default
		}
		
		ClassLoaderRepository.useUnavailableClassesCache = true;
		try {
			ClassLoaderRepository repository = setupRepository();
			assertNotNull(repository.loadClass("java.lang.String"));
			attemptLoadThatWillFail(repository);
			for (int i = 0; i < 1000; i++) {
				attemptLoadThatWillFail(repository);
			}
			assertEquals(1000,repository.reportStats()[8]);
		} finally {
			ClassLoaderRepository.useUnavailableClassesCache = false;
		}
		// If checking the report stats for time spent manipulating URLs it will be massively reduced
	}

	private ClassLoaderRepository setupRepository() throws Exception {
		ClassLoader cl = Thread.currentThread().getContextClassLoader();
		ClassLoader res = new URLClassLoader(new URL[] {}, cl);
		ClassLoaderRepository rep = new ClassLoaderRepository(res);
		return rep;
	}

	private void attemptLoadThatWillFail(ClassLoaderRepository repository) {
		try {
			repository.loadClass("this.is.made.up");
			throw new IllegalStateException("Should not have found 'this.is.made.up'");
		} catch (ClassNotFoundException cnfe) {
			// ... expected ...
		}
	}

	public void tearDown() throws Exception {
		super.tearDown();
		System.err.println("Rep1: "+rep1.reportStats());
		System.err.println("Rep2: "+rep2.reportStats());
		rep1.reset();
		rep2.reset();
	}

	private long reportLocalCacheHits(ClassLoaderRepository rep) {
		return rep.reportStats()[5];
	}

	private long reportSharedCacheHits(ClassLoaderRepository rep) {
		return rep.reportStats()[3];
	}

}