Adding test infrastructure Change-Id: Icc0bb8ba23203fcfb66a893765ab26b83459a5a3
diff --git a/pom.xml b/pom.xml index e8ad831..2fb3958 100644 --- a/pom.xml +++ b/pom.xml
@@ -31,6 +31,9 @@ <Gerrit-ReloadMode>reload</Gerrit-ReloadMode> <Gerrit-InitStep>com.googlesource.gerrit.plugins.hooks.bz.InitBugzilla</Gerrit-InitStep> <Gerrit-Module>com.googlesource.gerrit.plugins.hooks.bz.BugzillaModule</Gerrit-Module> + <easymockVersion>3.0</easymockVersion> + <powermockVersion>1.5</powermockVersion> + <slf4jVersion>1.6.2</slf4jVersion> </properties> <build> @@ -100,6 +103,30 @@ <artifactId>j2bugzilla</artifactId> <version>2.2-SNAPSHOT</version> </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <version>${slf4jVersion}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <version>${easymockVersion}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-module-junit4</artifactId> + <version>${powermockVersion}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.powermock</groupId> + <artifactId>powermock-api-easymock</artifactId> + <version>${powermockVersion}</version> + <scope>test</scope> + </dependency> </dependencies> <repositories>
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java new file mode 100644 index 0000000..4bc25b8 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java
@@ -0,0 +1,107 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.hooks.testutil; + +import com.google.common.collect.Lists; +import com.googlesource.gerrit.plugins.hooks.testutil.MockingTestCase; +import com.googlesource.gerrit.plugins.hooks.testutil.log.LogUtil; + +import org.apache.log4j.spi.LoggingEvent; +import org.junit.After; + +import java.util.Iterator; +import org.apache.log4j.Level; + +public abstract class LoggingMockingTestCase extends MockingTestCase { + + private java.util.Collection<LoggingEvent> loggedEvents; + + protected final void assertLogMessageContains(String needle, Level level) { + LoggingEvent hit = null; + Iterator<LoggingEvent> iter = loggedEvents.iterator(); + while (hit == null && iter.hasNext()) { + LoggingEvent event = iter.next(); + if (event.getRenderedMessage().contains(needle)) { + if (level == null || level.equals(event.getLevel())) { + hit = event; + } + } + } + assertNotNull("Could not find log message containing '" + needle + "'", + hit); + assertTrue("Could not remove log message containing '" + needle + "'", + loggedEvents.remove(hit)); + } + + protected final void assertLogMessageContains(String needle) { + assertLogMessageContains(needle, null); + } + + protected final void assertLogThrowableMessageContains(String needle) { + LoggingEvent hit = null; + Iterator<LoggingEvent> iter = loggedEvents.iterator(); + while (hit == null && iter.hasNext()) { + LoggingEvent event = iter.next(); + + if (event.getThrowableInformation().getThrowable().toString() + .contains(needle)) { + hit = event; + } + } + assertNotNull("Could not find log message with a Throwable containing '" + + needle + "'", hit); + assertTrue("Could not remove log message with a Throwable containing '" + + needle + "'", loggedEvents.remove(hit)); + } + + // As the PowerMock runner does not pass through runTest, we inject log + // verification through @After + @After + public final void assertNoUnassertedLogEvents() { + if (loggedEvents.size() > 0) { + LoggingEvent event = loggedEvents.iterator().next(); + String msg = "Found untreated logged events. First one is:\n"; + msg += event.getRenderedMessage(); + if (event.getThrowableInformation() != null) { + msg += "\n" + event.getThrowableInformation().getThrowable(); + } + fail(msg); + } + } + + @Override + public void setUp() throws Exception { + super.setUp(); + loggedEvents = Lists.newArrayList(); + + // The logger we're interested is class name without the trailing "Test". + // While this is not the most general approach it is sufficient for now, + // and we can improve later to allow tests to specify which loggers are + // to check. + String logName = this.getClass().getCanonicalName(); + logName = logName.substring(0, logName.length()-4); + LogUtil.logToCollection(logName, loggedEvents); + } + + @Override + protected void runTest() throws Throwable { + super.runTest(); + // Plain JUnit runner does not pick up @After, so we add it here + // explicitly. Note, that we cannot put this into tearDown, as failure + // to verify mocks would bail out and might leave open resources from + // subclasses open. + assertNoUnassertedLogEvents(); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java new file mode 100644 index 0000000..819b138 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java
@@ -0,0 +1,153 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.hooks.testutil; + +import junit.framework.TestCase; + +import org.easymock.EasyMock; +import org.easymock.IMocksControl; +import org.junit.After; +import org.junit.runner.RunWith; +import org.powermock.api.easymock.PowerMock; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Test case with some support for automatically verifying mocks. + */ +public abstract class MockingTestCase extends TestCase { + private Collection<Object> mocks; + private Collection<IMocksControl> mockControls; + private boolean mocksReplayed; + private boolean usePowerMock; + + /** + * Create and register a mock control. + * + * @return The mock control instance. + */ + protected final IMocksControl createMockControl() { + IMocksControl mockControl = EasyMock.createControl(); + assertTrue("Adding mock control failed", mockControls.add(mockControl)); + return mockControl; + } + + /** + * Create and register a mock. + * + * Creates a mock and registers it in the list of created mocks, so it gets + * treated automatically upon {@code replay} and {@code verify}; + * @param toMock The class to create a mock for. + * @return The mock instance. + */ + protected final <T> T createMock(Class<T> toMock) { + return createMock(toMock, null); + } + + /** + * Create a mock for a mock control and register a mock. + * + * Creates a mock and registers it in the list of created mocks, so it gets + * treated automatically upon {@code replay} and {@code verify}; + * @param toMock The class to create a mock for. + * @param control The mock control to create the mock on. If null, do not use + * a specific control. + * @return The mock instance. + */ + protected final <T> T createMock(Class<T> toMock, IMocksControl control) { + assertFalse("Mocks have already been set to replay", mocksReplayed); + final T mock; + if (control == null) { + if (usePowerMock) { + mock = PowerMock.createMock(toMock); + } else { + mock = EasyMock.createMock(toMock); + } + assertTrue("Adding " + toMock.getName() + " mock failed", + mocks.add(mock)); + } else { + mock = control.createMock(toMock); + } + return mock; + } + + /** + * Set all registered mocks to replay + */ + protected final void replayMocks() { + assertFalse("Mocks have already been set to replay", mocksReplayed); + if (usePowerMock) { + PowerMock.replayAll(); + } else { + EasyMock.replay(mocks.toArray()); + } + for (IMocksControl mockControl : mockControls) { + mockControl.replay(); + } + mocksReplayed = true; + } + + /** + * Verify all registered mocks + * + * This method is called automatically at the end of a test. Nevertheless, + * it is safe to also call it beforehand, if this better meets the + * verification part of a test. + */ + // As the PowerMock runner does not pass through runTest, we inject mock + // verification through @After + @After + public final void verifyMocks() { + if (!mocks.isEmpty() || !mockControls.isEmpty()) { + assertTrue("Created mocks have not been set to replay. Call replayMocks " + + "within the test", mocksReplayed); + if (usePowerMock) { + PowerMock.verifyAll(); + } else { + EasyMock.verify(mocks.toArray()); + } + for (IMocksControl mockControl : mockControls) { + mockControl.verify(); + } + } + } + + @Override + public void setUp() throws Exception { + super.setUp(); + + usePowerMock = false; + RunWith runWith = this.getClass().getAnnotation(RunWith.class); + if (runWith != null) { + usePowerMock = PowerMockRunner.class.isAssignableFrom(runWith.value()); + } + + mocks = new ArrayList<Object>(); + mockControls = new ArrayList<IMocksControl>(); + mocksReplayed = false; + } + + @Override + protected void runTest() throws Throwable { + super.runTest(); + // Plain JUnit runner does not pick up @After, so we add it here + // explicitly. Note, that we cannot put this into tearDown, as failure + // to verify mocks would bail out and might leave open resources from + // subclasses open. + verifyMocks(); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java new file mode 100644 index 0000000..f63c7c3 --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java
@@ -0,0 +1,58 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.hooks.testutil.log; + +import com.google.common.collect.Lists; + +import org.apache.log4j.AppenderSkeleton; +import org.apache.log4j.spi.LoggingEvent; + +import java.util.Collection; +import java.util.LinkedList; + +/** + * Log4j appender that logs into a list + */ +public class CollectionAppender extends AppenderSkeleton { + private Collection<LoggingEvent> events; + + public CollectionAppender() { + events = new LinkedList<LoggingEvent>(); + } + + public CollectionAppender(Collection<LoggingEvent> events) { + this.events = events; + } + + @Override + public boolean requiresLayout() { + return false; + } + + @Override + protected void append(LoggingEvent event) { + if (! events.add(event)) { + throw new RuntimeException("Could not append event " + event); + } + } + + @Override + public void close() { + } + + public Collection<LoggingEvent> getLoggedEvents() { + return Lists.newLinkedList(events); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java new file mode 100644 index 0000000..8ea559c --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java
@@ -0,0 +1,35 @@ +// Copyright (C) 2013 The Android Open Source Project +// +// 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.googlesource.gerrit.plugins.hooks.testutil.log; + +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; +import org.apache.log4j.spi.LoggingEvent; + +import com.googlesource.gerrit.plugins.hooks.testutil.log.CollectionAppender; + + +import java.util.Collection; + +public class LogUtil { + public static CollectionAppender logToCollection(String logName, + Collection<LoggingEvent> collection) { + Logger log = LogManager.getLogger(logName); + CollectionAppender listAppender = new CollectionAppender(collection); + log.removeAllAppenders(); + log.addAppender(listAppender); + return listAppender; + } +}