Skip to content

Commit 0f3c1e8

Browse files
committed
Cloud Haskell tests for QUIC
1 parent adbcba1 commit 0f3c1e8

File tree

8 files changed

+282
-116
lines changed

8 files changed

+282
-116
lines changed

.github/workflows/cabal.yml

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,15 @@ jobs:
4949
with:
5050
ghc-version: ${{ matrix.ghc-version }}
5151
cabal-version: '3.12.1.0'
52-
52+
5353
- name: Generate freeze file
5454
run: |
55-
cabal configure --enable-tests --test-show-details=direct
55+
# Cloud Haskell tests using the QUIC backend are quite flaky, but in CI only.
56+
# Therefore, the 'quic' flag is normally enabled locally, but disabled in CI.
57+
cabal configure --enable-tests --test-show-details=direct --flags "-quic"
5658
cabal freeze ${{matrix.cabal-flags}} --minimize-conflict-set
5759
cat cabal.project.freeze
58-
60+
5961
- name: Cache cabal work
6062
uses: actions/cache@v4
6163
with:
@@ -68,12 +70,14 @@ jobs:
6870

6971
- name: Build dependencies only
7072
run: cabal build all --only-dependencies ${{matrix.cabal-flags}}
71-
73+
7274
- name: Build all packages
7375
run: cabal build all ${{matrix.cabal-flags}}
74-
76+
7577
- name: Run all tests
7678
# We have seen in the past some tests hang for hours, wasting resources.
7779
# The timeout below should be plenty
7880
timeout-minutes: 10
79-
run: cabal test all ${{matrix.cabal-flags}}
81+
# We run each test suite one-by-one to better observe problems.
82+
run: cabal test all -j1 ${{matrix.cabal-flags}}
83+

