This is an automated email from the ASF dual-hosted git repository. danhaywood pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/isis.git
commit 55936f2d4358f80890363af8e0e0bbb61e375990 Author: Dan Haywood <d...@haywood-associates.co.uk> AuthorDate: Mon Dec 4 08:45:00 2017 +0000 ISIS-1784: adds new IntegrationTestAbstract3 as an improved/simplified way of bootstrapping integration tests based on a Module rather than an AppManifest. --- .../integtestsupport/IntegrationTestAbstract3.java | 520 +++++++++++++++++++++ .../core/integtestsupport/logging/LogConfig.java | 81 ++++ .../core/integtestsupport/logging/LogStream.java | 87 ++++ ...egrationTestAbstract3_haveSameModules_Test.java | 54 +++ 4 files changed, 742 insertions(+) diff --git a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3.java b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3.java new file mode 100644 index 0000000..98b07fd --- /dev/null +++ b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3.java @@ -0,0 +1,520 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.isis.core.integtestsupport; + +import java.io.PrintStream; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.jdo.PersistenceManagerFactory; + +import com.google.common.base.Function; +import com.google.common.base.Strings; +import com.google.common.base.Throwables; +import com.google.common.collect.FluentIterable; + +import org.apache.log4j.PropertyConfigurator; +import org.joda.time.LocalDate; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.junit.rules.MethodRule; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; + +import org.apache.isis.applib.AppManifest; +import org.apache.isis.applib.AppManifestAbstract; +import org.apache.isis.applib.NonRecoverableException; +import org.apache.isis.applib.RecoverableException; +import org.apache.isis.applib.clock.Clock; +import org.apache.isis.applib.clock.TickingFixtureClock; +import org.apache.isis.applib.fixtures.FixtureClock; +import org.apache.isis.applib.fixturescripts.BuilderScriptAbstract; +import org.apache.isis.applib.fixturescripts.FixtureScript; +import org.apache.isis.applib.fixturescripts.FixtureScripts; +import org.apache.isis.applib.modules.Module; +import org.apache.isis.applib.services.clock.ClockService; +import org.apache.isis.applib.services.factory.FactoryService; +import org.apache.isis.applib.services.jdosupport.IsisJdoSupport; +import org.apache.isis.applib.services.registry.ServiceRegistry2; +import org.apache.isis.applib.services.repository.RepositoryService; +import org.apache.isis.applib.services.sessmgmt.SessionManagementService; +import org.apache.isis.applib.services.user.UserService; +import org.apache.isis.applib.services.wrapper.WrapperFactory; +import org.apache.isis.applib.services.xactn.TransactionService; +import org.apache.isis.core.commons.factory.InstanceUtil; +import org.apache.isis.core.integtestsupport.logging.LogConfig; +import org.apache.isis.core.integtestsupport.logging.LogStream; +import org.apache.isis.core.runtime.system.context.IsisContext; +import org.apache.isis.core.runtime.system.session.IsisSessionFactory; +import org.apache.isis.objectstore.jdo.datanucleus.IsisConfigurationForJdoIntegTests; + +/** + * Reworked base class for integration tests, uses a {@link Module} to bootstrap, rather than an {@link AppManifest}. + */ +public abstract class IntegrationTestAbstract3 { + + private static final Logger LOG = LoggerFactory.getLogger(IntegrationTestAbstract3.class); + private final LogConfig logConfig; + + protected static PrintStream logPrintStream() { + return logPrintStream(Level.DEBUG); + } + protected static PrintStream logPrintStream(Level level) { + return LogStream.logPrintStream(LOG, level); + } + + @Rule + public ExpectedException expectedExceptions = ExpectedException.none(); + + /** + * this is asymmetric - handles only the teardown of the transaction afterwards, not the initial set up + * (which is done instead by the @Before, so that can also bootstrap system the very first time) + */ + @Rule + public IntegrationTestAbstract3.IsisTransactionRule isisTransactionRule = new IntegrationTestAbstract3.IsisTransactionRule(); + + private final static ThreadLocal<Boolean> setupLogging = new ThreadLocal<Boolean>() {{ + set(false); + }}; + + protected final Module module; + private final Class[] additionalModuleClasses; + + public IntegrationTestAbstract3(final Module module, final Class... additionalModuleClasses) { + this(new LogConfig(Level.INFO), module, additionalModuleClasses); + } + + private Long t0; + public IntegrationTestAbstract3( + final LogConfig logConfig, + final Module module, final Class... additionalModuleClasses) { + this.logConfig = logConfig; + final boolean firstTime = !setupLogging.get(); + if(firstTime) { + PropertyConfigurator.configure(logConfig.getLoggingPropertyFile()); + System.setOut(logConfig.getFixtureTracing()); + setupLogging.set(true); + t0 = System.currentTimeMillis(); + } + + final String moduleFqcn = System.getProperty("isis.integTest.module"); + + if(!Strings.isNullOrEmpty(moduleFqcn)) { + this.module = InstanceUtil.createInstance(moduleFqcn, Module.class); + this.additionalModuleClasses = new Class<?>[] { }; + } else { + this.module = module; + this.additionalModuleClasses = additionalModuleClasses; + } + } + + private void log(final String message) { + switch (logConfig.getTestLoggingLevel()) { + case ERROR: + LOG.error(message); + break; + case WARN: + LOG.warn(message); + break; + case INFO: + LOG.info(message); + break; + case DEBUG: + LOG.debug(message); + break; + case TRACE: + LOG.trace(message); + break; + } + } + + private LocalDate timeBeforeTest; + + @Before + public void bootstrapAndSetupIfRequired() { + + System.setProperty("isis.integTest", "true"); + + bootstrapIfRequired(); + + if(t0 != null) { + long t1 = System.currentTimeMillis(); + log("##########################################################################"); + log("# Bootstrapped in " + (t1- t0) + " millis"); + log("##########################################################################"); + } + log("### TEST: " + this.getClass().getCanonicalName()); + + beginTransaction(); + + timeBeforeTest = Clock.getTimeAsLocalDate(); + + setupModuleRefData(); + } + + private void bootstrapIfRequired() { + + final AppManifestAbstract.Builder builder = + Module.Util.builderFor(module) + .withAdditionalModules(additionalModuleClasses); // eg fake module, as passed into constructor + + final AppManifest appManifest = builder.build(); + + bootstrapUsing(appManifest); + } + + /** + * The {@link AppManifest} used to bootstrap the {@link IsisSystemForTest} (on the thread-local) + */ + private static ThreadLocal<AppManifest> isftAppManifest = new ThreadLocal<>(); + + private void bootstrapUsing(AppManifest appManifest) { + + final SystemState systemState = determineSystemState(appManifest); + switch (systemState) { + + case BOOTSTRAPPED_SAME_MODULES: + // nothing to do + break; + case BOOTSTRAPPED_DIFFERENT_MODULES: + // TODO: this doesn't work correctly yet; + teardownSystem(); + setupSystem(appManifest); + break; + case NOT_BOOTSTRAPPED: + setupSystem(appManifest); + TickingFixtureClock.replaceExisting(); + break; + } + } + + private static void teardownSystem() { + final IsisSessionFactory isisSessionFactory = IsisSystemForTest.get().getService(IsisSessionFactory.class); + + // TODO: this ought to be part of isisSessionFactory's responsibilities + final IsisJdoSupport isisJdoSupport = isisSessionFactory.getServicesInjector() + .lookupService(IsisJdoSupport.class); + final PersistenceManagerFactory pmf = + isisJdoSupport.getJdoPersistenceManager().getPersistenceManagerFactory(); + isisSessionFactory.destroyServicesAndShutdown(); + pmf.close(); + + IsisContext.testReset(); + } + + private static void setupSystem(final AppManifest appManifest) { + + final IsisConfigurationForJdoIntegTests configuration = new IsisConfigurationForJdoIntegTests(); + configuration.putDataNucleusProperty("javax.jdo.option.ConnectionURL","jdbc:hsqldb:mem:test-" + UUID.randomUUID().toString()); + final IsisSystemForTest.Builder isftBuilder = + new IsisSystemForTest.Builder() + .withLoggingAt(org.apache.log4j.Level.INFO) + .with(appManifest) + .with(configuration); + + IsisSystemForTest isft = isftBuilder.build(); + isft.setUpSystem(); + + // save both the system and the manifest + // used to bootstrap the system onto thread-loca + IsisSystemForTest.set(isft); + isftAppManifest.set(appManifest); + } + + enum SystemState { + NOT_BOOTSTRAPPED, + BOOTSTRAPPED_SAME_MODULES, + BOOTSTRAPPED_DIFFERENT_MODULES + } + + private static SystemState determineSystemState(final AppManifest appManifest) { + IsisSystemForTest isft = IsisSystemForTest.getElseNull(); + if (isft == null) + return SystemState.NOT_BOOTSTRAPPED; + + final AppManifest appManifestFromPreviously = isftAppManifest.get(); + return haveSameModules(appManifest, appManifestFromPreviously) + ? SystemState.BOOTSTRAPPED_SAME_MODULES + : SystemState.BOOTSTRAPPED_DIFFERENT_MODULES; + } + + static boolean haveSameModules( + final AppManifest m1, + final AppManifest m2) { + final List<Class<?>> m1Modules = m1.getModules(); + final List<Class<?>> m2Modules = m2.getModules(); + return m1Modules.containsAll(m2Modules) && m2Modules.containsAll(m1Modules); + } + + private static class IsisTransactionRule implements MethodRule { + + @Override + public Statement apply(final Statement base, final FrameworkMethod method, final Object target) { + + return new Statement() { + @Override + public void evaluate() throws Throwable { + + // we don't set up the ISFT, because the very first time it won't be there. + // Instead we expect it to be bootstrapped via @Before + try { + base.evaluate(); + final IsisSystemForTest isft = IsisSystemForTest.get(); + isft.endTran(); + } catch(final Throwable e) { + // determine if underlying cause is an applib-defined exception, + final RecoverableException recoverableException = + determineIfRecoverableException(e); + final NonRecoverableException nonRecoverableException = + determineIfNonRecoverableException(e); + + if(recoverableException != null) { + try { + final IsisSystemForTest isft = IsisSystemForTest.get(); + isft.getContainer().flush(); // don't care if npe + isft.getService(IsisJdoSupport.class).getJdoPersistenceManager().flush(); + } catch (Exception ignore) { + // ignore + } + } + // attempt to close this + try { + final IsisSystemForTest isft = IsisSystemForTest.getElseNull(); + isft.closeSession(); // don't care if npe + } catch(Exception ignore) { + // ignore + } + + // attempt to start another + try { + final IsisSystemForTest isft = IsisSystemForTest.getElseNull(); + isft.openSession(); // don't care if npe + } catch(Exception ignore) { + // ignore + } + + + // if underlying cause is an applib-defined, then + // throw that rather than Isis' wrapper exception + if(recoverableException != null) { + throw recoverableException; + } + if(nonRecoverableException != null) { + throw nonRecoverableException; + } + + // report on the error that caused + // a problem for *this* test + throw e; + } + } + + NonRecoverableException determineIfNonRecoverableException(final Throwable e) { + NonRecoverableException nonRecoverableException = null; + final List<Throwable> causalChain2 = Throwables.getCausalChain(e); + for (final Throwable cause : causalChain2) { + if(cause instanceof NonRecoverableException) { + nonRecoverableException = (NonRecoverableException) cause; + break; + } + } + return nonRecoverableException; + } + + RecoverableException determineIfRecoverableException(final Throwable e) { + RecoverableException recoverableException = null; + final List<Throwable> causalChain = Throwables.getCausalChain(e); + for (final Throwable cause : causalChain) { + if(cause instanceof RecoverableException) { + recoverableException = (RecoverableException) cause; + break; + } + } + return recoverableException; + } + }; + } + } + + private void beginTransaction() { + final IsisSystemForTest isft = IsisSystemForTest.get(); + + isft.getContainer().injectServicesInto(this); + isft.beginTran(); + } + + protected void setupModuleRefData() { + final List<Module> dependencies = Module.Util.transitiveDependenciesOf(module); + for (Module dependency : dependencies) { + final FixtureScript fixture = dependency.getRefDataSetupFixture(); + if(fixture != null) { + runFixtureScript(fixture); + } + } + } + + @After + public void tearDownAllModules() { + + final boolean testHealthy = transactionService != null; + if(!testHealthy) { + // avoid throwing an NPE here if something unexpected has occurred... + return; + } + + transactionService.nextTransaction(); + + final List<Module> dependencies = Module.Util.transitiveDependenciesOf(module); + Collections.reverse(dependencies); + + for (Module dependency : dependencies) { + final FixtureScript fixture = dependency.getTeardownFixture(); + if(fixture != null) { + runFixtureScript(fixture); + } + } + + // reinstate clock + setFixtureClockDate(timeBeforeTest); + + } + + protected void runFixtureScript(final FixtureScript... fixtureScriptList) { + if (fixtureScriptList.length == 1) { + this.fixtureScripts.runFixtureScript(fixtureScriptList[0], null); + } else { + this.fixtureScripts.runFixtureScript(new FixtureScript() { + protected void execute(ExecutionContext executionContext) { + FixtureScript[] fixtureScripts = fixtureScriptList; + for (FixtureScript fixtureScript : fixtureScripts) { + executionContext.executeChild(this, fixtureScript); + } + } + }, null); + } + + transactionService.nextTransaction(); + } + + + protected <T,F extends BuilderScriptAbstract<T,F>> T runBuilderScript(final F fixture) { + + serviceRegistry.injectServicesInto(fixture); + + fixture.run(null); + + + final T object = fixture.getObject(); + transactionService.nextTransaction(); + + return object; + } + + + private static Class[] asClasses(final List<Module> dependencies) { + final List<? extends Class<? extends Module>> dependenciesAsClasses = + FluentIterable.from(dependencies).transform(new Function<Module, Class<? extends Module>>() { + @Nullable @Override public Class apply(@Nullable final Module module) { + return module.getClass(); + } + }).toList(); + return dependenciesAsClasses.toArray(new Class[] {}); + } + + /** + * For convenience of subclasses, remove some boilerplate + */ + protected <T> T wrap(final T obj) { + return wrapperFactory.wrap(obj); + } + + /** + * For convenience of subclasses, remove some boilerplate + */ + protected <T> T mixin(final Class<T> mixinClass, final Object mixedIn) { + return factoryService.mixin(mixinClass, mixedIn); + } + + + /** + * To use instead of {@link #getFixtureClock()}'s {@link FixtureClock#setDate(int, int, int)} ()}. + */ + protected void setFixtureClockDate(final LocalDate date) { + setFixtureClockDate(date.getYear(), date.getMonthOfYear(), date.getDayOfMonth()); + } + + /** + * To use instead of {@link #getFixtureClock()}'s {@link FixtureClock#setDate(int, int, int)} ()}. + */ + protected void setFixtureClockDate(final int year, final int month, final int day) { + final Clock instance = Clock.getInstance(); + + if(instance instanceof TickingFixtureClock) { + TickingFixtureClock.reinstateExisting(); + getFixtureClock().setDate(year, month, day); + TickingFixtureClock.replaceExisting(); + } + + if(instance instanceof FixtureClock) { + getFixtureClock().setDate(year, month, day); + } + } + + /** + * If just require the current time, use {@link ClockService} + */ + private FixtureClock getFixtureClock() { + return ((FixtureClock)FixtureClock.getInstance()); + } + + + + /** + * For convenience of subclasses, remove some boilerplate + */ + protected <T> T unwrap(final T obj) { + return wrapperFactory.unwrap(obj); + } + + @Inject + protected FixtureScripts fixtureScripts; + @Inject + protected FactoryService factoryService; + @Inject + protected ServiceRegistry2 serviceRegistry; + @Inject + protected RepositoryService repositoryService; + @Inject + protected UserService userService; + @Inject + protected WrapperFactory wrapperFactory; + @Inject + protected TransactionService transactionService; + @Inject + protected SessionManagementService sessionManagementService; + +} \ No newline at end of file diff --git a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/logging/LogConfig.java b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/logging/LogConfig.java new file mode 100644 index 0000000..143d2ac --- /dev/null +++ b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/logging/LogConfig.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.isis.core.integtestsupport.logging; + +import java.io.PrintStream; + +import org.slf4j.Logger; +import org.slf4j.event.Level; + +import static org.slf4j.event.Level.INFO; + +public class LogConfig { + private final String loggingPropertyFile; + private final Level testLoggingLevel; + private final PrintStream fixtureTracing; + + public LogConfig( + final Level testLoggingLevel, + final Level fixtureTracingLevel, + final Logger fixtureTracingLogger) { + this(testLoggingLevel, fixtureTracingLevel, fixtureTracingLogger, null); + } + public LogConfig( + final Level testLoggingLevel, + final Level fixtureTracingLevel, + final Logger fixtureTracingLogger, + final String loggingPropertyFile) { + this(testLoggingLevel, LogStream.logPrintStream(fixtureTracingLogger, fixtureTracingLevel), loggingPropertyFile); + } + public LogConfig( + final Level testLoggingLevel) { + this(testLoggingLevel, (String)null); + } + public LogConfig( + final Level testLoggingLevel, + final String loggingPropertyFile) { + this(testLoggingLevel, null, loggingPropertyFile); + } + public LogConfig( + final Level testLoggingLevel, + final PrintStream fixtureTracing) { + this(testLoggingLevel, fixtureTracing, null); + } + public LogConfig( + final Level testLoggingLevel, + final PrintStream fixtureTracing, + final String loggingPropertyFile) { + this.testLoggingLevel = testLoggingLevel != null ? testLoggingLevel : INFO; + this.fixtureTracing = fixtureTracing != null ? fixtureTracing : System.out; + this.loggingPropertyFile = + loggingPropertyFile != null ? loggingPropertyFile : "logging-integtest.properties"; + } + + public Level getTestLoggingLevel() { + return testLoggingLevel; + } + + public PrintStream getFixtureTracing() { + return fixtureTracing; + } + + public String getLoggingPropertyFile() { + return loggingPropertyFile; + } +} diff --git a/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/logging/LogStream.java b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/logging/LogStream.java new file mode 100644 index 0000000..022d066 --- /dev/null +++ b/core/integtestsupport/src/main/java/org/apache/isis/core/integtestsupport/logging/LogStream.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.isis.core.integtestsupport.logging; + +import java.io.OutputStream; +import java.io.PrintStream; + +import org.slf4j.Logger; +import org.slf4j.event.Level; + +public class LogStream extends OutputStream { + + private final Logger logger; + private final Level level; + + private final StringBuilder buf = new StringBuilder(); + + public static PrintStream logPrintStream(final Logger logger, final Level level) { + return new PrintStream(new LogStream(logger, level)); + } + + public LogStream(final Logger logger, final Level level) { + this.logger = logger; + this.level = level; + } + + public void close() {} + + public void flush() { + final String message = toString(); + switch (level) { + case ERROR: + logger.error(message); + break; + case WARN: + logger.warn(message); + break; + case INFO: + logger.info(message); + break; + case DEBUG: + logger.debug(message); + break; + case TRACE: + logger.trace(message); + break; + } + + // Clear the buffer + buf.delete(0, buf.length()); + } + + public void write(byte[] b) { + String str = new String(b); + this.buf.append(str); + } + + public void write(byte[] b, int off, int len) { + String str = new String(b, off, len); + this.buf.append(str); + } + + public void write(int b) { + String str = Integer.toString(b); + this.buf.append(str); + } + + public String toString() { + return buf.toString(); + } +} diff --git a/core/integtestsupport/src/test/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3_haveSameModules_Test.java b/core/integtestsupport/src/test/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3_haveSameModules_Test.java new file mode 100644 index 0000000..44676e9 --- /dev/null +++ b/core/integtestsupport/src/test/java/org/apache/isis/core/integtestsupport/IntegrationTestAbstract3_haveSameModules_Test.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.isis.core.integtestsupport; + +import org.junit.Test; + +import org.apache.isis.applib.AppManifest; +import org.apache.isis.applib.AppManifestAbstract; + +import static junit.framework.TestCase.assertFalse; +import static org.junit.Assert.assertTrue; + +public class IntegrationTestAbstract3_haveSameModules_Test { + + public static class SomeModule{} + public static class OtherModule{} + + final AppManifest m1 = new AppManifestAbstract(AppManifestAbstract.Builder.forModules(SomeModule.class, OtherModule.class)) { + }; + final AppManifest m1_different_order = new AppManifestAbstract(AppManifestAbstract.Builder.forModules(OtherModule.class, SomeModule.class)) { + }; + final AppManifest m2 = new AppManifestAbstract(AppManifestAbstract.Builder.forModules(SomeModule.class, OtherModule.class)) { + }; + final AppManifest m3 = new AppManifestAbstract(AppManifestAbstract.Builder.forModules(SomeModule.class)) { + }; + + @Test + public void when_they_do() throws Exception { + + assertTrue(IntegrationTestAbstract3.haveSameModules(m1, m2)); + assertTrue(IntegrationTestAbstract3.haveSameModules(m1, m1_different_order)); + } + + @Test + public void when_they_dont() throws Exception { + assertFalse(IntegrationTestAbstract3.haveSameModules(m1, m3)); + } +} \ No newline at end of file -- To stop receiving notification emails like this one, please contact "commits@isis.apache.org" <commits@isis.apache.org>.