From 0184064ad590091a413c2ea9c2270efce3a18734 Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Wed, 21 Oct 2026 23:19:13 +0200 Subject: [PATCH] fix(conformance): fail fast when the suite is not on libSQL --- .../conformance/LibsqlConformanceGuard.java | 73 ++++++++++++ .../LibsqlConformanceGuardTest.java | 108 ++++++++++++++++++ .../XerialLauncherSessionListener.java | 1 + 3 files changed, 182 insertions(+) create mode 100644 libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/LibsqlConformanceGuard.java create mode 100644 libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/LibsqlConformanceGuardTest.java diff --git a/libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/LibsqlConformanceGuard.java b/libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/LibsqlConformanceGuard.java new file mode 100644 index 0000000..ebce89b --- /dev/null +++ b/libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/LibsqlConformanceGuard.java @@ -0,0 +1,73 @@ +package com.github.magnusp.libsql.conformance; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +final class LibsqlConformanceGuard { + + static final String EXPECTED_DRIVER_NAME = "libSQL JDBC Driver"; + static final String EXPECTED_DATABASE_PRODUCT_NAME = "SQLite (libSQL)"; + static final String LIBSQL_SOURCE_ID_SUFFIX = "alt1"; + + private static final String PROBE_URL = "jdbc:sqlite::memory:"; + + private LibsqlConformanceGuard() {} + + static void verify() { + try (Connection connection = DriverManager.getConnection(PROBE_URL)) { + verify(connection); + } catch (SQLException e) { + throw new IllegalStateException( + "Conformance suite could not obtain a JDBC connection from " + PROBE_URL, e); + } + } + + static void verify(Connection connection) throws SQLException { + DatabaseMetaData metadata = connection.getMetaData(); + String driverName = metadata.getDriverName(); + String productName = metadata.getDatabaseProductName(); + + if (!EXPECTED_DRIVER_NAME.equals(driverName) + || !EXPECTED_DATABASE_PRODUCT_NAME.equals(productName)) { + throw new AssertionError( + "Conformance suite is not connected to libSQL: expected driver \"" + + EXPECTED_DRIVER_NAME + + "\" and database product \"" + + EXPECTED_DATABASE_PRODUCT_NAME + + "\", but resolved driver \"" + + driverName + + "\" and database product \"" + + productName + + "\""); + } + + String sourceId = readSourceId(connection); + if (sourceId == null || !sourceId.endsWith(LIBSQL_SOURCE_ID_SUFFIX)) { + throw new AssertionError( + "Conformance suite is not connected to a libSQL server: expected SQLite" + + " source id ending \"" + + LIBSQL_SOURCE_ID_SUFFIX + + "\", but resolved \"" + + sourceId + + "\" through driver \"" + + driverName + + "\""); + } + } + + private static String readSourceId(Connection connection) throws SQLException { + try (Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery("SELECT sqlite_source_id()")) { + if (!resultSet.next()) { + throw new AssertionError( + "Conformance suite could not read a SQLite source id from " + + "the libSQL connection"); + } + return resultSet.getString(1); + } + } +} diff --git a/libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/LibsqlConformanceGuardTest.java b/libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/LibsqlConformanceGuardTest.java new file mode 100644 index 0000000..9a237fb --- /dev/null +++ b/libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/LibsqlConformanceGuardTest.java @@ -0,0 +1,108 @@ +package com.github.magnusp.libsql.conformance; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.Statement; +import org.junit.jupiter.api.Test; + +class LibsqlConformanceGuardTest { + + @Test + void acceptsALibsqlConnection() throws Exception { + Connection connection = + connection( + "libSQL JDBC Driver", + "SQLite (libSQL)", + "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f284" + + "11c85dda156f82alt1"); + + LibsqlConformanceGuard.verify(connection); + } + + @Test + void rejectsAForeignDriverAndNamesTheResolvedAndExpectedDriver() { + Connection connection = connection("SQLite JDBC", "SQLite", null); + + AssertionError failure = + assertThrows( + AssertionError.class, () -> LibsqlConformanceGuard.verify(connection)); + + assertThat(failure) + .hasMessageContaining("\"SQLite JDBC\"") + .hasMessageContaining("\"libSQL JDBC Driver\""); + } + + @Test + void rejectsANonLibsqlServerAndNamesTheResolvedSourceId() { + Connection connection = + connection( + "libSQL JDBC Driver", + "SQLite (libSQL)", + "2024-10-21 16:30:22 03a9703e27c44437c39363d0baf82db4ebc94538a0f284" + + "11c85dda156f82"); + + AssertionError failure = + assertThrows( + AssertionError.class, () -> LibsqlConformanceGuard.verify(connection)); + + assertThat(failure) + .hasMessageContaining("2024-10-21 16:30:22") + .hasMessageContaining("alt1"); + } + + private static Connection connection( + String driverName, String productName, String sourceId) { + ResultSet resultSet = + proxy( + ResultSet.class, + (proxy, method, args) -> + switch (method.getName()) { + case "next" -> true; + case "getString" -> sourceId; + case "close" -> null; + default -> unsupported(method.getName()); + }); + Statement statement = + proxy( + Statement.class, + (proxy, method, args) -> + switch (method.getName()) { + case "executeQuery" -> resultSet; + case "close" -> null; + default -> unsupported(method.getName()); + }); + DatabaseMetaData metadata = + proxy( + DatabaseMetaData.class, + (proxy, method, args) -> + switch (method.getName()) { + case "getDriverName" -> driverName; + case "getDatabaseProductName" -> productName; + default -> unsupported(method.getName()); + }); + return proxy( + Connection.class, + (proxy, method, args) -> + switch (method.getName()) { + case "getMetaData" -> metadata; + case "createStatement" -> statement; + case "close" -> null; + default -> unsupported(method.getName()); + }); + } + + private static Object unsupported(String methodName) { + throw new UnsupportedOperationException(methodName); + } + + @SuppressWarnings("unchecked") + private static T proxy(Class type, InvocationHandler handler) { + return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] {type}, handler); + } +} diff --git a/libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/XerialLauncherSessionListener.java b/libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/XerialLauncherSessionListener.java index f1f503c..9838895 100644 --- a/libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/XerialLauncherSessionListener.java +++ b/libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/XerialLauncherSessionListener.java @@ -8,6 +8,7 @@ public final class XerialLauncherSessionListener implements LauncherSessionListe @Override public void launcherSessionOpened(LauncherSession session) { XerialConformanceDriver.register(); + LibsqlConformanceGuard.verify(); } @Override