cabal.project

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
packages: packages/*/**.cabal
22

33
package distributed-process-tests
4-
flags: +tcp
4+
flags: +tcp

packages/distributed-process-tests/distributed-process-tests.cabal

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ copyright: Well-Typed LLP
1212
category: Control, Cloud Haskell
1313
build-type: Simple
1414
tested-with: GHC==8.10.7 GHC==9.0.2 GHC==9.2.8 GHC==9.4.8 GHC==9.6.7 GHC==9.8.4 GHC==9.10.3 GHC==9.12.2
15+
extra-source-files: tests/credentials/*
1516

1617
source-repository head
1718
Type: git
@@ -22,6 +23,10 @@ flag tcp
2223
Description: build and run TCP tests
2324
Default: False
2425

26+
flag quic
27+
Description: build and run QUIC tests
28+
Default: True
29+
2530
common warnings
2631
ghc-options: -Wall
2732
-Wcompat
@@ -100,6 +105,22 @@ Test-Suite TestCHInTCP
100105
ghc-options: -threaded -rtsopts -with-rtsopts=-N -fno-warn-unused-do-bind
101106
HS-Source-Dirs: tests
102107

108+
Test-Suite TestCHInQUIC
109+
import: warnings
110+
Type: exitcode-stdio-1.0
111+
Main-Is: runQUIC.hs
112+
if flag(quic)
113+
Build-Depends: base >= 4.14 && < 5,
114+
distributed-process-tests,
115+
filepath,
116+
network-transport,
117+
network-transport-quic,
118+
tasty >= 1.5 && <1.6,
119+
else
120+
Buildable: False
121+
default-language: Haskell2010
122+
ghc-options: -threaded -rtsopts -with-rtsopts=-N
123+
HS-Source-Dirs: tests
103124

104125
Test-Suite TestClosure
105126
import: warnings

packages/distributed-process-tests/src/Control/Distributed/Process/Tests/CH.hs

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE NumericUnderscores #-}
12
module Control.Distributed.Process.Tests.CH (tests) where
23

34

@@ -42,8 +43,9 @@ import Control.Distributed.Process.Node
4243
import Control.Distributed.Process.Tests.Internal.Utils (pause)
4344
import Control.Distributed.Process.Serializable (Serializable)
4445
import Data.Maybe (isNothing, isJust)
46+
import System.Timeout (timeout)
4547
import Test.Tasty (TestTree, testGroup)
46-
import Test.Tasty.HUnit (Assertion, assertBool, assertEqual, testCase)
48+
import Test.Tasty.HUnit (Assertion, assertBool, assertEqual, testCase, assertFailure)
4749

4850
newtype Ping = Ping ProcessId
4951
deriving (Typeable, Binary, Show)
@@ -70,7 +72,12 @@ ping = do
7072
ping
7173

7274
verifyClient :: String -> MVar Bool -> IO ()
73-
verifyClient s b = takeMVar b >>= assertBool s
75+
verifyClient s b =
76+
-- The timeout below must be generous enough to support
77+
-- running tests in the Github Actions CI environment, which is quite slow.
78+
timeout 60_000_000
79+
(takeMVar b >>= assertBool s)
80+
>>= maybe (assertFailure $ "verifyClient timeout: " <> s) (\_ -> pure ())
7481

7582
expectPing :: MVar Bool -> Process ()
7683
expectPing mv = expect >>= liftIO . putMVar mv . checkPing
@@ -175,14 +182,14 @@ monitorTestProcess theirAddr mOrL un reason monitorSetup done =
175182
unmonitor ref
176183
liftIO $ putMVar done ()
177184
(False, ref) -> do
178-
receiveWait [
185+
receiveTimeout 1_000_000 [
179186
match (\(ProcessMonitorNotification ref' pid reason') -> do
180187
liftIO $ do
181188
assertBool "Bad Monitor Signal"
182189
(Just ref' == ref && pid == theirAddr &&
183190
mOrL && reason == reason')
184191
putMVar done ())
185-
]
192+
] >>= maybe (liftIO $ assertFailure "No ProcessMonitorNotification received within timeout window") pure
186193
)
187194
(\(ProcessLinkException pid reason') -> do
188195
(liftIO $ assertBool "link exception unmatched" $
@@ -220,11 +227,11 @@ testPing TestTransport{..} = do
220227
p <- expectTimeout 3000000
221228
case p of
222229
Just (Ping _) -> return ()
223-
Nothing -> die "Failed to receive Ping"
230+
Nothing -> let msg = "Failed to receive Ping" in liftIO (putMVar clientDone (Left msg)) >> die msg
224231

225-
putMVar clientDone ()
232+
putMVar clientDone (Right ())
226233

227-
takeMVar clientDone
234+
takeMVar clientDone >>= either assertFailure pure
228235

229236
-- | Monitor a process on an unreachable node
230237
testMonitorUnreachable :: TestTransport -> Bool -> Bool -> Assertion
@@ -348,6 +355,7 @@ testMonitorDisconnect TestTransport{..} mOrL un = do
348355
putMVar processAddr addr
349356
readMVar monitorSetup
350357
NT.closeEndPoint (localEndPoint localNode)
358+
threadDelay 100_000
351359
putMVar processAddr2 addr2
352360

353361
forkIO $ do
@@ -430,7 +438,7 @@ testTimeout TestTransport{..} = do
430438
done <- newEmptyMVar
431439

432440
runProcess localNode $ do
433-
res <- receiveTimeout 1000000 [match (\Add{} -> return ())]
441+
res <- receiveTimeout 1_000_000 [match (\Add{} -> return ())]
434442
liftIO $ putMVar done $ res == Nothing
435443

436444
verifyClient "Expected receiveTimeout to timeout..." done
@@ -447,7 +455,7 @@ testTimeout0 TestTransport{..} = do
447455
-- Variation on the venerable ping server which uses a zero timeout
448456
partner <- fix $ \loop ->
449457
receiveTimeout 0 [match (\(Pong partner) -> return partner)]
450-
>>= maybe (liftIO (threadDelay 100000) >> loop) return
458+
>>= maybe (liftIO (threadDelay 100_000) >> loop) return
451459
self <- getSelfPid
452460
send partner (Ping self)
453461
putMVar serverAddr addr
@@ -459,7 +467,7 @@ testTimeout0 TestTransport{..} = do
459467
pid <- getSelfPid
460468
-- Send a bunch of messages. A large number of messages that the server
461469
-- is not interested in, and then a single message that it wants
462-
replicateM_ 10000 $ send server "Irrelevant message"
470+
replicateM_ 10_000 $ send server "Irrelevant message"
463471
send server (Pong pid)
464472
expectPing clientDone
465473

@@ -582,7 +590,7 @@ testMergeChannels TestTransport{..} = do
582590
charChannel c = do
583591
(sport, rport) <- newChan
584592
replicateM_ 3 $ sendChan sport c
585-
liftIO $ threadDelay 10000 -- Make sure messages have been sent
593+
liftIO $ threadDelay 10_000 -- Make sure messages have been sent
586594
return rport
587595

588596
testTerminate :: TestTransport -> Assertion
@@ -621,15 +629,19 @@ testMonitorLiveNode TestTransport{..} = do
621629
forkProcess node2 $ do
622630
ref <- monitorNode (localNodeId node1)
623631
liftIO $ putMVar ready ()
632+
-- node1 gets closed
624633
liftIO $ takeMVar readyr
625634
send p ()
626-
receiveWait [
635+
receiveTimeout 10_000_000 [
627636
match (\(NodeMonitorNotification ref' nid _) ->
628637
(return $ ref == ref' && nid == localNodeId node1))
629-
] >>= liftIO . putMVar done
638+
] >>= maybe
639+
(liftIO $ assertFailure "Did not receive NodeMonitorNotification message within timeout window")
640+
(liftIO . putMVar done)
630641

631642
takeMVar ready
632643
closeLocalNode node1
644+
threadDelay 1_000_000
633645
putMVar readyr ()
634646

635647
verifyClient "Expected NodeMonitorNotification for LIVE node" done
@@ -638,22 +650,27 @@ testMonitorChannel :: TestTransport -> Assertion
638650
testMonitorChannel TestTransport{..} = do
639651
[node1, node2] <- replicateM 2 $ newLocalNode testTransport initRemoteTable
640652
gotNotification <- newEmptyMVar
653+
ready <- newEmptyMVar
641654

642655
pid <- forkProcess node1 $ do
656+
liftIO $ putMVar ready ()
643657
sport <- expect :: Process (SendPort ())
644658
ref <- monitorPort sport
645-
receiveWait [
659+
receiveTimeout 10_000_000 [
646660
-- reason might be DiedUnknownId if the receive port is GCed before the
647661
-- monitor is established (TODO: not sure that this is reasonable)
648662
match (\(PortMonitorNotification ref' port' reason) ->
649663
return $ ref' == ref && port' == sendPortId sport &&
650664
(reason == DiedNormal || reason == DiedUnknownId))
651-
] >>= liftIO . putMVar gotNotification
665+
] >>= maybe
666+
(liftIO $ assertFailure "Did not receive PortMonitorNotification message within timeout window")
667+
(liftIO . putMVar gotNotification)
652668

653669
runProcess node2 $ do
654670
(sport, _) <- newChan :: Process (SendPort (), ReceivePort ())
671+
liftIO $ takeMVar ready
655672
send pid sport
656-
liftIO $ threadDelay 100000
673+
liftIO $ threadDelay 100_000
657674

658675
verifyClient "Expected PortMonitorNotification" gotNotification
659676

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDoTCCAomgAwIBAgIUVp3lTRQWZSOwolWHNaghO6gR68owDQYJKoZIhvcNAQEL
3+
BQAwRTESMBAGA1UEAwwJMTI3LjAuMC4xMQswCQYDVQQGEwJDQTEPMA0GA1UECAwG
4+
UXVlYmVjMREwDwYDVQQHDAhNb250cmVhbDAgFw0yNTA4MTgwMDU1MDRaGA8yMTI1
5+
MDcyNTAwNTUwNFowRTESMBAGA1UEAwwJMTI3LjAuMC4xMQswCQYDVQQGEwJDQTEP
6+
MA0GA1UECAwGUXVlYmVjMREwDwYDVQQHDAhNb250cmVhbDCCASIwDQYJKoZIhvcN
7+
AQEBBQADggEPADCCAQoCggEBAORALZlg9Qmu+A2HT4MUjF1iGUdWF6tlRgF6+zLZ
8+
uvuSM+eR0yH+EJZB2xqanzkXHVAkAnHPWRZ2HWqTS7TLOMyRdPEkiCg+WmW2f0t0
9+
hNCjZVMviahQgOwHkbTZbfsUHTv65cEk4XCgvQXFteMC+Q3lCeXWGoeMOt7AZ3ld
10+
vf7jgmPTQXOQFhqa9q5Qcxn+b1+2NBgQXqEQTVARBLPbCB4M0SKLZ4fWK4VHZsbe
11+
k8fUJBGgz/gTDNNClUiVBhBiv/9uvunZRpU1QBN5tZYXAPc0hX608L33R+LFsoDM
12+
cO5+j+XIjvxWNk94cmM/cb4PLlZBeNBlXxWxY1lKAxjja58CAwEAAaOBhjCBgzAd
13+
BgNVHQ4EFgQUGj/6Vt/0fjbTGBHPZNRIxJywRnkwHwYDVR0jBBgwFoAUGj/6Vt/0
14+
fjbTGBHPZNRIxJywRnkwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQGCCsG
15+
AQUFBwMBBggrBgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
16+
A4IBAQA+AuoFBODpWaWrVSjdGZPHP4DtlhB9jDy0WmUBJ8BxeB8SooJoyTsBXVhq
17+
7ACKp11rxJPk9Tv9JOsRrWi+YLzgs+QsKpUKb6RK5nszz17K1md8BavGzE4n/e0F
18+
tzYvWAeyIazHW551GMB1MkpSVcsJNqe91z35qmykmwIo8h+BgqTFzUFiln6bLnqP
19+
KxrWKdlVh2BGEVbH5APClQii0bX1qEn0A8CkAMbldC1GNFbfhyxk1v+8CVK1M6Nx
20+
BrTe15/CVTw/ceCfFZra4DinsflyCP+CcitGOUhWKgrUSiyN8xtr+Wopq4+ntm/Z
21+
ku6j3frrSJnT9A+nZyyGvZlSPrxf
22+
-----END CERTIFICATE-----
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDkQC2ZYPUJrvgN
3+
h0+DFIxdYhlHVherZUYBevsy2br7kjPnkdMh/hCWQdsamp85Fx1QJAJxz1kWdh1q
4+
k0u0yzjMkXTxJIgoPlpltn9LdITQo2VTL4moUIDsB5G02W37FB07+uXBJOFwoL0F
5+
xbXjAvkN5Qnl1hqHjDrewGd5Xb3+44Jj00FzkBYamvauUHMZ/m9ftjQYEF6hEE1Q
6+
EQSz2wgeDNEii2eH1iuFR2bG3pPH1CQRoM/4EwzTQpVIlQYQYr//br7p2UaVNUAT
7+
ebWWFwD3NIV+tPC990fixbKAzHDufo/lyI78VjZPeHJjP3G+Dy5WQXjQZV8VsWNZ
8+
SgMY42ufAgMBAAECggEAGfwodM6x9tFBkiC2b6DWPgdeA14Mwcl8x8xdbrOU8vD5
9+
EcLrO3J2JvUGYaf6uoAkKSyATr6hUMpPnQN52fJM3BUvMAjNq2810WCOa2OvfyUq
10+
8uZ1kIDhvH08HE+okq3+igaNQ4jUVYMnIdIZW+fJvMg3cUAHsyjGxvc2kH2YlLzQ
11+
3zxEFacnTb2K/Sxa/rFC7O3r2M6casTVsqfLyeShnSLEwLLk8tzCZZc6Sap9rVgh
12+
CIcUhZFGxLYWMBJwRs68rmgT7rvQvh8NxzDMGM9Z/AQzeeHAvjAkb4gZBu+W69vD
13+
CYjMi3cchdG/2ouYqijdv9DcqRDfz6BDwf8fT96dyQKBgQD0rGreqY7E8Wnt3EjF
14+
TYwi6Hj7r6gMw3kdIIJ49st2lTvOmeZpvJX7DOh43NNidx9q2Ai1XCCEDQlpPS7i
15+
UnqOLwX0gGYZjYkI8QSdNbJ9T4wepfSeox7dte/xnglEkfipHV3tLqhurgw+wvGW
16+
52hBB6DVSumzjcG/hrvkDth31QKBgQDu0SMH5mg4L4KaT9+qZm3IW+Xey3vwPFES
17+
w4bGsmAddzxXRIw6+ut2+AX/WSccUnZmgtiKKzS1yrBXGa98dqzjGRcDnbchkm+6
18+
Ka1s3ZSx7cjgya43jLIZ9ycwva8+OPPfzrOB6zLgIauwi5B7JsB1Qt81AXeo5/jb
19+
S64FRXkjowKBgChebj+QoEK0RjL9nnAXTGDSFGwKXmLEua3pmD1XEtjc5IJA+DhH
20+
6kMCrTSL0sCzQNbDECTEL4U6FWxssNicnSXqckQWD0J2DL8R7R33JxzvzAGehg7K
21+
gSQ5iX5HAeZzYyCb/MxOX3Hre4+7YFrykUvxc0Ld2lNKt0XfeA63uFWFAoGAOMfk
22+
ylYP5Xv2U3Y2Oa+M3pxq9SPwXdgZdpqiis+SZq8Y267ioItUPL8PvfyWffdlS05E
23+
6eUH7Uk50Bu9S5xz0rL+c8+l4QeOJPcP0tiEKCHfJwMMtwxutBm9aatP5T1pToc4
24+
yuT+/adDyQAF5CH8lGTH6TRmHPS6iHlf8MTp3n0CgYEAwUWjiimBoPQV3X2mHYp5
25+
yXBKGrsEItOmZUKYpl9UGVdGHHuZqzKi5ckOUK+vfd2uH9toUBMFK5aBM3VmFWPb
26+
3IpTrYe/Zu545dZszESjpl9JeiiSOVvPllCh0BrOAK1TwRapWUTsS8ut5pt5zLuo
27+
VbKNvUzMHtq6vp511AD0zCY=
28+
-----END PRIVATE KEY-----
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
{-# LANGUAGE CPP #-}
2+
{-# LANGUAGE LambdaCase #-}
3+
4+
-- Run tests using the QUIC transport.
5+
--
6+
module Main where
7+
8+
import Control.Distributed.Process.Tests.CH (tests)
9+
import Control.Exception (bracket, throwIO)
10+
import Data.List.NonEmpty (NonEmpty (..))
11+
import Network.Transport (Transport, closeTransport)
12+
import Network.Transport.QUIC
13+
( QUICTransportConfig (..),
14+
createTransport,
15+
credentialLoadX509,
16+
)
17+
import Network.Transport.Test (TestTransport (..))
18+
import System.FilePath ((</>))
19+
import System.IO
20+
( BufferMode (LineBuffering),
21+
hSetBuffering,
22+
stderr,
23+
stdout,
24+
)
25+
import Test.Tasty (defaultMain, localOption)
26+
import Test.Tasty.Runners (NumThreads)
27+
28+
main :: IO ()
29+
main = do
30+
hSetBuffering stdout LineBuffering
31+
hSetBuffering stderr LineBuffering
32+
withQUICTransport $ \transport -> do
33+
ts <-
34+
tests
35+
TestTransport
36+
{ testTransport = transport,
37+
testBreakConnection = \_ _ -> pure () -- I'm not sure how to break the connection at this time
38+
}
39+
40+
-- Tests are time sensitive. Running the tests concurrently can slow them
41+
-- down enough that threads using threadDelay would wake up later than
42+
-- expected, thus changing the order in which messages were expected.
43+
-- Therefore we run the tests sequentially
44+
--
45+
-- The problem was first detected with
46+
-- 'Control.Distributed.Process.Tests.CH.testMergeChannels'
47+
-- in particular.
48+
defaultMain (localOption (1 :: NumThreads) ts)
49+
50+
withQUICTransport :: (Transport -> IO a) -> IO a
51+
withQUICTransport =
52+
bracket
53+
(mkQUICTransport >>= either (throwIO . userError) pure)
54+
closeTransport
55+
56+
mkQUICTransport :: IO (Either String Transport)
57+
mkQUICTransport = do
58+
credentialLoadX509
59+
-- Generate a self-signed x509v3 certificate using this nifty tool:
60+
-- https://certificatetools.com/
61+
("tests" </> "credentials" </> "cert.crt")
62+
("tests" </> "credentials" </> "cert.key")
63+
>>= \case
64+
Left errmsg -> pure $ Left errmsg
65+
Right creds ->
66+
Right
67+
<$> createTransport
68+
( QUICTransportConfig
69+
{ hostName = "127.0.0.1",
70+
serviceName = "0",
71+
credentials = creds :| [],
72+
-- credentials are self-signed, and therefore cannot be validated
73+
validateCredentials = False
74+
}
75+
)

0 commit comments

Comments
 (0)