-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathThreadSafeCountedHolderTests.kt
153 lines (141 loc) · 5.16 KB
/
ThreadSafeCountedHolderTests.kt
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
package pt.isel.pc.problemsets.set2
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.junit.jupiter.api.RepeatedTest
import org.junit.jupiter.api.Test
import pt.isel.pc.problemsets.utils.MultiThreadTestHelper
import pt.isel.pc.problemsets.utils.randomTo
import java.io.Closeable
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertSame
import kotlin.time.Duration.Companion.seconds
internal class ThreadSafeCountedHolderTests {
private class TestResource : Closeable {
val closedCounter = AtomicInteger(0)
override fun close() {
// increment the counter each time the close method is called
closedCounter.incrementAndGet()
}
}
// tests without concurrency stress:
@Test
fun `Holder closes a resource that was not used before`() {
val resource = TestResource()
val holder = ThreadSafeCountedHolder(resource)
holder.endUse()
assertEquals(1, resource.closedCounter.get())
}
@Test
fun `Holder closes a resource after the usage counter reaches zero`() {
val resource = TestResource()
val holder = ThreadSafeCountedHolder(resource)
holder.endUse()
assertFailsWith<IllegalStateException> {
holder.endUse()
}
}
@Test
fun `Use a resource that was closed prior`() {
val resource = TestResource()
val holder = ThreadSafeCountedHolder(resource)
holder.endUse()
assertNull(holder.tryStartUse())
}
@Test
fun `Holder allows the usage of a resource once`() {
val resource = TestResource()
val holder = ThreadSafeCountedHolder(resource)
// should be the same instance
assertSame(resource, holder.tryStartUse())
}
// tests with concurrency stress:
@Test
fun `Several threads try to use the resource`() {
val resource = TestResource()
val holder = ThreadSafeCountedHolder(resource)
val testHelper = MultiThreadTestHelper(10.seconds)
val nrThreads = 5 randomTo 15
testHelper.createAndStartMultipleThreads(nrThreads) { _, _ ->
assertNotNull(holder.tryStartUse())
}
testHelper.join()
}
@RepeatedTest(3)
fun `Several threads try to close a resource`() {
val resource = TestResource()
val holder = ThreadSafeCountedHolder(resource)
val testHelper = MultiThreadTestHelper(10.seconds)
val nrThreads = 10 randomTo 24
val counter = AtomicInteger(0)
val exceptionCounter = AtomicInteger(0)
testHelper.createAndStartMultipleThreads(nrThreads) { _, _ ->
// only one thread will not throw exception
runCatching {
holder.endUse()
}.onSuccess {
counter.getAndIncrement()
}.onFailure {
exceptionCounter.getAndIncrement()
}
}
testHelper.join()
assertEquals(1, counter.get())
assertEquals(nrThreads - 1, exceptionCounter.get())
}
@RepeatedTest(3)
fun `Several threads use the resource and try to end it's usage several times, always succeding`() {
val resource = TestResource()
val holder = ThreadSafeCountedHolder(resource)
val testHelper = MultiThreadTestHelper(10.seconds)
val nrThreads = 10 randomTo 24
val exceptionCounter = AtomicInteger(0)
testHelper.createAndStartMultipleThreads(nrThreads) { _, _ ->
val value = holder.tryStartUse()
assertNotNull(value)
runCatching {
holder.endUse()
}.onFailure {
exceptionCounter.getAndIncrement()
}
}
testHelper.join()
assertEquals(0, exceptionCounter.get())
}
@RepeatedTest(3)
fun `A thread uses the resource several times and for the same amount of times threads try to end it's usage`() {
val resource = TestResource()
val holder = ThreadSafeCountedHolder(resource)
val testHelper = MultiThreadTestHelper(10.seconds)
val nrOfUsages = 500 randomTo 1000
val exceptionCounter = AtomicInteger(0)
// this thread uses the value several times
val th1 = testHelper.createAndStartThread {
repeat(nrOfUsages) {
holder.tryStartUse()
}
}
th1.join()
// this threads try to decrement its usage counter
testHelper.createAndStartMultipleThreads(nrOfUsages + 1) { _, _ ->
runCatching {
holder.endUse()
}.onFailure {
exceptionCounter.getAndIncrement()
}
}
testHelper.join()
assertEquals(nrOfUsages, exceptionCounter.get())
// ensure the resource is only closed once
assertEquals(1, resource.closedCounter.get())
assertFailsWith<IllegalStateException> {
holder.endUse()
}
assertNull(holder.tryStartUse())
}
}