1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package de.softwareforge.testing.postgres.junit5;
16
17 import static com.google.common.base.Preconditions.checkNotNull;
18 import static com.google.common.base.Preconditions.checkState;
19
20 import de.softwareforge.testing.postgres.embedded.DatabaseInfo;
21 import de.softwareforge.testing.postgres.embedded.DatabaseManager;
22 import de.softwareforge.testing.postgres.embedded.DatabaseManager.DatabaseManagerBuilder;
23 import de.softwareforge.testing.postgres.embedded.EmbeddedPostgres;
24
25 import jakarta.annotation.Nonnull;
26 import java.lang.reflect.Type;
27 import java.sql.SQLException;
28 import java.util.UUID;
29 import javax.sql.DataSource;
30
31 import com.google.common.annotations.VisibleForTesting;
32 import org.junit.jupiter.api.extension.AfterAllCallback;
33 import org.junit.jupiter.api.extension.AfterEachCallback;
34 import org.junit.jupiter.api.extension.BeforeAllCallback;
35 import org.junit.jupiter.api.extension.BeforeEachCallback;
36 import org.junit.jupiter.api.extension.ExtensionContext;
37 import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
38 import org.junit.jupiter.api.extension.ExtensionContext.Store;
39 import org.junit.jupiter.api.extension.ParameterContext;
40 import org.junit.jupiter.api.extension.ParameterResolutionException;
41 import org.junit.jupiter.api.extension.ParameterResolver;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45
46
47
48
49
50
51
52
53
54
55 public final class EmbeddedPgExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, AfterEachCallback, ParameterResolver {
56
57 private static final Logger LOG = LoggerFactory.getLogger(EmbeddedPgExtension.class);
58
59
60 private final Namespace pgNamespace = Namespace.create(UUID.randomUUID());
61
62 private final DatabaseManager.Builder<DatabaseManager> databaseManagerBuilder;
63
64 private volatile DatabaseManager databaseManager = null;
65
66 private EmbeddedPgExtension(DatabaseManager.Builder<DatabaseManager> databaseManagerBuilder) {
67 this.databaseManagerBuilder = databaseManagerBuilder;
68 }
69
70
71
72
73
74
75
76 @Nonnull
77 static EmbeddedPgExtensionBuilder multiDatabase() {
78 return new EmbeddedPgExtensionBuilder(true);
79 }
80
81
82
83
84
85
86
87 @Nonnull
88 static EmbeddedPgExtensionBuilder singleDatabase() {
89 return new EmbeddedPgExtensionBuilder(false);
90 }
91
92
93
94
95
96
97
98
99
100
101
102 public EmbeddedPgExtension() {
103 this(new DatabaseManagerBuilder(true).withInstancePreparer(EmbeddedPostgres.Builder::withDefaults));
104 }
105
106
107
108
109
110
111
112
113 @Nonnull
114 public DataSource createDataSource() throws SQLException {
115 return createDatabaseInfo().asDataSource();
116 }
117
118 @VisibleForTesting
119 EmbeddedPostgres getEmbeddedPostgres() {
120 return databaseManager.getEmbeddedPostgres();
121 }
122
123
124
125
126
127
128
129
130
131 @Nonnull
132 public DatabaseInfo createDatabaseInfo() throws SQLException {
133 checkState(databaseManager != null, "no before method has been called!");
134
135 DatabaseInfo databaseInfo = databaseManager.getDatabaseInfo();
136 LOG.info("Connection to {}", databaseInfo.asJdbcUrl());
137 return databaseInfo;
138 }
139
140 @Override
141 public void beforeAll(@Nonnull ExtensionContext extensionContext) throws Exception {
142 checkNotNull(extensionContext, "extensionContext is null");
143
144 Store pgStore = extensionContext.getStore(pgNamespace);
145
146 TestingContext testingContext = pgStore.getOrComputeIfAbsent(TestingContext.TESTING_CONTEXT_KEY,
147 k -> new TestingContext(extensionContext.getUniqueId(), databaseManagerBuilder.build()),
148 TestingContext.class);
149
150 this.databaseManager = testingContext.start(extensionContext.getUniqueId());
151 }
152
153 @Override
154 public void afterAll(@Nonnull ExtensionContext extensionContext) throws Exception {
155 checkNotNull(extensionContext, "extensionContext is null");
156
157 Store pgStore = extensionContext.getStore(pgNamespace);
158 TestingContext testingContext = pgStore.get(TestingContext.TESTING_CONTEXT_KEY, TestingContext.class);
159
160 if (testingContext != null) {
161 this.databaseManager = testingContext.stop(extensionContext.getUniqueId());
162 }
163 }
164
165 @Override
166 public void beforeEach(@Nonnull ExtensionContext extensionContext) throws Exception {
167 checkNotNull(extensionContext, "extensionContext is null");
168
169 Store pgStore = extensionContext.getStore(pgNamespace);
170 TestingContext testingContext = pgStore.getOrComputeIfAbsent(TestingContext.TESTING_CONTEXT_KEY,
171 k -> new TestingContext(extensionContext.getUniqueId(), databaseManagerBuilder.build()),
172 TestingContext.class);
173
174 this.databaseManager = testingContext.start(extensionContext.getUniqueId());
175 }
176
177 @Override
178 public void afterEach(@Nonnull ExtensionContext extensionContext) throws Exception {
179 checkNotNull(extensionContext, "extensionContext is null");
180
181 Store pgStore = extensionContext.getStore(pgNamespace);
182 TestingContext testingContext = pgStore.get(TestingContext.TESTING_CONTEXT_KEY, TestingContext.class);
183
184 if (testingContext != null) {
185 this.databaseManager = testingContext.stop(extensionContext.getUniqueId());
186 }
187 }
188
189 @Override
190 public boolean supportsParameter(@Nonnull ParameterContext parameterContext, ExtensionContext extensionContext) {
191 Type type = parameterContext.getParameter().getType();
192 return type == EmbeddedPostgres.class
193 || type == DatabaseInfo.class
194 || type == DataSource.class;
195 }
196
197 @Override
198 public Object resolveParameter(@Nonnull ParameterContext parameterContext, ExtensionContext extensionContext) {
199 Type type = parameterContext.getParameter().getType();
200 try {
201 if (type == EmbeddedPostgres.class) {
202 return getEmbeddedPostgres();
203 } else if (type == DatabaseInfo.class) {
204 return createDatabaseInfo();
205
206 } else if (type == DataSource.class) {
207 return createDataSource();
208 }
209 } catch (SQLException e) {
210 throw new ParameterResolutionException("Could not create " + type.getTypeName() + " instance", e);
211 }
212 return null;
213 }
214
215
216
217
218 public static final class EmbeddedPgExtensionBuilder extends DatabaseManager.Builder<EmbeddedPgExtension> {
219
220 private EmbeddedPgExtensionBuilder(boolean multiMode) {
221 super(multiMode);
222 }
223
224
225
226
227
228
229 @Override
230 @Nonnull
231 public EmbeddedPgExtension build() {
232 DatabaseManager.Builder<DatabaseManager> databaseManagerBuilder = new DatabaseManagerBuilder(multiMode)
233 .withDatabasePreparers(databasePreparers.build())
234 .withInstancePreparers(instancePreparers.build());
235
236 return new EmbeddedPgExtension(databaseManagerBuilder);
237 }
238 }
239
240 private static final class TestingContext {
241
242 private static final Object TESTING_CONTEXT_KEY = new Object();
243
244 private final String id;
245 private final DatabaseManager databaseManager;
246
247 private TestingContext(String id, DatabaseManager databaseManager) {
248 this.id = id;
249 this.databaseManager = databaseManager;
250 }
251
252 private DatabaseManager start(String id) throws Exception {
253 if (this.id.equals(id)) {
254 databaseManager.start();
255 }
256
257 return databaseManager;
258 }
259
260 private DatabaseManager stop(String id) throws Exception {
261 if (this.id.equals(id)) {
262 databaseManager.close();
263 return null;
264 }
265
266 return databaseManager;
267 }
268 }
269 }