From c470daf355f38a0539b1a3d783c4986b73eaf483 Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Wed, 18 Mar 2020 09:23:30 -0400
Subject: [PATCH] Demonstrate how to make it work

A separate connection per concurrent transaction is required.
---
 test/Persistence.hs | 69 +++++++++++++++++++++++++++------------------
 1 file changed, 42 insertions(+), 27 deletions(-)

diff --git a/test/Persistence.hs b/test/Persistence.hs
index 48b9dab..4d435e5 100644
--- a/test/Persistence.hs
+++ b/test/Persistence.hs
@@ -74,57 +74,70 @@ failPayment = throwIO ArbitraryException
 makeVoucherPaymentTests
   :: VoucherDatabase d
   => Text.Text           -- ^ A distinctive identifier for this group's label.
-  -> IO d                -- ^ An operation that creates a new, empty voucher
-                         -- database.
+  -> IO (IO d)           -- ^ An operation that creates a new, empty voucher
+                         -- database and results in an operation that creates
+                         -- a new connection to that database.
   -> TestTree
 makeVoucherPaymentTests label makeDatabase =
   testGroup ("voucher payments (" ++ Text.unpack label ++ ")")
   [ testCase "not paid for" $ do
-      db <- makeDatabase
-      result <- redeemVoucher db voucher fingerprint
+      connect <- makeDatabase
+      conn <- connect
+      result <- redeemVoucher conn voucher fingerprint
       assertEqual "redeeming unpaid voucher" (Left NotPaid) result
   , testCase "paid for" $ do
-      db <- makeDatabase
-      () <- payForVoucher db voucher paySuccessfully
-      result <- redeemVoucher db voucher fingerprint
+      connect <- makeDatabase
+      conn <- connect
+      () <- payForVoucher conn voucher paySuccessfully
+      result <- redeemVoucher conn voucher fingerprint
       assertEqual "redeeming paid voucher" (Right ()) result
   , testCase "allowed double redemption" $ do
-      db <- makeDatabase
-      () <- payForVoucher db voucher paySuccessfully
-      let redeem = redeemVoucher db voucher fingerprint
+      connect <- makeDatabase
+      conn <- connect
+      () <- payForVoucher conn voucher paySuccessfully
+      let redeem = redeemVoucher conn voucher fingerprint
       first <- redeem
       second <- redeem
       assertEqual "redeeming paid voucher" (Right ()) first
       assertEqual "re-redeeming paid voucher" (Right ()) second
   , testCase "disallowed double redemption" $ do
-      db <- makeDatabase
-      () <- payForVoucher db voucher paySuccessfully
-      let redeem = redeemVoucher db voucher
+      connect <- makeDatabase
+      conn <- connect
+      () <- payForVoucher conn voucher paySuccessfully
+      let redeem = redeemVoucher conn voucher
       first <- redeem fingerprint
       second <- redeem (Text.cons 'a' $ Text.tail fingerprint)
       assertEqual "redeeming paid voucher" (Right ()) first
       assertEqual "re-redeeming paid voucher" (Left AlreadyRedeemed) second
   , testCase "pay with exception" $ do
-      db <- makeDatabase
-      payResult <- try $ payForVoucher db voucher failPayment
+      connect <- makeDatabase
+      conn <- connect
+      payResult <- try $ payForVoucher conn voucher failPayment
       assertEqual "failing a payment for a voucher" (Left ArbitraryException) payResult
-      result <- redeemVoucher db voucher fingerprint
+      result <- redeemVoucher conn voucher fingerprint
       assertEqual "redeeming voucher with failed payment" (Left NotPaid) result
   , testCase "disallowed double payment" $ do
-      db <- makeDatabase
-      let pay = payForVoucher db voucher paySuccessfully
+      connect <- makeDatabase
+      conn <- connect
+      let pay = payForVoucher conn voucher paySuccessfully
       () <- pay
       payResult <- try pay
       assertEqual "double-paying for a voucher" (Left AlreadyPaid) payResult
-      redeemResult <- redeemVoucher db voucher fingerprint
+      redeemResult <- redeemVoucher conn voucher fingerprint
       assertEqual "redeeming double-paid voucher" (Right ()) redeemResult
   , testCase "concurrent redemption" $ do
-      db <- makeDatabase
-      () <- payForVoucher db voucher paySuccessfully
-      () <- payForVoucher db anotherVoucher paySuccessfully
-
-      let redeem = redeemVoucher db voucher fingerprint
-      let anotherRedeem = redeemVoucher db anotherVoucher fingerprint
+      connect <- makeDatabase
+      connA <- connect
+      connB <- connect
+      -- It doesn't matter which connection pays for the vouchers.  They
+      -- payments are concurrent and the connections are to the same database.
+      () <- payForVoucher connA voucher paySuccessfully
+      () <- payForVoucher connA anotherVoucher paySuccessfully
+
+      -- It does matter which connection is used to redeem the voucher.  A
+      -- connection can only do one thing at a time.
+      let redeem = redeemVoucher connA voucher fingerprint
+      let anotherRedeem = redeemVoucher connB anotherVoucher fingerprint
 
       result <- withAsync redeem $ \r1 -> do
         withAsync anotherRedeem $ \r2 -> do
@@ -135,7 +148,9 @@ makeVoucherPaymentTests label makeDatabase =
 
 -- | Instantiate the persistence tests for the memory backend.
 memoryDatabaseVoucherPaymentTests :: TestTree
-memoryDatabaseVoucherPaymentTests = makeVoucherPaymentTests "memory" memory
+memoryDatabaseVoucherPaymentTests = makeVoucherPaymentTests "memory" $ do
+  db <- memory
+  return $ return db
 
 -- | Instantiate the persistence tests for the sqlite3 backend.
 sqlite3DatabaseVoucherPaymentTests :: TestTree
@@ -144,4 +159,4 @@ sqlite3DatabaseVoucherPaymentTests =
   do
     tempdir <- getTemporaryDirectory
     (path, handle) <- openTempFile tempdir "voucher-.db"
-    getDBConnection $ Text.pack path
+    return . getDBConnection . Text.pack $ path
-- 
GitLab