@@ -3,6 +3,8 @@ module Main where
3
3
import CLI (GenSize (.. ), NumCases (.. ), Seed (.. ))
4
4
import qualified CLI
5
5
import qualified Codec.CBOR.Write as C
6
+ import qualified Control.Concurrent.Async as Async
7
+ import Control.Concurrent.MVar (modifyMVar_ , newMVar )
6
8
import Data.Aeson (FromJSON , ToJSON )
7
9
import qualified Data.Aeson as J
8
10
import qualified Data.Aeson.Encode.Pretty as J
@@ -11,6 +13,7 @@ import qualified Data.ByteString.Base16 as B16
11
13
import qualified Data.ByteString.Char8 as B8
12
14
import qualified Data.ByteString.Lazy as BL
13
15
import qualified Data.ByteString.Lazy.Char8 as BL8
16
+ import Data.Foldable (foldl' )
14
17
import Data.Proxy (Proxy (.. ))
15
18
import Data.Text (Text )
16
19
import qualified Data.Text as T
@@ -71,17 +74,37 @@ runGenerate (CLI.GenerateOptions maybeSeed genSize numCases command) = do
71
74
genSize
72
75
numCases
73
76
77
+ -- | We have to do this in multiple threads, otherwise this generator code is a
78
+ -- bottleneck for Rust tests. We still want to deterministically get the same
79
+ -- set of test cases for the same seed, but keeping the order is irrelevant,
80
+ -- which lets us do less of the costly sync.
74
81
writeRandom :: forall a . (Arbitrary a , Show a , G. OurCBOR a ) => Proxy a -> Seed -> GenSize -> NumCases -> IO ()
75
82
writeRandom _ (Seed seed) (GenSize generatorSize) (NumCases numCases) = do
76
- loop numCases (QC. mkQCGen seed)
83
+ let numGreenThreads = 64 -- changing this, changes determinism – how the seed influences the cases
84
+ putsLock <- newMVar ()
85
+ let chunks =
86
+ snd $
87
+ foldl'
88
+ ( \ (prevRng, acc) chunk ->
89
+ let (rngL, rngR) = System.Random. split prevRng
90
+ in (rngL, (chunk, rngR) : acc)
91
+ )
92
+ (QC. mkQCGen seed, [] )
93
+ (fairChunks numCases numGreenThreads)
94
+ let worker :: Int -> QC. QCGen -> IO ()
95
+ worker 0 _ = pure ()
96
+ worker n rng1 = do
97
+ let (value :: a , rng2 ) = splittingUnGen QC. arbitrary rng1 generatorSize
98
+ testCase :: TestCase a = mkTestCase value
99
+ modifyMVar_ putsLock . const . BL8. putStrLn $ J. encode testCase
100
+ worker (n - 1 ) rng2
101
+ Async. mapConcurrently_ (uncurry worker) chunks
102
+
103
+ -- | Split `total` into `n` fair chunks, e.g. `chunks 20 3 == [7,7,6]`.
104
+ fairChunks :: Int -> Int -> [Int ]
105
+ fairChunks total n = replicate remainder (base + 1 ) ++ replicate (n - remainder) base
77
106
where
78
- loop :: Int -> QC. QCGen -> IO ()
79
- loop 0 _ = pure ()
80
- loop n rng1 = do
81
- let (value :: a , rng2 ) = splittingUnGen QC. arbitrary rng1 generatorSize
82
- testCase :: TestCase a = mkTestCase value
83
- BL8. putStrLn $ J. encode testCase
84
- loop (n - 1 ) rng2
107
+ (base, remainder) = total `divMod` n
85
108
86
109
-- | For streaming, we need a version of `unGen` that returns the next RNG –
87
110
-- purely and deterministically.
0 commit comments