package org.sqlite; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.concurrent.CountDownLatch; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; public class BusyHandlerTest { private Connection conn; private Statement stat; @BeforeEach public void connect() throws Exception { conn = DriverManager.getConnection("jdbc:sqlite:target/test.db"); stat = conn.createStatement(); } @AfterEach public void close() throws SQLException { stat.close(); conn.close(); } public class BusyWork extends Thread { private final Connection conn; private final Statement stat; private final CountDownLatch lockedLatch = new CountDownLatch(1); private final CountDownLatch completeLatch = new CountDownLatch(1); public BusyWork() throws Exception { conn = DriverManager.getConnection("jdbc:sqlite:target/test.db"); Function.create( conn, "wait_for_latch", new Function() { @Override protected void xFunc() throws SQLException { lockedLatch.countDown(); try { completeLatch.await(); } catch (InterruptedException e) { throw new SQLException("Interrupted"); } result(100); } }); stat = conn.createStatement(); stat.setQueryTimeout(1); } @Override public void run() { try { // Generate some work for the sqlite vm stat.executeUpdate("drop table if exists foo;"); stat.executeUpdate("create table foo (id integer);"); stat.execute("insert into foo (id) values (wait_for_latch());"); } catch (SQLException ex) { System.out.println("HERE" + ex.toString()); } } } private void workWork() throws SQLException { // Generate some work for the sqlite vm int i = 0; while (i < 5) { stat.execute("insert into foo (id) values (" + i + ")"); i++; } } @Test public void basicBusyHandler() throws Exception { final int[] calls = {0}; BusyHandler.setHandler( conn, new BusyHandler() { @Override protected int callback(int nbPrevInvok) throws SQLException { assertEquals(nbPrevInvok, calls[0]); calls[0]++; if (nbPrevInvok <= 1) { return 1; } else { return 0; } } }); BusyWork busyWork = new BusyWork(); busyWork.start(); // let busyWork block inside insert busyWork.lockedLatch.await(); try { workWork(); fail("Should throw SQLITE_BUSY exception"); } catch (SQLException ex) { assertEquals(SQLiteErrorCode.SQLITE_BUSY.code, ex.getErrorCode()); } busyWork.completeLatch.countDown(); busyWork.join(); assertEquals(3, calls[0]); } @Test public void testUnregister() throws Exception { final int[] calls = {0}; BusyHandler.setHandler( conn, new BusyHandler() { @Override protected int callback(int nbPrevInvok) throws SQLException { assertEquals(nbPrevInvok, calls[0]); calls[0]++; if (nbPrevInvok <= 1) { return 1; } else { return 0; } } }); BusyWork busyWork = new BusyWork(); busyWork.start(); // let busyWork block inside insert busyWork.lockedLatch.await(); try { workWork(); fail("Should throw SQLITE_BUSY exception"); } catch (SQLException ex) { assertEquals(SQLiteErrorCode.SQLITE_BUSY.code, ex.getErrorCode()); } busyWork.completeLatch.countDown(); busyWork.join(); assertEquals(3, calls[0]); int totalCalls = calls[0]; BusyHandler.clearHandler(conn); busyWork = new BusyWork(); busyWork.start(); // let busyWork block inside insert busyWork.lockedLatch.await(); try { workWork(); fail("Should throw SQLITE_BUSY exception"); } catch (SQLException ex) { assertEquals(SQLiteErrorCode.SQLITE_BUSY.code, ex.getErrorCode()); } busyWork.completeLatch.countDown(); busyWork.join(); assertEquals(totalCalls, calls[0]); } }