Skip to content

Commit fd8c058

Browse files
committed
Added tests for thread-pool pattern
Fixed concurrency problem in id generation of Task
1 parent 47709e2 commit fd8c058

File tree

8 files changed

+198
-5
lines changed

8 files changed

+198
-5
lines changed

thread-pool/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,10 @@
1414
<artifactId>junit</artifactId>
1515
<scope>test</scope>
1616
</dependency>
17+
<dependency>
18+
<groupId>org.mockito</groupId>
19+
<artifactId>mockito-core</artifactId>
20+
<scope>test</scope>
21+
</dependency>
1722
</dependencies>
1823
</project>

thread-pool/src/main/java/com/iluwatar/threadpool/CoffeeMakingTask.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88
public class CoffeeMakingTask extends Task {
99

10-
private static final int TIME_PER_CUP = 300;
10+
private static final int TIME_PER_CUP = 100;
1111

1212
public CoffeeMakingTask(int numCups) {
1313
super(numCups * TIME_PER_CUP);

thread-pool/src/main/java/com/iluwatar/threadpool/PotatoPeelingTask.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88
public class PotatoPeelingTask extends Task {
99

10-
private static final int TIME_PER_POTATO = 500;
10+
private static final int TIME_PER_POTATO = 200;
1111

1212
public PotatoPeelingTask(int numPotatoes) {
1313
super(numPotatoes * TIME_PER_POTATO);

thread-pool/src/main/java/com/iluwatar/threadpool/Task.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
package com.iluwatar.threadpool;
22

3+
import java.util.concurrent.atomic.AtomicInteger;
4+
35
/**
4-
*
6+
*
57
* Abstract base class for tasks
68
*
79
*/
810
public abstract class Task {
911

10-
private static int nextId = 1;
12+
private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
1113

1214
private final int id;
1315
private final int timeMs;
1416

1517
public Task(final int timeMs) {
16-
this.id = nextId++;
18+
this.id = ID_GENERATOR.incrementAndGet();
1719
this.timeMs = timeMs;
1820
}
1921

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.iluwatar.threadpool;
2+
3+
/**
4+
* Date: 12/30/15 - 18:23 PM
5+
*
6+
* @author Jeroen Meulemeester
7+
*/
8+
public class CoffeeMakingTaskTest extends TaskTest<CoffeeMakingTask> {
9+
10+
/**
11+
* Create a new test instance
12+
*/
13+
public CoffeeMakingTaskTest() {
14+
super(CoffeeMakingTask::new, 100);
15+
}
16+
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.iluwatar.threadpool;
2+
3+
/**
4+
* Date: 12/30/15 - 18:23 PM
5+
*
6+
* @author Jeroen Meulemeester
7+
*/
8+
public class PotatoPeelingTaskTest extends TaskTest<PotatoPeelingTask> {
9+
10+
/**
11+
* Create a new test instance
12+
*/
13+
public PotatoPeelingTaskTest() {
14+
super(PotatoPeelingTask::new, 200);
15+
}
16+
17+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package com.iluwatar.threadpool;
2+
3+
import org.junit.Test;
4+
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
import java.util.Objects;
8+
import java.util.concurrent.Callable;
9+
import java.util.concurrent.ExecutionException;
10+
import java.util.concurrent.ExecutorService;
11+
import java.util.concurrent.Executors;
12+
import java.util.concurrent.Future;
13+
import java.util.function.Function;
14+
import java.util.stream.Collectors;
15+
16+
import static org.junit.Assert.assertEquals;
17+
import static org.junit.Assert.assertNotNull;
18+
19+
/**
20+
* Date: 12/30/15 - 18:22 PM
21+
*
22+
* @author Jeroen Meulemeester
23+
*/
24+
public abstract class TaskTest<T extends Task> {
25+
26+
/**
27+
* The number of tasks used during the concurrency test
28+
*/
29+
private static final int TASK_COUNT = 128 * 1024;
30+
31+
/**
32+
* The number of threads used during the concurrency test
33+
*/
34+
private static final int THREAD_COUNT = 8;
35+
36+
/**
37+
* The task factory, used to create new test items
38+
*/
39+
private final Function<Integer, T> factory;
40+
41+
/**
42+
* The expected time needed to run the task 1 single time, in milli seconds
43+
*/
44+
private final int expectedExecutionTime;
45+
46+
/**
47+
* Create a new test instance
48+
*
49+
* @param factory The task factory, used to create new test items
50+
* @param expectedExecutionTime The expected time needed to run the task 1 time, in milli seconds
51+
*/
52+
public TaskTest(final Function<Integer, T> factory, final int expectedExecutionTime) {
53+
this.factory = factory;
54+
this.expectedExecutionTime = expectedExecutionTime;
55+
}
56+
57+
/**
58+
* Verify if the generated id is unique for each task, even if the tasks are created in separate
59+
* threads
60+
*/
61+
@Test(timeout = 10000)
62+
public void testIdGeneration() throws Exception {
63+
final ExecutorService service = Executors.newFixedThreadPool(THREAD_COUNT);
64+
65+
final List<Callable<Integer>> tasks = new ArrayList<>();
66+
for (int i = 0; i < TASK_COUNT; i++) {
67+
tasks.add(() -> factory.apply(1).getId());
68+
}
69+
70+
final List<Integer> ids = service.invokeAll(tasks)
71+
.stream()
72+
.map(TaskTest::get)
73+
.filter(Objects::nonNull)
74+
.collect(Collectors.toList());
75+
76+
service.shutdownNow();
77+
78+
final long uniqueIdCount = ids.stream()
79+
.distinct()
80+
.count();
81+
82+
assertEquals(TASK_COUNT, ids.size());
83+
assertEquals(TASK_COUNT, uniqueIdCount);
84+
85+
}
86+
87+
/**
88+
* Verify if the time per execution of a task matches the actual time required to execute the task
89+
* a given number of times
90+
*/
91+
@Test
92+
public void testTimeMs() {
93+
for (int i = 0; i < 10; i++) {
94+
assertEquals(this.expectedExecutionTime * i, this.factory.apply(i).getTimeMs());
95+
}
96+
}
97+
98+
/**
99+
* Verify if the task has some sort of {@link T#toString()}, different from 'null'
100+
*/
101+
@Test
102+
public void testToString() {
103+
assertNotNull(this.factory.apply(0).toString());
104+
}
105+
106+
/**
107+
* Extract the result from a future or returns 'null' when an exception occurred
108+
*
109+
* @param future The future we want the result from
110+
* @param <O> The result type
111+
* @return The result or 'null' when a checked exception occurred
112+
*/
113+
private static <O> O get(Future<O> future) {
114+
try {
115+
return future.get();
116+
} catch (InterruptedException | ExecutionException e) {
117+
return null;
118+
}
119+
}
120+
121+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.iluwatar.threadpool;
2+
3+
import org.junit.Test;
4+
5+
import static org.mockito.Mockito.mock;
6+
import static org.mockito.Mockito.verify;
7+
import static org.mockito.Mockito.verifyNoMoreInteractions;
8+
import static org.mockito.Mockito.verifyZeroInteractions;
9+
10+
/**
11+
* Date: 12/30/15 - 18:21 PM
12+
*
13+
* @author Jeroen Meulemeester
14+
*/
15+
public class WorkerTest {
16+
17+
/**
18+
* Verify if a worker does the actual job
19+
*/
20+
@Test
21+
public void testRun() {
22+
final Task task = mock(Task.class);
23+
final Worker worker = new Worker(task);
24+
verifyZeroInteractions(task);
25+
26+
worker.run();
27+
verify(task).getTimeMs();
28+
verifyNoMoreInteractions(task);
29+
}
30+
31+
}

0 commit comments

Comments
 (0)