This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cayenne.git
commit 551609e6586b841b2081cd4e04fe9d0660a0ce79 Author: Andrus Adamchik <[email protected]> AuthorDate: Thu May 14 10:40:24 2026 -0400 tests cleanup - unwinding DI --- .../unit/di/runtime/CayenneRuntimeProvider.java | 103 --------------------- .../cayenne/unit/di/runtime/CayenneTestsEnv.java | 91 +++++++++++++++--- .../runtime/RuntimeCaseDataChannelInterceptor.java | 9 +- .../cayenne/unit/di/runtime/RuntimeCaseModule.java | 21 +---- 4 files changed, 82 insertions(+), 142 deletions(-) diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneRuntimeProvider.java b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneRuntimeProvider.java deleted file mode 100644 index 21c4c91f2..000000000 --- a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneRuntimeProvider.java +++ /dev/null @@ -1,103 +0,0 @@ -/***************************************************************** - * 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 - * - * https://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.cayenne.unit.di.runtime; - -import org.apache.cayenne.ConfigurationException; -import org.apache.cayenne.access.DataDomain; -import org.apache.cayenne.configuration.Constants; -import org.apache.cayenne.configuration.runtime.CoreModule; -import org.apache.cayenne.configuration.runtime.DataNodeFactory; -import org.apache.cayenne.dba.DbAdapter; -import org.apache.cayenne.di.Binder; -import org.apache.cayenne.di.Inject; -import org.apache.cayenne.di.Module; -import org.apache.cayenne.di.Provider; -import org.apache.cayenne.runtime.CayenneRuntime; -import org.apache.cayenne.unit.UnitDbAdapter; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - -public class CayenneRuntimeProvider implements Provider<CayenneRuntime> { - - private RuntimeCaseProperties properties; - private RuntimeCaseExtraModules extraModules; - private RuntimeCaseDataSourceFactory dataSourceFactory; - private UnitDbAdapter unitDbAdapter; - private Provider<DbAdapter> dbAdapterProvider; - - public CayenneRuntimeProvider(@Inject RuntimeCaseDataSourceFactory dataSourceFactory, - @Inject RuntimeCaseProperties properties, - @Inject RuntimeCaseExtraModules extraModules, - @Inject Provider<DbAdapter> dbAdapterProvider, - @Inject UnitDbAdapter unitDbAdapter) { - - this.dataSourceFactory = dataSourceFactory; - this.properties = properties; - this.extraModules = extraModules; - this.dbAdapterProvider = dbAdapterProvider; - this.unitDbAdapter = unitDbAdapter; - } - - @Override - public CayenneRuntime get() throws ConfigurationException { - - String configurationLocation = properties.getConfigurationLocation(); - if (configurationLocation == null) { - throw new NullPointerException("Null 'configurationLocation', " - + "annotate your test case with @UseCayenneRuntime"); - } - - Collection<Module> modules = new ArrayList<>(getExtraModules()); - modules.addAll(extraModules.getExtraModules()); - - return CayenneRuntime.builder() - .addConfig(configurationLocation) - .addModules(modules) - .build(); - } - - protected Collection<? extends Module> getExtraModules() { - return Collections.singleton(new ExtraModule()); - } - - class ExtraModule implements Module { - - @Override - public void configure(Binder binder) { - - // these are the objects overriding standard CoreModule definitions or - // dependencies needed by such overrides - - binder.bind(DbAdapter.class).toProviderInstance(dbAdapterProvider); - binder.bind(DataDomain.class).toProvider(RuntimeCaseDataDomainProvider.class); - binder.bind(DataNodeFactory.class).to(RuntimeCaseDataNodeFactory.class); - binder.bind(UnitDbAdapter.class).toInstance(unitDbAdapter); - - CoreModule.extend(binder) - // Use soft references instead of default weak. - // Should remove problems with random-failing tests (those that are GC-sensitive). - .setProperty(Constants.OBJECT_RETAIN_STRATEGY_PROPERTY, "soft"); - - // map DataSources for all test DataNode names - binder.bind(RuntimeCaseDataSourceFactory.class).toInstance(dataSourceFactory); - } - } -} diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneTestsEnv.java b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneTestsEnv.java index a19a6972e..8af819bde 100644 --- a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneTestsEnv.java +++ b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/CayenneTestsEnv.java @@ -25,13 +25,14 @@ import org.apache.cayenne.configuration.Constants; import org.apache.cayenne.configuration.DataMapLoader; import org.apache.cayenne.configuration.DataSourceDescriptor; import org.apache.cayenne.configuration.runtime.CoreModule; +import org.apache.cayenne.configuration.runtime.DataNodeFactory; import org.apache.cayenne.dba.DbAdapter; import org.apache.cayenne.di.AdhocObjectFactory; import org.apache.cayenne.di.Binder; import org.apache.cayenne.di.DIBootstrap; import org.apache.cayenne.di.Injector; import org.apache.cayenne.di.Module; -import org.apache.cayenne.di.spi.DefaultScope; +import org.apache.cayenne.di.Provider; import org.apache.cayenne.log.JdbcEventLogger; import org.apache.cayenne.map.DataMap; import org.apache.cayenne.map.EntityResolver; @@ -45,6 +46,8 @@ import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; +import java.util.ArrayList; +import java.util.List; import java.util.stream.Collectors; /** @@ -52,13 +55,11 @@ import java.util.stream.Collectors; */ public class CayenneTestsEnv implements BeforeEachCallback, AfterEachCallback { - private static final DefaultScope TEST_SCOPE; private static final Injector INJECTOR; public static final AllTestsSchemaManager SCHEMA_MANAGER; static { - TEST_SCOPE = new DefaultScope(); - INJECTOR = DIBootstrap.createInjector(new RuntimeCaseModule(TEST_SCOPE)); + INJECTOR = DIBootstrap.createInjector(new RuntimeCaseModule()); SCHEMA_MANAGER = new AllTestsSchemaManager( INJECTOR.getInstance(RuntimeCaseDataSourceFactory.class), INJECTOR.getInstance(UnitDbAdapter.class), @@ -73,6 +74,7 @@ public class CayenneTestsEnv implements BeforeEachCallback, AfterEachCallback { private final boolean autoClean; private final boolean weakReferenceStrategy; + // single-test scoped vars private DataContext context; private DbHelper dbHelper; private DbCleaner dbCleaner; @@ -116,17 +118,11 @@ public class CayenneTestsEnv implements BeforeEachCallback, AfterEachCallback { @Override public void beforeEach(ExtensionContext ctx) { - INJECTOR.getInstance(RuntimeCaseProperties.class).setConfigurationLocation(project); - - Class<?>[] effectiveExtras = extraModules; - if (weakReferenceStrategy) { - effectiveExtras = new Class<?>[extraModules.length + 1]; - System.arraycopy(extraModules, 0, effectiveExtras, 0, extraModules.length); - effectiveExtras[extraModules.length] = WeakReferenceStrategyModule.class; + if (project == null) { + throw new NullPointerException("Null 'project', annotate your test case with @UseCayenneRuntime"); } - INJECTOR.getInstance(RuntimeCaseExtraModules.class).setExtraModules(effectiveExtras); - this.runtime = INJECTOR.getInstance(CayenneRuntime.class); + this.runtime = buildRuntime(); this.context = (DataContext) runtime.newContext(); DataNode node = runtime.getDataDomain().getDataNodes().iterator().next(); @@ -148,13 +144,44 @@ public class CayenneTestsEnv implements BeforeEachCallback, AfterEachCallback { @Override public void afterEach(ExtensionContext ctx) { - TEST_SCOPE.shutdown(); + if (runtime != null) { + runtime.shutdown(); + } this.context = null; this.dbHelper = null; this.dbCleaner = null; this.runtime = null; } + private CayenneRuntime buildRuntime() { + RuntimeCaseDataSourceFactory dataSourceFactory = INJECTOR.getInstance(RuntimeCaseDataSourceFactory.class); + UnitDbAdapter unitDbAdapter = INJECTOR.getInstance(UnitDbAdapter.class); + // a fresh DbAdapter per call — RuntimeCaseDbAdapterProvider is unscoped in the test injector + Provider<DbAdapter> dbAdapterProvider = () -> INJECTOR.getInstance(DbAdapter.class); + + List<Module> modules = new ArrayList<>(); + modules.add(new TestRuntimeOverridesModule(dataSourceFactory, unitDbAdapter, dbAdapterProvider)); + for (Class<?> moduleType : extraModules) { + modules.add(instantiateModule(moduleType)); + } + if (weakReferenceStrategy) { + modules.add(new WeakReferenceStrategyModule()); + } + + return CayenneRuntime.builder() + .addConfig(project) + .addModules(modules) + .build(); + } + + private static Module instantiateModule(Class<?> moduleType) { + try { + return (Module) moduleType.getConstructor().newInstance(); + } catch (ReflectiveOperationException e) { + throw new RuntimeException("Failed to instantiate extra module: " + moduleType.getName(), e); + } + } + public DataContext context() { return context; } @@ -185,7 +212,7 @@ public class CayenneTestsEnv implements BeforeEachCallback, AfterEachCallback { } public DataChannelInterceptor dataChannelInterceptor() { - return INJECTOR.getInstance(DataChannelInterceptor.class); + return new RuntimeCaseDataChannelInterceptor(() -> runtime); } public AdhocObjectFactory adhocObjectFactory() { @@ -214,4 +241,38 @@ public class CayenneTestsEnv implements BeforeEachCallback, AfterEachCallback { CoreModule.extend(binder).setProperty(Constants.OBJECT_RETAIN_STRATEGY_PROPERTY, "weak"); } } + + /** + * Wires test-specific overrides into the per-test {@link CayenneRuntime} injector: + * shares the {@link RuntimeCaseDataSourceFactory} and {@link UnitDbAdapter} held by the + * static test injector, swaps in the test {@link DbAdapter} and data-domain/node + * implementations, and sets the soft-reference retain strategy. + */ + private static class TestRuntimeOverridesModule implements Module { + + private final RuntimeCaseDataSourceFactory dataSourceFactory; + private final UnitDbAdapter unitDbAdapter; + private final Provider<DbAdapter> dbAdapterProvider; + + TestRuntimeOverridesModule(RuntimeCaseDataSourceFactory dataSourceFactory, + UnitDbAdapter unitDbAdapter, + Provider<DbAdapter> dbAdapterProvider) { + this.dataSourceFactory = dataSourceFactory; + this.unitDbAdapter = unitDbAdapter; + this.dbAdapterProvider = dbAdapterProvider; + } + + @Override + public void configure(Binder binder) { + binder.bind(DbAdapter.class).toProviderInstance(dbAdapterProvider); + binder.bind(DataDomain.class).toProvider(RuntimeCaseDataDomainProvider.class); + binder.bind(DataNodeFactory.class).to(RuntimeCaseDataNodeFactory.class); + binder.bind(UnitDbAdapter.class).toInstance(unitDbAdapter); + binder.bind(RuntimeCaseDataSourceFactory.class).toInstance(dataSourceFactory); + + CoreModule.extend(binder) + // Soft refs instead of weak — avoids GC-sensitive test flakiness. + .setProperty(Constants.OBJECT_RETAIN_STRATEGY_PROPERTY, "soft"); + } + } } diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseDataChannelInterceptor.java b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseDataChannelInterceptor.java index 4efe8434e..729cb1144 100644 --- a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseDataChannelInterceptor.java +++ b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseDataChannelInterceptor.java @@ -19,7 +19,6 @@ package org.apache.cayenne.unit.di.runtime; import org.apache.cayenne.access.UnitTestDomain; -import org.apache.cayenne.di.Inject; import org.apache.cayenne.di.Provider; import org.apache.cayenne.runtime.CayenneRuntime; import org.apache.cayenne.unit.di.DataChannelInterceptor; @@ -28,9 +27,11 @@ import org.apache.cayenne.unit.di.UnitTestClosure; public class RuntimeCaseDataChannelInterceptor implements DataChannelInterceptor { - @Inject - // injecting provider to make this provider independent from scoping of CayenneRuntime - protected Provider<CayenneRuntime> runtimeProvider; + private final Provider<CayenneRuntime> runtimeProvider; + + public RuntimeCaseDataChannelInterceptor(Provider<CayenneRuntime> runtimeProvider) { + this.runtimeProvider = runtimeProvider; + } public void runWithQueriesBlocked(UnitTestClosure closure) { diff --git a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseModule.java b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseModule.java index c7708fe33..f2697948c 100644 --- a/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseModule.java +++ b/cayenne/src/test/java/org/apache/cayenne/unit/di/runtime/RuntimeCaseModule.java @@ -76,14 +76,12 @@ import org.apache.cayenne.di.Module; import org.apache.cayenne.di.Provider; import org.apache.cayenne.di.spi.DefaultAdhocObjectFactory; import org.apache.cayenne.di.spi.DefaultClassLoaderManager; -import org.apache.cayenne.di.spi.DefaultScope; import org.apache.cayenne.log.JdbcEventLogger; import org.apache.cayenne.log.Slf4jJdbcEventLogger; import org.apache.cayenne.reflect.generic.DefaultValueComparisonStrategyFactory; import org.apache.cayenne.reflect.generic.ValueComparisonStrategyFactory; import org.apache.cayenne.resource.ClassLoaderResourceLocator; import org.apache.cayenne.resource.ResourceLocator; -import org.apache.cayenne.runtime.CayenneRuntime; import org.apache.cayenne.unit.DB2UnitDbAdapter; import org.apache.cayenne.unit.DerbyUnitDbAdapter; import org.apache.cayenne.unit.FirebirdUnitDbAdapter; @@ -99,7 +97,6 @@ import org.apache.cayenne.unit.SQLiteUnitDbAdapter; import org.apache.cayenne.unit.SybaseUnitDbAdapter; import org.apache.cayenne.unit.UnitDataSourceDescriptor; import org.apache.cayenne.unit.UnitDbAdapter; -import org.apache.cayenne.unit.di.DataChannelInterceptor; import org.apache.cayenne.unit.testcontainers.Db2ContainerProvider; import org.apache.cayenne.unit.testcontainers.MariaDbContainerProvider; import org.apache.cayenne.unit.testcontainers.MysqlContainerProvider; @@ -115,19 +112,9 @@ import java.util.GregorianCalendar; public class RuntimeCaseModule implements Module { - protected DefaultScope testScope; - - public RuntimeCaseModule(DefaultScope testScope) { - this.testScope = testScope; - } - public void configure(Binder binder) { - // these are the objects injectable in unit tests that subclass from - // RuntimeCase. Note that CayenneRuntimeProvider creates CayenneRuntime - // instances complete with their own DI injectors, independent of the - // unit test injector. CayenneRuntime injector contents are customized - // inside CayenneRuntimeProvider. + // these are the objects injectable in unit tests that subclass from RuntimeCase. binder.bindMap(String.class, UnitDbAdapterProvider.TEST_ADAPTERS_MAP) .put(FirebirdAdapter.class.getName(), FirebirdUnitDbAdapter.class.getName()) @@ -231,7 +218,6 @@ public class RuntimeCaseModule implements Module { // server runtime... BatchQueryBuilderFactory is hardcoded and whatever is placed // in the CoreModule is ignored binder.bind(BatchTranslatorFactory.class).toProvider(RuntimeCaseBatchQueryBuilderFactoryProvider.class); - binder.bind(DataChannelInterceptor.class).to(RuntimeCaseDataChannelInterceptor.class); binder.bind(SQLTemplateCustomizer.class).toProvider(SQLTemplateCustomizerProvider.class); binder.bind(RuntimeCaseDataSourceFactory.class).to(RuntimeCaseDataSourceFactory.class); binder.bind(ClassLoaderManager.class).to(DefaultClassLoaderManager.class); @@ -245,11 +231,6 @@ public class RuntimeCaseModule implements Module { binder.bind(DataChannelMetaData.class).to(NoopDataChannelMetaData.class); binder.bind(XMLReader.class).toProviderInstance(new XMLReaderProvider(false)).withoutScope(); - - // test-scoped objects - binder.bind(RuntimeCaseProperties.class).to(RuntimeCaseProperties.class).in(testScope); - binder.bind(RuntimeCaseExtraModules.class).to(RuntimeCaseExtraModules.class).in(testScope); - binder.bind(CayenneRuntime.class).toProvider(CayenneRuntimeProvider.class).in(testScope); } // this class exists so that ToolsModule can call "initAllExtensions()" that is protected in CoreModuleExtender.
