View Javadoc
1   /*
2    *
3    * The DbUnit Database Testing Framework
4    * Copyright (C)2002-2026, DbUnit.org
5    *
6    * This library is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 2.1 of the License, or (at your option) any later version.
10   *
11   * This library is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this library; if not, write to the Free Software
18   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   *
20   */
21  package org.dbunit.junit.jupiter;
22  
23  import static org.assertj.core.api.Assertions.assertThat;
24  import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
25  
26  import java.sql.Statement;
27  
28  import org.dbunit.DataSourceDatabaseTester;
29  import org.dbunit.DatabaseEnvironment;
30  import org.dbunit.IDatabaseTester;
31  import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;
32  import org.dbunit.database.DatabaseConfig;
33  import org.dbunit.database.rowcount.ClearRowCountCheckSystemProperties;
34  import org.dbunit.operation.DatabaseOperation;
35  import org.junit.jupiter.api.BeforeEach;
36  import org.junit.jupiter.api.Test;
37  import org.junit.jupiter.api.extension.ExtendWith;
38  import org.junit.platform.testkit.engine.EngineTestKit;
39  
40  /**
41   * Real-database integration test that the annotation runtime never holds more database
42   * connections at once than it needs: the executor's own memoized connection (for the row count
43   * check, or - on the classic path - the eagerly-resolved baseline probe) plus at most one the
44   * tester opens for an operation. A pool of two always suffices; the executor never reaches for a
45   * third.
46   *
47   * <p>The classic path holds its baseline connection until {@code afterTest()} the same way the
48   * annotation paths do - it must not close a connection a fixed-connection tester would hand
49   * straight to {@code onSetup()} - so a bounded pool needs capacity for two even with the row
50   * count check disabled.
51   *
52   * <p>Uses {@link CountingDataSource} with a hard cap so a runtime that tries to hold too many
53   * fails fast rather than deadlocking. Matrix rows 1 & 2 under pool pressure (G-c3).
54   */
55  @ClearRowCountCheckSystemProperties
56  class DbUnitExtensionBoundedPoolIT
57  {
58      private static final String TEST_TABLE = "TEST_TABLE";
59  
60      @Test
61      void testAfterTestExecution_classicPathRowCountCheckOnPoolOfTwo_succeedsHoldingAtMostTwo()
62              throws Exception
63      {
64          final DatabaseEnvironment environment = DatabaseEnvironment.getInstance();
65          final CountingDataSource pool = new CountingDataSource(environment.getProfile(), 2);
66          deleteAllRowsQuietly(environment);
67          try
68          {
69              ClassicRowCountCheckSample.pool = pool;
70              ClassicRowCountCheckSample.schema = environment.getProfile().getSchema();
71  
72              EngineTestKit.engine("junit-jupiter")
73                      .selectors(selectClass(ClassicRowCountCheckSample.class)).execute()
74                      .testEvents().assertStatistics(stats -> stats.started(1).succeeded(1));
75  
76              assertThat(pool.peakConcurrent())
77                      .as("The classic path with the row count check enabled holds its baseline"
78                              + " connection through onSetup() (which opens a second) and the"
79                              + " later verify - a pool of two must be enough, and must actually"
80                              + " be used.")
81                      .isEqualTo(2);
82          } finally
83          {
84              deleteAllRowsQuietly(environment);
85              environment.closeConnection();
86              assertThat(pool.leaked())
87                      .as("Every connection the run opened must have been closed.").isZero();
88          }
89      }
90  
91      @Test
92      void testAfterTestExecution_classicPathRowCountCheckOffPoolOfTwo_succeedsHoldingAtMostTwo()
93              throws Exception
94      {
95          final DatabaseEnvironment environment = DatabaseEnvironment.getInstance();
96          final CountingDataSource pool = new CountingDataSource(environment.getProfile(), 2);
97          deleteAllRowsQuietly(environment);
98          try
99          {
100             ClassicNoRowCountCheckSample.pool = pool;
101             ClassicNoRowCountCheckSample.schema = environment.getProfile().getSchema();
102 
103             EngineTestKit.engine("junit-jupiter")
104                     .selectors(selectClass(ClassicNoRowCountCheckSample.class)).execute()
105                     .testEvents().assertStatistics(stats -> stats.started(1).succeeded(1));
106 
107             assertThat(pool.peakConcurrent())
108                     .as("Even with the row count check disabled, the classic path holds its"
109                             + " eagerly-resolved baseline connection through onSetup() (which"
110                             + " opens a second) rather than closing a connection a"
111                             + " fixed-connection tester would hand straight to onSetup() - a pool"
112                             + " of two must be enough, and is actually used.")
113                     .isEqualTo(2);
114         } finally
115         {
116             deleteAllRowsQuietly(environment);
117             environment.closeConnection();
118             assertThat(pool.leaked())
119                     .as("Every connection the run opened must have been closed.").isZero();
120         }
121     }
122 
123     private static void deleteAllRowsQuietly(final DatabaseEnvironment environment)
124     {
125         try (Statement statement =
126                 environment.getConnection().getConnection().createStatement())
127         {
128             statement.execute("DELETE FROM " + TEST_TABLE);
129         } catch (final Exception e)
130         {
131             // best-effort cleanup only; a failure here must not fail the test that already ran
132         }
133     }
134 
135     // The classic path: a bare, unannotated, non-static IDatabaseTester field (the 3.5.0
136     // lifecycle-only style). It must be non-static for the extension's field auto-scan, so it is
137     // built in the constructor from the static pool/schema the enclosing test sets.
138 
139     @ExtendWith(DbUnitExtension.class)
140     static class ClassicRowCountCheckSample
141     {
142         static CountingDataSource pool;
143         static String schema;
144 
145         final IDatabaseTester databaseTester;
146 
147         ClassicRowCountCheckSample()
148         {
149             final DatabaseConfig rowCountCheckOn = new DatabaseConfig();
150             rowCountCheckOn.setFeature(DatabaseConfig.FEATURE_ROW_COUNT_CHECK, true);
151             databaseTester = new DataSourceDatabaseTester(pool, schema, null, rowCountCheckOn);
152         }
153 
154         @BeforeEach
155         void configureLifecycle() throws Exception
156         {
157             databaseTester.setDataSet(new FlatXmlDataSetBuilder()
158                     .build(getClass().getResource("annotation-it-prep.xml")));
159             databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
160             databaseTester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
161         }
162 
163         @Test
164         void runsTheFullLifecycle()
165         {
166             // The connection accounting is the assertion - see the enclosing test. The
167             // @BeforeEach dataset + CLEAN_INSERT setup + DELETE_ALL teardown (+ row count
168             // check) is a complete lifecycle without the body needing to touch the database.
169         }
170     }
171 
172     @ExtendWith(DbUnitExtension.class)
173     static class ClassicNoRowCountCheckSample
174     {
175         static CountingDataSource pool;
176         static String schema;
177 
178         final IDatabaseTester databaseTester;
179 
180         ClassicNoRowCountCheckSample()
181         {
182             databaseTester = new DataSourceDatabaseTester(pool, schema);
183         }
184 
185         @BeforeEach
186         void configureLifecycle() throws Exception
187         {
188             databaseTester.setDataSet(new FlatXmlDataSetBuilder()
189                     .build(getClass().getResource("annotation-it-prep.xml")));
190             databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
191             databaseTester.setTearDownOperation(DatabaseOperation.DELETE_ALL);
192         }
193 
194         @Test
195         void runsTheFullLifecycle()
196         {
197             // The connection accounting is the assertion - see the enclosing test. The
198             // @BeforeEach dataset + CLEAN_INSERT setup + DELETE_ALL teardown (+ row count
199             // check) is a complete lifecycle without the body needing to touch the database.
200         }
201     }
202 
203 }