Skip to content

Commit 8bc4120

Browse files
committed
test(conformance): strengthen weak assertions and add xerial control run (#51)
1 parent b25ddd9 commit 8bc4120

9 files changed

Lines changed: 262 additions & 98 deletions

File tree

‎libsql-conformance/pom.xml‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
<properties>
1717
<docker.config.dir>${project.build.directory}/docker-config</docker.config.dir>
18+
<xerial.version>3.53.4.0</xerial.version>
1819
</properties>
1920

2021
<dependencies>
@@ -155,4 +156,41 @@
155156
</plugin>
156157
</plugins>
157158
</build>
159+
160+
<profiles>
161+
<!--
162+
Parity baseline: run the same vendored test manifest against the pinned
163+
xerial 3.53.4.0 driver instead of the libSQL driver.
164+
-->
165+
<profile>
166+
<id>xerial-control</id>
167+
<dependencies>
168+
<dependency>
169+
<groupId>org.xerial</groupId>
170+
<artifactId>sqlite-jdbc</artifactId>
171+
<version>${xerial.version}</version>
172+
<scope>test</scope>
173+
</dependency>
174+
</dependencies>
175+
<build>
176+
<plugins>
177+
<plugin>
178+
<groupId>org.apache.maven.plugins</groupId>
179+
<artifactId>maven-failsafe-plugin</artifactId>
180+
<configuration>
181+
<includes combine.self="override">
182+
<include>**/*Test.java</include>
183+
</includes>
184+
<excludes combine.self="override">
185+
<exclude>**/com/github/magnusp/**/*Test.java</exclude>
186+
</excludes>
187+
<systemPropertyVariables>
188+
<conformance.control.driver>xerial</conformance.control.driver>
189+
</systemPropertyVariables>
190+
</configuration>
191+
</plugin>
192+
</plugins>
193+
</build>
194+
</profile>
195+
</profiles>
158196
</project>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.github.magnusp.libsql.conformance;
2+
3+
import java.sql.Driver;
4+
import java.sql.DriverManager;
5+
import java.sql.SQLException;
6+
import java.util.Enumeration;
7+
8+
/**
9+
* Selects the upstream xerial driver for the parity control run.
10+
*
11+
* <p>The control run compiles the same vendored test manifest as the libSQL run but executes it
12+
* against sqlite-jdbc 3.53.4.0. Both drivers are on the test class path, so the control run first
13+
* removes every driver visible to this class loader and then registers the pinned xerial driver
14+
* explicitly. That keeps {@code jdbc:sqlite:} URLs from reaching the libSQL driver.
15+
*/
16+
final class XerialControl {
17+
18+
static final String ENABLED_PROPERTY = "conformance.control.driver";
19+
static final String XERIAL_DRIVER = "org.sqlite.JDBC";
20+
21+
private static final String PROBE_URL = "jdbc:sqlite::memory:";
22+
23+
private XerialControl() {}
24+
25+
static boolean isEnabled() {
26+
return "xerial".equals(System.getProperty(ENABLED_PROPERTY));
27+
}
28+
29+
static void register() {
30+
deregisterVisibleDrivers();
31+
try {
32+
Driver driver =
33+
(Driver) Class.forName(XERIAL_DRIVER).getConstructor().newInstance();
34+
DriverManager.registerDriver(driver);
35+
} catch (ReflectiveOperationException e) {
36+
throw new IllegalStateException(
37+
"Xerial control run could not load " + XERIAL_DRIVER, e);
38+
} catch (SQLException e) {
39+
throw new IllegalStateException(
40+
"Xerial control run could not register " + XERIAL_DRIVER, e);
41+
}
42+
verify();
43+
}
44+
45+
private static void deregisterVisibleDrivers() {
46+
ClassLoader classLoader = XerialControl.class.getClassLoader();
47+
Enumeration<Driver> drivers = DriverManager.getDrivers();
48+
while (drivers.hasMoreElements()) {
49+
Driver driver = drivers.nextElement();
50+
if (driver.getClass().getClassLoader() == classLoader) {
51+
try {
52+
DriverManager.deregisterDriver(driver);
53+
} catch (SQLException e) {
54+
throw new IllegalStateException(
55+
"Xerial control run could not deregister " + driver, e);
56+
}
57+
}
58+
}
59+
}
60+
61+
private static void verify() {
62+
try {
63+
Driver driver = DriverManager.getDriver(PROBE_URL);
64+
if (!XERIAL_DRIVER.equals(driver.getClass().getName())) {
65+
throw new IllegalStateException(
66+
"Xerial control run resolved "
67+
+ driver.getClass().getName()
68+
+ " for "
69+
+ PROBE_URL
70+
+ " instead of "
71+
+ XERIAL_DRIVER);
72+
}
73+
} catch (SQLException e) {
74+
throw new IllegalStateException(
75+
"Xerial control run found no driver for " + PROBE_URL, e);
76+
}
77+
}
78+
}

‎libsql-conformance/src/test/java/com/github/magnusp/libsql/conformance/XerialLauncherSessionListener.java‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ public final class XerialLauncherSessionListener implements LauncherSessionListe
77

88
@Override
99
public void launcherSessionOpened(LauncherSession session) {
10+
if (XerialControl.isEnabled()) {
11+
XerialControl.register();
12+
return;
13+
}
1014
XerialConformanceDriver.register();
1115
LibsqlConformanceGuard.verify();
1216
}

‎upstream-xerial_sqlite-jdbc.manifest‎

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,63 @@
88
# # divergence: <path> -- <non-empty reason>
99
# Use the reason to say which libSQL or Hrana behavior makes parity impossible.
1010
#
11+
# Selection rationale. The conformance suite selects tests that exercise JDBC
12+
# behavior through ordinary SQL, so the Hrana transport can satisfy them
13+
# unchanged. Tests that drive sqlite-jdbc's native extension API, connection
14+
# configuration surface, or known libSQL parity gaps stay upstream. Each
15+
# selected and rejected class from the pinned 3.53.4.0 test tree is listed.
16+
17+
# Selected support code.
18+
# selected: src/test/java/org/sqlite/Utils.java -- compile-option assumptions shared by selected tests; not a test class.
19+
20+
# Selected tests.
21+
# selected: src/test/java/org/sqlite/ResultSetTest.java -- ResultSet navigation and getter conversions over ordinary SQL.
22+
# selected: src/test/java/org/sqlite/InsertQueryTest.java -- transaction reuse and commit visibility across shared and independent connections.
23+
# selected: src/test/java/org/sqlite/FetchSizeTest.java -- fetch-size configuration and stable result ordering.
24+
# selected: src/test/java/org/sqlite/CachedRowSetTest.java -- disconnected CachedRowSet population through a JDBC connection.
25+
# selected: src/test/java/org/sqlite/JSON1Test.java -- SQL JSON function output and text conversion.
26+
# selected: src/test/java/org/sqlite/MetadataLeakTest.java -- metadata result sets close the statement that produced them.
27+
# selected: src/test/java/org/sqlite/PreparedStatementThreadTest.java -- concurrent execute and close must not crash or hide failures.
28+
# selected: src/test/java/org/sqlite/ResultSetWithoutResultsTest.java -- statement result-kind transitions leave no stale result set.
29+
# selected: src/test/java/org/sqlite/RSMetaDataTest.java -- ResultSetMetaData column names, types, and nullability.
30+
31+
# Rejected tests.
32+
# rejected: src/test/java/org/sqlite/TypeMapTest.java -- removed: libSQL accepts but ignores type maps (tracked by #48), so a parity assertion would be red; asserting the no-op would be a weak test.
33+
# rejected: src/test/java/org/sqlite/BackupTest.java -- drives sqlite-jdbc's native backup API; Hrana exposes no backup primitive.
34+
# rejected: src/test/java/org/sqlite/BusyHandlerTest.java -- registers native busy-handler callbacks that Hrana does not expose.
35+
# rejected: src/test/java/org/sqlite/CollationTest.java -- registers JVM collations through sqlite-jdbc's native extension API.
36+
# rejected: src/test/java/org/sqlite/ConnectionTest.java -- exercises sqlite-jdbc URL, open-mode, and resource-loading configuration that has no Hrana equivalent; covered JDBC paths are asserted by selected tests.
37+
# rejected: src/test/java/org/sqlite/DBMetaDataTest.java -- DatabaseMetaData parity is tracked by #41, #42, and #50; selecting it now would be red.
38+
# rejected: src/test/java/org/sqlite/ErrorMessageTest.java -- asserts sqlite-jdbc wording and native result codes; sqld returns different messages and error codes.
39+
# rejected: src/test/java/org/sqlite/ExtendedCommandTest.java -- exercises sqlite-jdbc's dot-command parser, not the JDBC API.
40+
# rejected: src/test/java/org/sqlite/ExtensionTest.java -- loads native SQLite extensions; the libSQL server does not permit extension loading.
41+
# rejected: src/test/java/org/sqlite/JDBCTest.java -- mixes driver URL handling, extension loading, and sqlite-jdbc read-only configuration; broad driver-level coverage is asserted by selected tests.
42+
# rejected: src/test/java/org/sqlite/ListenerTest.java -- update, commit, rollback, and authorizer hooks require native callbacks unavailable over Hrana.
43+
# rejected: src/test/java/org/sqlite/MathFunctionsTest.java -- passes only when the server binary was compiled with SQLITE_ENABLE_MATH_FUNCTIONS; the sqld image build is not pinned to that option.
44+
# rejected: src/test/java/org/sqlite/MultipleClassLoaderTest.java -- verifies sqlite-jdbc's native library loading per class loader; libSQL has no native library.
45+
# rejected: src/test/java/org/sqlite/PrepStmtTest.java -- includes getGeneratedKeys (#39) and CLOB/binary binding parity that are not yet implemented; selected prepared-statement paths cover the rest.
46+
# rejected: src/test/java/org/sqlite/ProgressHandlerTest.java -- registers native progress callbacks that Hrana does not expose.
47+
# rejected: src/test/java/org/sqlite/QueryTest.java -- date/time and blob conversion cases belong to the value-conversion lane (#31, #32, #33, #34); selecting it now would mix known gaps into the parity baseline.
48+
# rejected: src/test/java/org/sqlite/ReadUncommittedTest.java -- depends on sqlite-jdbc shared-cache and read-uncommitted connection configuration; sqld does not expose it.
49+
# rejected: src/test/java/org/sqlite/SavepointTest.java -- savepoints are not implemented by the libSQL driver.
50+
# rejected: src/test/java/org/sqlite/SerializeTest.java -- serializes and restores a native database image; Hrana has no equivalent.
51+
# rejected: src/test/java/org/sqlite/SQLiteConfigTest.java -- tests sqlite-jdbc's configuration object, not the JDBC API.
52+
# rejected: src/test/java/org/sqlite/SQLiteConnectionPoolDataSourceTest.java -- tests sqlite-jdbc DataSource and pooling classes that libSQL does not provide.
53+
# rejected: src/test/java/org/sqlite/SQLiteDataSourceTest.java -- tests sqlite-jdbc DataSource classes that libSQL does not provide.
54+
# rejected: src/test/java/org/sqlite/SQLiteJDBCLoaderTest.java -- tests the native SQLite library loader.
55+
# rejected: src/test/java/org/sqlite/StatementTest.java -- statement reuse and lifecycle parity is tracked by #47; selected ResultSet tests cover the current read paths.
56+
# rejected: src/test/java/org/sqlite/TransactionTest.java -- asserts SQLite file-locking and multi-connection wait behavior that a single sqld server cannot reproduce; commit visibility is covered by InsertQueryTest.
57+
# rejected: src/test/java/org/sqlite/UDFCustomErrorTest.java -- registers JVM UDFs and inspects native error callbacks.
58+
# rejected: src/test/java/org/sqlite/UDFTest.java -- registers JVM UDFs through sqlite-jdbc's native extension API.
59+
# rejected: src/test/java/org/sqlite/util/OSInfoTest.java -- tests sqlite-jdbc's OS and native-library detection utility.
60+
# rejected: src/test/java/org/sqlite/architecture/CodingRulesTest.java -- ArchUnit rules over sqlite-jdbc source conventions; not runtime JDBC behavior.
61+
# rejected: src/test/java/org/sqlite/architecture/TestCodingRulesTest.java -- ArchUnit rules over sqlite-jdbc test conventions; not runtime JDBC behavior.
62+
1163
# First conformance batch.
1264
src/test/java/org/sqlite/Utils.java
1365
src/test/java/org/sqlite/ResultSetTest.java
1466
src/test/java/org/sqlite/InsertQueryTest.java
1567
src/test/java/org/sqlite/FetchSizeTest.java
16-
src/test/java/org/sqlite/TypeMapTest.java
1768
src/test/java/org/sqlite/CachedRowSetTest.java
1869

1970
# Second batch: metadata, JSON, threading, and empty result sets.
@@ -32,4 +83,6 @@ VERSION
3283
# when its last connection closes, so the conformance tests must close every
3384
# connection they open; leaving one open leaks the namespace for the whole run.
3485
# divergence: src/test/java/org/sqlite/JSON1Test.java -- Hrana holds a namespace until its last connection closes, so the conformance copy closes the leaked per-test connections.
35-
# divergence: src/test/java/org/sqlite/CachedRowSetTest.java -- Hrana holds a namespace until its last connection closes, so the conformance copy closes the leaked connection.
86+
# divergence: src/test/java/org/sqlite/CachedRowSetTest.java -- Hrana holds a namespace until its last connection closes, so the conformance copy closes the leaked connection and asserts the row it reads.
87+
# divergence: src/test/java/org/sqlite/InsertQueryTest.java -- upstream misuses the connection factory so the transaction is applied to a different connection and never asserts the result; the conformance copy reuses the transaction connection, asserts rollback and commit visibility, and asserts the final count (#51).
88+
# divergence: src/test/java/org/sqlite/PreparedStatementThreadTest.java -- upstream swallows ExecutionException, which also wraps assertion failures; the conformance copy rethrows AssertionError so the race test can fail (#51).

‎upstream-xerial_sqlite-jdbc/CHECKSUMS.sha256‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,22 @@ a0670cf020edf700524c43c85321c2ba027a8bace5682001db60875d4a9fe52b ./NOTICE
1111
# upstream 0a60829ce6edd56de46fb87303401c5c1e1e666da5631056340f5c5ce902dcdf ./VERSION
1212
0a60829ce6edd56de46fb87303401c5c1e1e666da5631056340f5c5ce902dcdf ./VERSION
1313
# upstream 1351fdca2644e90ac9028ed99c6096c9ecee82ccb7d45cedd9bb454887fd2c6b ./src/test/java/org/sqlite/CachedRowSetTest.java
14-
9e896e20522e4c5f4bc786ad7f8c5b6765428ad531fc0c07130b5f0beb81756f ./src/test/java/org/sqlite/CachedRowSetTest.java
14+
e1743294d2e5e781fae3b647d0bfc2eeeeb856c5276efe308f8b58979b3ab178 ./src/test/java/org/sqlite/CachedRowSetTest.java
1515
# upstream 9fb0f2df7de66cac0a46b241c0c1cd3311a80646d0431d377097741b99053760 ./src/test/java/org/sqlite/FetchSizeTest.java
1616
9fb0f2df7de66cac0a46b241c0c1cd3311a80646d0431d377097741b99053760 ./src/test/java/org/sqlite/FetchSizeTest.java
1717
# upstream 6dfcecb209e01829ba0aea782b47351443e3970ad1d33ac4c3fb979e6dff1f40 ./src/test/java/org/sqlite/InsertQueryTest.java
18-
6dfcecb209e01829ba0aea782b47351443e3970ad1d33ac4c3fb979e6dff1f40 ./src/test/java/org/sqlite/InsertQueryTest.java
18+
ae4568fd773f158f04e89df8e0fe2326381b6af7ed1316193830d38f7cadbae5 ./src/test/java/org/sqlite/InsertQueryTest.java
1919
# upstream bb07398946480ca44c70d03e6f8c399f53506b44c83a312e2d28aebd239c0238 ./src/test/java/org/sqlite/JSON1Test.java
2020
075745907a5174b922db6a45b9c29f45901af1835af27a0014f5fac835b41d0f ./src/test/java/org/sqlite/JSON1Test.java
2121
# upstream 242bb51330240174bab5f443aa4d8de1049e6d947374e080a636c4aa4d0049ee ./src/test/java/org/sqlite/MetadataLeakTest.java
2222
242bb51330240174bab5f443aa4d8de1049e6d947374e080a636c4aa4d0049ee ./src/test/java/org/sqlite/MetadataLeakTest.java
2323
# upstream 7af8ae0999a0bf26a20c96ab23131505bd79827e6af17742771e36e9d8d985f4 ./src/test/java/org/sqlite/PreparedStatementThreadTest.java
24-
7af8ae0999a0bf26a20c96ab23131505bd79827e6af17742771e36e9d8d985f4 ./src/test/java/org/sqlite/PreparedStatementThreadTest.java
24+
bc6d868d2069dcc5db54a7720e7ecd9f725c83dbf910ec8ec61222b98f6198c1 ./src/test/java/org/sqlite/PreparedStatementThreadTest.java
2525
# upstream d787e71bdf2b8d12a2669eb06c432cf2a39ac77f678a92459e3dd9b6c4519a43 ./src/test/java/org/sqlite/RSMetaDataTest.java
2626
d787e71bdf2b8d12a2669eb06c432cf2a39ac77f678a92459e3dd9b6c4519a43 ./src/test/java/org/sqlite/RSMetaDataTest.java
2727
# upstream 6065d284933c8462cc13217992820a9261075c0e9ee126df6cbe9961d90303f9 ./src/test/java/org/sqlite/ResultSetTest.java
2828
6065d284933c8462cc13217992820a9261075c0e9ee126df6cbe9961d90303f9 ./src/test/java/org/sqlite/ResultSetTest.java
2929
# upstream 958c1f2ff5ca2f1257dfd847207ea793839969b234d140576b51af346697233c ./src/test/java/org/sqlite/ResultSetWithoutResultsTest.java
3030
958c1f2ff5ca2f1257dfd847207ea793839969b234d140576b51af346697233c ./src/test/java/org/sqlite/ResultSetWithoutResultsTest.java
31-
# upstream ed2fb4bf7319bcba0662b83707375501337173956bae7c57dda6e6a2c46b7505 ./src/test/java/org/sqlite/TypeMapTest.java
32-
ed2fb4bf7319bcba0662b83707375501337173956bae7c57dda6e6a2c46b7505 ./src/test/java/org/sqlite/TypeMapTest.java
3331
# upstream bef3eddca2a6b80472050a3a52d5e7246128f0644d29c24ed091709b0ed6f121 ./src/test/java/org/sqlite/Utils.java
3432
bef3eddca2a6b80472050a3a52d5e7246128f0644d29c24ed091709b0ed6f121 ./src/test/java/org/sqlite/Utils.java

‎upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/CachedRowSetTest.java‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.sqlite;
22

3+
import static org.assertj.core.api.Assertions.assertThat;
4+
35
import java.sql.Connection;
46
import java.sql.DriverManager;
57
import java.sql.SQLException;
@@ -22,6 +24,11 @@ public void gh_224() throws SQLException {
2224
try (CachedRowSet crs = factory.createCachedRowSet()) {
2325
crs.setCommand("select * from person");
2426
crs.execute(connection);
27+
28+
assertThat(crs.next()).isTrue();
29+
assertThat(crs.getInt("id")).isEqualTo(1);
30+
assertThat(crs.getString("name")).isEqualTo("leo");
31+
assertThat(crs.next()).isFalse();
2532
}
2633
}
2734
}

0 commit comments

Comments
 (0)