File: TestSequence.hs

package info (click to toggle)
haskell-expiring-cache-map 0.0.6.1-9
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 232 kB
  • sloc: haskell: 2,209; makefile: 5
file content (188 lines) | stat: -rw-r--r-- 6,619 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
{-# LANGUAGE CPP #-}

-- |
-- Module : Caching.ExpiringCacheMap.Utils.TestSequence
-- Copyright: (c) 2014 Edward L. Blake
-- License: BSD-style
-- Maintainer: Edward L. Blake <edwardlblake@gmail.com>
-- Stability: experimental
-- Portability: portable
--
-- TestSequence monad for testing caching behaviour.
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- > 
-- > import Caching.ExpiringCacheMap.HashECM (newECMForM, lookupECM, CacheSettings(..), consistentDuration)
-- > import qualified Caching.ExpiringCacheMap.Utils.TestSequence as TestSeq
-- > 
-- > import qualified Data.ByteString.Char8 as BS
-- > 
-- > test = do
-- >   (TestSeq.TestSequenceState (_, events, _), return_value) <- TestSeq.runTestSequence test'
-- >   (putStrLn . show . reverse) events
-- >   return ()
-- >   where
-- >     test' = do
-- >       filecache <- newECMForM
-- >             (consistentDuration 100 -- Duration between access and expiry time of each item, no state needed.
-- >               (\state _id -> do number <- TestSeq.readNumber
-- >                                 return (state, number)))
-- >             (TestSeq.getCurrentTime >>= return)
-- >             12000 -- Time check frequency: (accumulator `mod` this_number) == 0.
-- >             (CacheWithLRUList 
-- >               6   -- Expected size of key-value map when removing elements.
-- >               6   -- Size of map when to remove items from key-value map.
-- >               12  -- Size of list when to compact
-- >               )
-- >             TestSeq.newTestSVar TestSeq.enterTestSVar TestSeq.readTestSVar
-- >       
-- >       -- Use lookupECM whenever the contents of "file1" is needed.
-- >       b <- lookupECM filecache ("file1" :: BS.ByteString)
-- >       TestSeq.haveNumber b
-- >       b <- lookupECM filecache "file1"
-- >       b <- lookupECM filecache "file2"
-- >       TestSeq.haveNumber b
-- >       return b
-- >
--
-- Evaluating the @test@ function results in a list of events.
--
-- >>> test
-- [GetVar 3,ReadNumber 4,GetTime 7,PutVar 11,HaveNumber 4,GetVar 14,PutVar 17,
--  GetVar 19,ReadNumber 20,GetTime 23,PutVar 27,HaveNumber 20]
-- 
-- In this example the history shows 2 time accesses (@GetTime 7@ and
-- @GetTime 23@) since the time check frequency number is a high value (12000),
-- but regardless the high value a time check is still requested again because
-- of the new key request for @"file2"@.
--
-- Changing the time frequency to 1 will alter the list of events with more
-- frequent time checks:
--
-- >>> test
-- [GetVar 3,ReadNumber 4,GetTime 7,PutVar 11,HaveNumber 4,GetVar 14,GetTime 15,
--  GetTime 18,PutVar 22,GetVar 24,ReadNumber 25,GetTime 28,PutVar 32,
--  HaveNumber 25]
--

module Caching.ExpiringCacheMap.Utils.TestSequence (
    runTestSequence,
    newTestSVar,
    enterTestSVar,
    readTestSVar,
    getCurrentTime,
    readNumber,
    haveNumber,
    TestSequenceEvents(..),
    TestSequenceState(..),
    TestSequence(..),
    TestSVar(..)
) where

#if (!defined(__GLASGOW_HASKELL__)) || (__GLASGOW_HASKELL__ < 710)
import Control.Applicative (Applicative(..))
#endif
import Control.Monad (ap, liftM)
import Data.Word (Word32)

data TestSequenceEvents = 
  GetVar Word32 |
  PutVar Word32 |
  GetTime Word32 |
  ReadNumber Int |
  HaveNumber Int
  deriving (Eq)

instance Show TestSequenceEvents where
  show (GetVar a)     = "GetVar " ++ (show a)
  show (PutVar a)     = "PutVar " ++ (show a)
  show (GetTime a)    = "GetTime " ++ (show a)
  show (ReadNumber a) = "ReadNumber " ++ (show a)
  show (HaveNumber a) = "HaveNumber " ++ (show a)


newtype TestSequenceState b =
  TestSequenceState (Word32, [TestSequenceEvents], Maybe b)
  
instance Show (TestSequenceState ct) where
  show (TestSequenceState (a,b,_)) =
    "TestSequenceState " ++ (show a) ++ " " ++ (show b)

newtype TestSequence b a =
  TestSequence (TestSequenceState b -> (TestSequenceState b, a))

newtype TestSVar a = TestSVar a

-- For GHC 7.10
instance Functor (TestSequence a) where
  fmap = liftM
  
instance Applicative (TestSequence a) where
  pure = return
  (<*>) = ap

instance Monad (TestSequence a) where
  TestSequence fun >>= k =
    TestSequence
      (\state -> let (state', ret) = (fun state)
                     TestSequence fun' = k ret
                  in fun' state')
  return ret = 
    TestSequence $
      \(TestSequenceState (timer, hl, testsvar)) ->
       (TestSequenceState (timer+1,hl, testsvar), ret)

runTestSequence :: Show a => TestSequence b a -> IO (TestSequenceState b, a)
runTestSequence f = do
  let ret = (fun (TestSequenceState (0, [], Nothing)))
   in return ret
  where
    TestSequence fun = (TestSequence
      (\(TestSequenceState (t, hl, testsvar)) ->
        (TestSequenceState (t+1, hl, testsvar), ()))) >> f

newTestSVar :: a -> TestSequence a (TestSVar a)
newTestSVar var = TestSequence $
  \(TestSequenceState (timer, hl, Nothing)) ->
   (TestSequenceState (timer+1, hl, Just var), TestSVar var)

enterTestSVar :: TestSVar a -> (a -> TestSequence a (a,b)) -> TestSequence a b
enterTestSVar testsvar fun = do
  teststate <- readTestSVar testsvar
  (teststate',passalong) <- fun teststate
  putTestSVar testsvar teststate'
  return passalong

-- 'putTestSVar' is used along with 'readTestSVar' to implement enterTestSVar.
--
putTestSVar :: TestSVar a -> a -> TestSequence a a
putTestSVar _testsvar testsvar' = TestSequence $
  \(TestSequenceState (timer, hl, testsvar)) ->
   (TestSequenceState (timer+1, (PutVar timer) : hl, Just testsvar'),
      case testsvar of
        Nothing -> testsvar'
        Just testsvar'' -> testsvar'')

readTestSVar :: TestSVar a -> TestSequence a a
readTestSVar _testsvar = TestSequence $
  \(TestSequenceState (timer, hl, Just testsvar)) ->
   (TestSequenceState (timer+1, (GetVar timer) : hl, Just testsvar), testsvar)

getCurrentTime :: TestSequence a Int
getCurrentTime = TestSequence $
  \(TestSequenceState (timer, hl, testsvar)) ->
   (TestSequenceState (timer+1, (GetTime timer) : hl, testsvar), fromIntegral timer)

readNumber :: TestSequence a Int
readNumber = TestSequence $
  \(TestSequenceState (timer, hl, testsvar)) ->
    let number = fromIntegral timer
     in (TestSequenceState (timer+1, (ReadNumber number) : hl, testsvar), number)

haveNumber :: Int -> TestSequence a ()
haveNumber number = TestSequence $
  \(TestSequenceState (timer, hl, testsvar)) ->
   (TestSequenceState (timer+1, (HaveNumber number) : hl, testsvar), ())