Add test infrastructure Change-Id: Ia2f814fd2793cb63eb4302e9249902daa68b218c
diff --git a/hooks-its/pom.xml b/hooks-its/pom.xml index 41179f9..576530a 100644 --- a/hooks-its/pom.xml +++ b/hooks-its/pom.xml
@@ -36,5 +36,29 @@ <artifactId>gerrit-plugin-api</artifactId> <version>${project.version}</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> </project>
diff --git a/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java new file mode 100644 index 0000000..70098e5 --- /dev/null +++ b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/LoggingMockingTestCase.java
@@ -0,0 +1,99 @@ +// 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.log.LogUtil; + +import org.apache.log4j.spi.LoggingEvent; +import org.junit.After; + +import java.util.Iterator; + +public abstract class LoggingMockingTestCase extends MockingTestCase { + + private java.util.Collection<LoggingEvent> loggedEvents; + + protected final void assertLogMessageContains(String needle) { + LoggingEvent hit = null; + Iterator<LoggingEvent> iter = loggedEvents.iterator(); + while (hit == null && iter.hasNext()) { + LoggingEvent event = iter.next(); + if (event.getRenderedMessage().contains(needle)) { + 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 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/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/MockingTestCase.java new file mode 100644 index 0000000..819b138 --- /dev/null +++ b/hooks-its/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/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/CollectionAppender.java new file mode 100644 index 0000000..f63c7c3 --- /dev/null +++ b/hooks-its/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/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java new file mode 100644 index 0000000..57d98c7 --- /dev/null +++ b/hooks-its/src/test/java/com/googlesource/gerrit/plugins/hooks/testutil/log/LogUtil.java
@@ -0,0 +1,33 @@ +// 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 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; + } +}
diff --git a/pom.xml b/pom.xml index 4b67658..a1ee2c9 100644 --- a/pom.xml +++ b/pom.xml
@@ -26,6 +26,12 @@ <name>Gerrit Code Review - Issue tracker support</name> + <properties> + <easymockVersion>3.0</easymockVersion> + <powermockVersion>1.5</powermockVersion> + <slf4jVersion>1.6.2</slf4jVersion> + </properties> + <modules> <module>hooks-its</module> </modules>