Skip to content

Commit 7979479

Browse files
committed
Add AtomicMapSuite
1 parent 3d12d5c commit 7979479

File tree

1 file changed

+176
-0
lines changed

1 file changed

+176
-0
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* Copyright 2020-2025 Typelevel
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package cats
18+
package effect
19+
package std
20+
21+
import cats.syntax.all._
22+
import scala.concurrent.duration._
23+
24+
class AtomicMapSuite extends BaseSuite {
25+
tests("ConcurrentAtomicMap", AtomicMap.apply[IO, Int, Int])
26+
27+
def tests(name: String, atomicMap: IO[AtomicMap[IO, Int, Option[Int]]]) = {
28+
real(
29+
s"${name} should getAndSet successfully if the given key is free"
30+
) {
31+
val p = for {
32+
am <- atomicMap
33+
cell = am(key = 1)
34+
getAndSetResult <- cell.getAndSet(Some(1))
35+
getResult <- cell.get
36+
} yield getAndSetResult == None &&
37+
getResult == Some(1)
38+
39+
p.mustEqual(true)
40+
}
41+
42+
ticked(
43+
s"${name} get should not block during concurrent modification of the same key"
44+
) { implicit ticker =>
45+
val p = for {
46+
am <- atomicMap
47+
cell = am(key = 1)
48+
gate <- IO.deferred[Unit]
49+
_ <- cell.evalModify(_ => gate.complete(()) *> IO.never).start
50+
_ <- gate.get
51+
getResult <- cell.get
52+
} yield getResult == None
53+
54+
assertCompleteAs(p, true)
55+
}
56+
57+
ticked(
58+
s"${name} should block action if not free in the given key"
59+
) { implicit ticker =>
60+
val p = atomicMap.flatMap { am =>
61+
val cell = am(key = 1)
62+
63+
cell.evalUpdate(_ => IO.never) >>
64+
cell.evalUpdate(IO.pure)
65+
}
66+
67+
assertNonTerminate(p)
68+
}
69+
70+
ticked(
71+
s"${name} should not block action if using a different key"
72+
) { implicit ticker =>
73+
val p = atomicMap.flatMap { am =>
74+
val cell1 = am(key = 1)
75+
val cell2 = am(key = 2)
76+
77+
IO.race(
78+
cell1.evalUpdate(_ => IO.never),
79+
cell2.evalUpdate(IO.pure)
80+
).void
81+
}
82+
83+
assertCompleteAs(p, ())
84+
}
85+
86+
ticked(
87+
s"${name} should support concurrent usage in the same key"
88+
) { implicit ticker =>
89+
val p = atomicMap.flatMap { am =>
90+
val cell = am(key = 1)
91+
val usage = IO.sleep(1.second) >> cell.evalUpdate(IO.pure(_).delayBy(1.second))
92+
93+
(usage, usage).parTupled.void
94+
}
95+
96+
assertCompleteAs(p, ())
97+
}
98+
99+
ticked(
100+
s"${name} should support concurrent usage in different keys"
101+
) { implicit ticker =>
102+
val p = atomicMap.flatMap { am =>
103+
def usage(key: Int): IO[Unit] = {
104+
val cell = am(key)
105+
IO.sleep(1.second) >> cell.evalUpdate(IO.pure(_).delayBy(1.second))
106+
}
107+
108+
List.range(start = 1, end = 10).parTraverse_(usage)
109+
}
110+
111+
assertCompleteAs(p, ())
112+
}
113+
114+
real(
115+
s"${name} should support OptionOps"
116+
) {
117+
val p = for {
118+
am <- atomicMap
119+
key = 1
120+
default = 0
121+
value = 1
122+
updateFunction = (x: Int) => x + 1
123+
cell = am(key)
124+
cellGetResultBeforeSetValue <- cell.get
125+
amGetOrElseResultBeforeSetValue <- am.getOrElse(key, default)
126+
_ <- am.setValue(key, value)
127+
cellGetResultAfterSetValue <- cell.get
128+
amGetOrElseResultAfterSetValue <- am.getOrElse(key, default)
129+
_ <- am.updateValueIfSet(key)(updateFunction)
130+
cellGetResultAfterUpdate <- cell.get
131+
amGetOrElseResultAfterUpdate <- am.getOrElse(key, default)
132+
_ <- am.unsetKey(key)
133+
cellGetResultAfterUnsetKey <- cell.get
134+
amGetOrElseResultAfterUnsetKey <- am.getOrElse(key, default)
135+
} yield cellGetResultBeforeSetValue == None &&
136+
amGetOrElseResultBeforeSetValue == default &&
137+
cellGetResultAfterSetValue == Some(value) &&
138+
amGetOrElseResultAfterSetValue == value &&
139+
cellGetResultAfterUpdate == Some(updateFunction(value)) &&
140+
amGetOrElseResultAfterUpdate == updateFunction(value) &&
141+
cellGetResultAfterUnsetKey == None &&
142+
amGetOrElseResultAfterUnsetKey == default
143+
144+
p.mustEqual(true)
145+
}
146+
147+
real(
148+
s"A defaulted ${name} cell should be consistent with its underlying AtomicCell"
149+
) {
150+
val p = for {
151+
am <- atomicMap
152+
default = 0
153+
key = 1
154+
value = 1
155+
cell = am(key)
156+
defaultedAM = AtomicMap.defaultedAtomicMap(am, default)
157+
defaultedCell = defaultedAM(key)
158+
cellGetResultBeforeModification <- cell.get
159+
defaultedCellGetResultBeforeModification <- defaultedCell.get
160+
_ <- defaultedCell.set(value)
161+
cellGetResultAfterModification <- cell.get
162+
defaultedCellGetResultAfterModification <- defaultedCell.get
163+
_ <- defaultedCell.set(default)
164+
cellGetResultAfterClear <- cell.get
165+
defaultedCellGetResultAfterClear <- defaultedCell.get
166+
} yield cellGetResultBeforeModification == None &&
167+
defaultedCellGetResultBeforeModification == default &&
168+
cellGetResultAfterModification == Some(value) &&
169+
defaultedCellGetResultAfterModification == value &&
170+
cellGetResultAfterClear == None &&
171+
defaultedCellGetResultAfterClear == default
172+
173+
p.mustEqual(true)
174+
}
175+
}
176+
}

0 commit comments

Comments
 (0)