package de.is24.deadcode4j.plugin;
import de.is24.deadcode4j.junit.LoggingRule;
import org.apache.maven.execution.DefaultMavenExecutionRequest;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.LegacySupport;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.shared.runtime.MavenProjectProperties;
import org.apache.maven.shared.runtime.MavenRuntime;
import org.apache.maven.shared.runtime.MavenRuntimeException;
import org.codehaus.plexus.components.interactivity.Prompter;
import org.codehaus.plexus.components.interactivity.PrompterException;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URLEncoder;
import java.util.List;
import java.util.UUID;
import static com.google.common.base.Strings.emptyToNull;
import static com.google.common.collect.Lists.newArrayList;
import static de.is24.deadcode4j.plugin.UsageStatisticsManager.DeadCodeStatistics;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static org.codehaus.plexus.util.ReflectionUtils.setVariableValueInObject;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.AdditionalMatchers.and;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyList;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.contains;
import static org.mockito.Mockito.*;
public final class A_UsageStatisticsManager {
private Log log;
private UsageStatisticsManager objectUnderTest;
private HttpURLConnection urlConnectionMock;
private ByteArrayOutputStream outputStream;
@Rule
public LoggingRule enableLogging() {
log = LoggingRule.createMock();
return new LoggingRule(log);
}
@Before
public void setUp() throws Exception {
objectUnderTest = new UsageStatisticsManager() {
@Override
protected HttpURLConnection openUrlConnection() throws IOException {
super.openUrlConnection(); // cover this ;)
return urlConnectionMock;
}
};
MavenRuntime mavenRuntimeMock = mock(MavenRuntime.class);
when(mavenRuntimeMock.getProjectProperties(any(Class.class))).thenReturn(
new MavenProjectProperties("de.is24", "junit", "42.23"));
setVariableValueInObject(objectUnderTest, "mavenRuntime", mavenRuntimeMock);
Prompter prompterMock = mock(Prompter.class);
when(prompterMock.prompt(anyString(), anyList(), anyString())).thenReturn("N");
setVariableValueInObject(objectUnderTest, "prompter", prompterMock);
givenHttpTransferResultsIn(200);
}
@After
public void resetLog() {
reset(log);
}
@Test
public void shouldDoNothingIfSoConfigured() throws Exception {
givenModes(NetworkModes.ONLINE, InteractivityModes.INTERACTIVE);
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(TRUE, null));
assertThatStatisticsWereNotSent();
}
@Test
public void shouldDoNothingIfInOfflineMode() throws Exception {
givenModes(NetworkModes.OFFLINE, InteractivityModes.INTERACTIVE);
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(FALSE, null));
assertThatStatisticsWereNotSent();
}
@Test
public void shouldSimplySendStatisticsIfSoConfigured() throws Exception {
givenModes(NetworkModes.ONLINE, InteractivityModes.INTERACTIVE);
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(FALSE, null));
assertThatStatisticsWereSent();
}
@Test
@SuppressWarnings("ConstantConditions")
public void shouldSendStatisticsValues() throws Exception {
List<Object> expectedValues = newArrayList();
DeadCodeStatistics deadCodeStatistics = new DeadCodeStatistics(FALSE, "Greetings from JUnit!");
expectedValues.add("Greetings from JUnit!");
deadCodeStatistics.numberOfAnalyzedClasses = 1;
expectedValues.add(deadCodeStatistics.numberOfAnalyzedClasses);
deadCodeStatistics.numberOfAnalyzedModules = 22;
expectedValues.add(deadCodeStatistics.numberOfAnalyzedModules);
deadCodeStatistics.numberOfDeadClassesFound = 333;
expectedValues.add(deadCodeStatistics.numberOfDeadClassesFound);
deadCodeStatistics.config_numberOfClassesToIgnore = 4444;
expectedValues.add(deadCodeStatistics.config_numberOfClassesToIgnore);
deadCodeStatistics.config_numberOfCustomAnnotations = 55555;
expectedValues.add(deadCodeStatistics.config_numberOfCustomAnnotations);
deadCodeStatistics.config_numberOfCustomInterfaces = 6666;
expectedValues.add(deadCodeStatistics.config_numberOfCustomInterfaces);
deadCodeStatistics.config_numberOfCustomSuperclasses = 777;
expectedValues.add(deadCodeStatistics.config_numberOfCustomSuperclasses);
deadCodeStatistics.config_numberOfCustomXmlDefinitions = 88;
expectedValues.add(deadCodeStatistics.config_numberOfCustomXmlDefinitions);
deadCodeStatistics.config_numberOfModulesToSkip = 9;
expectedValues.add(deadCodeStatistics.config_numberOfModulesToSkip);
deadCodeStatistics.config_ignoreMainClasses = true;
expectedValues.add(deadCodeStatistics.config_ignoreMainClasses);
deadCodeStatistics.config_skipUpdateCheck = false;
expectedValues.add(deadCodeStatistics.config_skipUpdateCheck);
for (String key : UsageStatisticsManager.SystemProperties.KEYS.keySet()) {
String value = emptyToNull(System.getProperties().getProperty(key));
if (value == null) {
value = UUID.randomUUID().toString();
System.getProperties().setProperty(key, value);
}
expectedValues.add(value);
}
givenModes(NetworkModes.ONLINE, InteractivityModes.INTERACTIVE);
objectUnderTest.sendUsageStatistics(deadCodeStatistics);
assertThatStatisticsWereSent(expectedValues.toArray());
}
@Test
public void shouldHandleNon200ErrorCode() throws Exception {
givenModes(NetworkModes.ONLINE, InteractivityModes.NON_INTERACTIVE);
givenHttpTransferResultsIn(503);
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(FALSE, null));
assertThatStatisticsWereSent();
}
@Test
public void shouldHandleFailureInHttpTransfer() throws Exception {
givenModes(NetworkModes.ONLINE, InteractivityModes.NON_INTERACTIVE);
givenHttpConnectionFails();
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(FALSE, null));
verify(log).info(and(contains("Fail"), contains("usage statistics")));
}
@Test
public void shouldHandleFailureOfMavenRuntime() throws Exception {
givenModes(NetworkModes.ONLINE, InteractivityModes.NON_INTERACTIVE);
givenProjectPropertiesCannotBeDetermined();
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(FALSE, null));
assertThatStatisticsWereSent();
}
@Test
public void shouldAbortIfInNonInteractiveModeAndNonConfigured() throws Exception {
givenModes(NetworkModes.ONLINE, InteractivityModes.NON_INTERACTIVE);
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(null, null));
assertThatStatisticsWereNotSent();
}
@Test
public void shouldAbortIfRequestedByUser() throws Exception {
givenModes(NetworkModes.ONLINE, InteractivityModes.INTERACTIVE);
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(null, null));
assertThatStatisticsWereNotSent();
}
@Test
public void shouldSendStatisticsWithGivenCommentIfUserAgrees() throws Exception {
String comment = "Just do it!";
givenModes(NetworkModes.ONLINE, InteractivityModes.INTERACTIVE);
givenUserAgreesToSendStatistics(comment);
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(null, null));
assertThatStatisticsWereSent(comment);
}
@Test
public void shouldSendStatisticsWithConfiguredCommentIfUserAgrees() throws Exception {
String comment = "Just do it!";
givenModes(NetworkModes.ONLINE, InteractivityModes.INTERACTIVE);
givenUserAgreesToSendStatistics();
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(null, comment));
assertThatStatisticsWereSent(comment);
}
@Test
public void abortsIfPromptingFails() throws Exception {
givenModes(NetworkModes.ONLINE, InteractivityModes.INTERACTIVE);
givenPrompterFails();
objectUnderTest.sendUsageStatistics(new DeadCodeStatistics(null, null));
assertThatStatisticsWereNotSent();
}
@SuppressWarnings("deprecation") // there's no non-deprecated constructor for MavenSession :|
private void givenModes(NetworkModes networkMode, InteractivityModes interactivity) throws IllegalAccessException {
DefaultMavenExecutionRequest mavenExecutionRequest = new DefaultMavenExecutionRequest();
mavenExecutionRequest.setOffline(NetworkModes.OFFLINE == networkMode);
mavenExecutionRequest.setInteractiveMode(InteractivityModes.INTERACTIVE == interactivity);
mavenExecutionRequest.setSystemProperties(System.getProperties());
LegacySupport legacySupport = mock(LegacySupport.class);
when(legacySupport.getSession()).thenReturn(new MavenSession(null, null, mavenExecutionRequest, null));
setVariableValueInObject(objectUnderTest, "legacySupport", legacySupport);
}
private void givenHttpTransferResultsIn(int responseCode) throws Exception {
InputStream inputMock = mock(InputStream.class);
when(inputMock.read(any(byte[].class), anyInt(), anyInt())).then(new Answer<Integer>() {
@Override
public Integer answer(InvocationOnMock invocation) throws Throwable {
byte[] buffer = (byte[]) invocation.getArguments()[0];
int offset = (Integer) invocation.getArguments()[1];
buffer[offset] = 'F';
buffer[1 + offset] = 'A';
buffer[2 + offset] = 'I';
buffer[3 + offset] = 'L';
return 4;
}
}).thenReturn(-1);
urlConnectionMock = mock(HttpURLConnection.class);
when(urlConnectionMock.getInputStream()).thenReturn(inputMock);
outputStream = new ByteArrayOutputStream();
when(urlConnectionMock.getOutputStream()).thenReturn(outputStream);
when(urlConnectionMock.getResponseCode()).thenReturn(responseCode);
}
private void givenHttpConnectionFails() throws IOException {
urlConnectionMock = mock(HttpURLConnection.class);
doThrow(new IOException("I/O You!")).when(urlConnectionMock).connect();
}
private void givenProjectPropertiesCannotBeDetermined() throws MavenRuntimeException, IllegalAccessException {
MavenRuntime mock = mock(MavenRuntime.class);
when(mock.getProjectProperties(any(Class.class))).thenThrow(new MavenRuntimeException("Yack Fou!"));
setVariableValueInObject(objectUnderTest, "mavenRuntime", mock);
}
private void givenUserAgreesToSendStatistics(String comment) throws IllegalAccessException, PrompterException {
Prompter mock = mock(Prompter.class);
when(mock.prompt(anyString(), anyList(), anyString())).thenReturn("Y");
if (comment != null) {
when(mock.prompt(anyString())).thenReturn(comment);
}
setVariableValueInObject(objectUnderTest, "prompter", mock);
}
private void givenUserAgreesToSendStatistics() throws IllegalAccessException, PrompterException {
givenUserAgreesToSendStatistics(null);
}
private void givenPrompterFails() throws IllegalAccessException, PrompterException {
Prompter mock = mock(Prompter.class);
when(mock.prompt(anyString(), anyList(), anyString())).thenThrow(new PrompterException("Prompt You!"));
setVariableValueInObject(objectUnderTest, "prompter", mock);
}
private void assertThatStatisticsWereNotSent() throws Exception {
verifyZeroInteractions(urlConnectionMock);
}
private void assertThatStatisticsWereSent() throws Exception {
verify(urlConnectionMock).getResponseCode();
}
private void assertThatStatisticsWereSent(Object... values) throws UnsupportedEncodingException {
String sendData = outputStream.toString("UTF-8");
for (Object value : values) {
assertThat(sendData, Matchers.containsString(URLEncoder.encode(String.valueOf(value), "UTF-8")));
}
}
private enum NetworkModes {
ONLINE, OFFLINE
}
private enum InteractivityModes {
INTERACTIVE, NON_INTERACTIVE
}
}