/*
* Copyright 2014 Ben Manes. All Rights Reserved.
*
* 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 com.github.benmanes.caffeine.cache.testing;
import static com.github.benmanes.caffeine.cache.IsValidAsyncCache.validAsyncCache;
import static com.github.benmanes.caffeine.cache.IsValidCache.validCache;
import static com.github.benmanes.caffeine.cache.IsValidMapView.validAsMap;
import static com.github.benmanes.caffeine.cache.testing.CacheWriterVerifier.verifyWriter;
import static com.github.benmanes.caffeine.cache.testing.HasStats.hasHitCount;
import static com.github.benmanes.caffeine.cache.testing.HasStats.hasLoadFailureCount;
import static com.github.benmanes.caffeine.cache.testing.HasStats.hasLoadSuccessCount;
import static com.github.benmanes.caffeine.cache.testing.HasStats.hasMissCount;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.testng.IInvokedMethod;
import org.testng.IInvokedMethodListener;
import org.testng.ITestResult;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.testing.CacheSpec.ExecutorFailure;
/**
* A listener that validates the internal structure after a successful test execution.
*
* @author ben.manes@gmail.com (Ben Manes)
*/
public final class CacheValidationListener implements IInvokedMethodListener {
@Override
public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {}
@Override
public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
try {
if (testResult.isSuccess()) {
boolean foundCache = false;
CacheContext context = null;
for (Object param : testResult.getParameters()) {
if (param instanceof Cache<?, ?>) {
foundCache = true;
assertThat((Cache<?, ?>) param, is(validCache()));
} else if (param instanceof AsyncLoadingCache<?, ?>) {
foundCache = true;
assertThat((AsyncLoadingCache<?, ?>) param, is(validAsyncCache()));
} else if (param instanceof Map<?, ?>) {
foundCache = true;
assertThat((Map<?, ?>) param, is(validAsMap()));
} else if (param instanceof CacheContext) {
context = (CacheContext) param;
}
}
if (context != null) {
if (!foundCache) {
assertThat(context.cache, is(validCache()));
}
checkWriter(testResult, context);
checkNoStats(testResult, context);
checkExecutor(testResult, context);
}
} else {
testResult.setThrowable(new AssertionError(getTestName(method), testResult.getThrowable()));
}
} catch (Throwable caught) {
testResult.setStatus(ITestResult.FAILURE);
testResult.setThrowable(new AssertionError(getTestName(method), caught));
} finally {
cleanUp(testResult);
}
}
/** Returns the name of the executed test. */
private static String getTestName(IInvokedMethod method) {
return StringUtils.substringAfterLast(method.getTestMethod().getTestClass().getName(), ".")
+ "#" + method.getTestMethod().getConstructorOrMethod().getName();
}
/** Checks whether the {@link TrackingExecutor} had unexpected failures. */
private static void checkExecutor(ITestResult testResult, CacheContext context) {
Method testMethod = testResult.getMethod().getConstructorOrMethod().getMethod();
CacheSpec cacheSpec = testMethod.getAnnotation(CacheSpec.class);
if (cacheSpec == null) {
return;
}
assertThat("CacheContext required", context, is(not(nullValue())));
if (!(context.executor() instanceof TrackingExecutor)) {
return;
}
TrackingExecutor executor = (TrackingExecutor) context.executor();
if (cacheSpec.executorFailure() == ExecutorFailure.EXPECTED) {
assertThat(executor.failureCount(), is(greaterThan(0)));
} else if (cacheSpec.executorFailure() == ExecutorFailure.DISALLOWED) {
assertThat(executor.failureCount(), is(0));
}
}
/** Checks the writer if {@link CheckNoWriter} is found. */
private static void checkWriter(ITestResult testResult, CacheContext context) {
Method testMethod = testResult.getMethod().getConstructorOrMethod().getMethod();
CheckNoWriter checkWriter = testMethod.getAnnotation(CheckNoWriter.class);
if (checkWriter == null) {
return;
}
assertThat("Test requires CacheContext param for validation", context, is(not(nullValue())));
verifyWriter(context, (verifier, writer) -> verifier.zeroInteractions());
}
/** Checks the statistics if {@link CheckNoStats} is found. */
private static void checkNoStats(ITestResult testResult, CacheContext context) {
Method testMethod = testResult.getMethod().getConstructorOrMethod().getMethod();
boolean checkNoStats = testMethod.isAnnotationPresent(CheckNoStats.class);
if (!checkNoStats) {
return;
}
assertThat("Test requires CacheContext param for validation", context, is(not(nullValue())));
assertThat(context, hasHitCount(0));
assertThat(context, hasMissCount(0));
assertThat(context, hasLoadSuccessCount(0));
assertThat(context, hasLoadFailureCount(0));
}
/** Free memory by clearing unused resources after test execution. */
private void cleanUp(ITestResult testResult) {
Object[] params = testResult.getParameters();
for (int i = 0; i < params.length; i++) {
Object param = params[i];
if ((param instanceof AsyncLoadingCache<?, ?>) || (param instanceof Cache<?, ?>)
|| (param instanceof Map<?, ?>)) {
params[i] = param.getClass().getSimpleName();
} else {
params[i] = Objects.toString(param);
}
}
CacheSpec.interner.remove();
}
}