11// SPDX-FileCopyrightText: Copyright 2003 Ryan Baldwin
22// SPDX-FileCopyrightText: Copyright 2003-2010 Roman Rokytskyy
3- // SPDX-FileCopyrightText: Copyright 2012-2025 Mark Rotteveel
3+ // SPDX-FileCopyrightText: Copyright 2012-2026 Mark Rotteveel
44// SPDX-License-Identifier: LGPL-2.1-or-later
55package org .firebirdsql .common ;
66
3737import java .sql .SQLException ;
3838import java .util .Map ;
3939import java .util .MissingResourceException ;
40+ import java .util .Optional ;
4041import java .util .Properties ;
4142import java .util .ResourceBundle ;
4243
44+ import static java .util .Objects .requireNonNullElse ;
45+ import static org .firebirdsql .common .PathUtils .posixPathString ;
4346import static org .firebirdsql .common .matchers .GdsTypeMatchers .isEmbeddedType ;
4447import static org .firebirdsql .common .matchers .GdsTypeMatchers .isOtherNativeType ;
4548import static org .firebirdsql .common .matchers .GdsTypeMatchers .isPureJavaType ;
4649import static org .firebirdsql .jaybird .util .StringUtils .trimToNull ;
4750import static org .hamcrest .Matchers .not ;
4851
4952/**
50- * Helper class for test properties (database user, password, paths etc)
53+ * Helper class for test properties (database user, password, paths etc).
5154 */
5255public final class FBTestProperties {
5356
@@ -83,10 +86,15 @@ public static String getProperty(String property, String defaultValue) {
8386 public static final String DB_USER = getProperty ("test.user" , "sysdba" );
8487 public static final String DB_PASSWORD = getProperty ("test.password" , "masterkey" );
8588 public static final String DB_PATH = getProperty ("test.db.dir" , "" );
89+ private static final Path DB_DIR_PATH ;
90+ @ SuppressWarnings ("OptionalUsedAsFieldOrParameterType" )
91+ private static final Optional <Path > DB_MAPPED_LOCAL_PATH =
92+ Optional .ofNullable (trimToNull (getProperty ("test.db.mapped" ))).map (Path ::of );
8693 public static final String DB_SERVER_URL = getProperty ("test.db.host" , "localhost" );
8794 public static final int DB_SERVER_PORT = Integer .parseInt (getProperty ("test.db.port" , "3050" ));
8895 public static final String DB_LC_CTYPE = getProperty ("test.db.lc_ctype" , "NONE" );
8996 public static final boolean DB_ON_DOCKER = Boolean .parseBoolean (getProperty ("test.db_on_docker" , "false" ));
97+ private static final @ Nullable Boolean EVENTS_AVAILABLE ;
9098 public static final String GDS_TYPE = getProperty ("test.gds_type" , "PURE_JAVA" );
9199 public static final boolean USE_FIREBIRD_AUTOCOMMIT =
92100 Boolean .parseBoolean (getProperty ("test.use_firebird_autocommit" , "false" ));
@@ -96,39 +104,149 @@ public static String getProperty(String property, String defaultValue) {
96104 Boolean .parseBoolean (getProperty ("test.native_legacy_auth_compat" , "false" ));
97105 private static final String NATIVE_LEGACY_AUTH_COMPAT_AUTH_PLUGINS = "Legacy_Auth" ;
98106
107+ static {
108+ var dbDirPath = Path .of (DB_PATH .isEmpty () ? "." : DB_PATH );
109+ if (isSameHostServer ()) {
110+ dbDirPath = dbDirPath .toAbsolutePath ();
111+ }
112+ DB_DIR_PATH = dbDirPath ;
113+
114+ String eventsAvailable = trimToNull (getProperty ("test.event.available" ));
115+ EVENTS_AVAILABLE = eventsAvailable != null ? Boolean .valueOf (eventsAvailable ) : null ;
116+ }
117+
99118 public static boolean isLocalhost () {
100- return "localhost" .equals (DB_SERVER_URL ) || "127.0.0.1" .equals (DB_SERVER_URL );
119+ return "localhost" .equalsIgnoreCase (DB_SERVER_URL )
120+ || "127.0.0.1" .equals (DB_SERVER_URL )
121+ || "::1" .equals (DB_SERVER_URL );
122+ }
123+
124+ public static boolean isDbOnDocker () {
125+ return DB_ON_DOCKER ;
101126 }
102127
103128 public static boolean isDefaultPort () {
104129 return DB_SERVER_PORT == PropertyConstants .DEFAULT_PORT ;
105130 }
106131
132+ public static boolean isEventPortAvailable () {
133+ return requireNonNullElse (EVENTS_AVAILABLE , !DB_ON_DOCKER );
134+ }
135+
107136 public static String getDatabasePath () {
108137 return getDatabasePath (DB_NAME );
109138 }
110139
111140 public static String getDatabasePath (String name ) {
112- if (not (isEmbeddedType ()).matches (GDS_TYPE ) && (!isLocalhost () || DB_ON_DOCKER )) {
113- return DB_PATH + "/" + name ;
114- }
115- return Path .of (DB_PATH , name ).toAbsolutePath ().toString ();
141+ return getDatabasePath (Path .of (DB_PATH , name ));
142+ }
143+
144+ public static String getDatabasePath (Path path ) {
145+ return isSameHostServer () ? path .toString () : posixPathString (path );
146+ }
147+
148+ /**
149+ * Check if database paths and Firebird server (or embedded) is truly local.
150+ * <p>
151+ * Specifically, it means that the server accesses paths on the local filesystem of this machine and that the return
152+ * values of {@link #getDatabasePath()} and {@link #getDatabasePath(String)} are local to this machine.
153+ * </p>
154+ *
155+ * @return {@code true} if server has direct access to the filesystem of this host
156+ * @see #hasMappedDatabaseDirectory()
157+ */
158+ public static boolean isSameHostServer () {
159+ return isEmbeddedType ().matches (GDS_TYPE ) || (isLocalhost () && !isDbOnDocker ());
160+ }
161+
162+ /**
163+ * Check if database files are locally accessible to the tests.
164+ * <p>
165+ * Specifically, it means that either the server has direct filesystem access (i.e. {@link #isSameHostServer()}
166+ * returns {@code true}), or the directory specified in system property {@code test.db.dir} is mapped (e.g. as a
167+ * volume, or network share) in the directory specified in system property {@code test.db.mapped}, and
168+ * {@link #getMappedDatabaseDirectory()}, {@link #getMappedDatabasePath()} and {@link #getMappedDatabasePath(String)} return
169+ * non-empty.
170+ * </p>
171+ * <p>
172+ * Contrary to {@link #isSameHostServer()}, this method returning {@code true} does not necessarily mean that the
173+ * Firebird server has direct filesystem access to this machine, nor that it would recognize paths as returned by
174+ * the {@code getLocalDatabase...} methods.
175+ * </p>
176+ *
177+ * @return {@code true} if database files are locally accessible
178+ */
179+ public static boolean hasMappedDatabaseDirectory () {
180+ return isSameHostServer () || DB_MAPPED_LOCAL_PATH .isPresent ();
116181 }
117182
118183 /**
119184 * Builds a firebird database connection string for the supplied database file.
120- *
121- * @param name Database name
185+ *
186+ * @param name
187+ * Database name
122188 * @return URL or path for the gds type.
123189 */
124190 public static String getdbpath (String name ) {
191+ String databasePath = getDatabasePath (name );
125192 if (isEmbeddedType ().matches (GDS_TYPE )) {
126- return Path . of ( DB_PATH , name ). toAbsolutePath (). toString () ;
193+ return databasePath ;
127194 } else {
128- return DB_SERVER_URL + "/" + DB_SERVER_PORT + ":" + getDatabasePath ( name ) ;
195+ return DB_SERVER_URL + "/" + DB_SERVER_PORT + ":" + databasePath ;
129196 }
130197 }
131198
199+ /**
200+ * The mapped database directory.
201+ * <p>
202+ * For local databases, this is the normal database directory ({@code test.db.dir}). For remote databases, or
203+ * databases on Docker, this can be configured using the {@code test.db.mapped} system property. For remote
204+ * databases, this can be the local mount of a network share, or for databases on Docker, the host directory backing
205+ * the volume.
206+ * </p>
207+ *
208+ * @return mapped database directory, or empty
209+ */
210+ public static Optional <Path > getMappedDatabaseDirectory () {
211+ return isSameHostServer () ? Optional .of (DB_DIR_PATH ) : DB_MAPPED_LOCAL_PATH ;
212+ }
213+
214+ /**
215+ * The database name {@code name} resolved against {@link #getMappedDatabaseDirectory()}.
216+ * <p>
217+ * The {@code name} expects a relative path; absolute paths will likely not work unless {@link #isSameHostServer()}
218+ * is {@code true}).
219+ * </p>
220+ *
221+ * @param name
222+ * database name (or other file)
223+ * @return mapped database file path, or empty ({@link #hasMappedDatabaseDirectory()} returns {@code false})
224+ */
225+ public static Optional <Path > getMappedDatabasePath (String name ) {
226+ return getMappedDatabaseDirectory ().map (p -> p .resolve (name ));
227+ }
228+
229+ /**
230+ * The database name of the default test database.
231+ *
232+ * @return mapped database file path, or empty ({@link #hasMappedDatabaseDirectory()} returns {@code false})
233+ */
234+ public static Optional <Path > getMappedDatabasePath () {
235+ return getMappedDatabasePath (DB_NAME );
236+ }
237+
238+ /**
239+ * Transforms (remaps) the mapped database path to the server-side database path.
240+ *
241+ * @param mappedPath
242+ * mapped path (i.e. relative to {@link #getMappedDatabaseDirectory()})
243+ * @return database file path
244+ */
245+ public static Optional <Path > transformMappedToDatabasePath (Path mappedPath ) {
246+ return getMappedDatabaseDirectory ()
247+ .map (mappedDir -> DB_DIR_PATH .resolve (mappedDir .relativize (mappedPath )));
248+ }
249+
132250 /**
133251 * @return Default database connection properties for this testrun
134252 */
@@ -293,14 +411,25 @@ public static String getUrl(String dbPath) {
293411 }
294412
295413 /**
296- * Convenience method equivalent to {@code getUrl(dbPath.toAbsolutePath().toString( ))}.
414+ * Convenience method equivalent to {@code getUrl(getDatabasePath(dbPath ))}.
297415 *
298416 * @param dbPath
299417 * path of the database
300418 * @return JDBC URL (without parameters) for this test run
301419 */
302420 public static String getUrl (Path dbPath ) {
303- return getUrl (dbPath .toAbsolutePath ().toString ());
421+ return getUrl (getDatabasePath (dbPath ));
422+ }
423+
424+ /**
425+ * Convenience method equivalent to {@code getUrl(mappedPath.toServerPath())}.
426+ *
427+ * @param mappedPath
428+ * path of the database
429+ * @return JDBC URL (without parameters) for this test run
430+ */
431+ public static String getUrl (MappedPath mappedPath ) {
432+ return getUrl (mappedPath .toServerPath ());
304433 }
305434
306435 // FACTORY METHODS
0 commit comments