(no commit message)
[utils] / support / general / src / test / java / org / wamblee / concurrency / ReadWriteLockProxyFactoryTest.java
1 package org.wamblee.concurrency;
2
3 import static junit.framework.Assert.*;
4 import static org.mockito.Matchers.*;
5 import static org.mockito.Mockito.*;
6
7 import java.util.ArrayList;
8 import java.util.List;
9 import java.util.Timer;
10 import java.util.TimerTask;
11 import java.util.concurrent.atomic.AtomicInteger;
12
13 import org.junit.Before;
14 import org.junit.Test;
15 import org.mockito.invocation.InvocationOnMock;
16 import org.mockito.stubbing.Answer;
17
18 public class ReadWriteLockProxyFactoryTest {
19
20     /**
21      * This test works with time intervals of this size. 
22      */
23     private static final int INTERVAL_MILLIS = 10;
24
25     public static interface X {
26         void doX(int aValue);
27
28         void doNoLocking();
29     }
30
31     public static interface Y {
32         void doY(String aValue);
33     }
34
35     public static interface Z extends X, Y {
36
37     }
38
39     private final Runnable doX = new Runnable() {
40         public void run() {
41             proxyX.doX(10);
42         }
43     };
44
45     private final Runnable doNoLock = new Runnable() {
46         public void run() {
47             proxyX.doNoLocking();
48         }
49     };
50
51     private final Runnable doY = new Runnable() {
52         public void run() {
53             proxyY.doY("hello");
54         }
55     };
56
57     private ReadWriteLockProxyFactory<Z> factory;
58     private Z service;
59     private X proxyX;
60     private Y proxyY;
61     private boolean threadFailed;
62     private Timer timer;
63     private List<Thread> threads;
64     private long tstart;
65
66     @Before
67     public void setUp() {
68         factory = new ReadWriteLockProxyFactory<ReadWriteLockProxyFactoryTest.Z>();
69
70         threadFailed = false;
71         timer = new Timer();
72         threads = new ArrayList<Thread>();
73     }
74
75     private void startTiming() {
76         tstart = System.currentTimeMillis();
77     }
78
79     private float endTiming() {
80         return (float) (System.currentTimeMillis() - tstart) / INTERVAL_MILLIS;
81     }
82
83     private void sleep(int aUnits) {
84         try {
85             Thread.sleep(aUnits * INTERVAL_MILLIS);
86         } catch (InterruptedException e) {
87             threadFailed = true;
88         }
89     }
90
91     private void schedule(final AtomicInteger aUnstartedThreads,
92         final Thread aThread, int aUnits) {
93         aUnstartedThreads.incrementAndGet();
94         timer.schedule(new TimerTask() {
95             @Override
96             public void run() {
97                 aThread.start();
98                 aUnstartedThreads.decrementAndGet();
99             }
100         }, aUnits * INTERVAL_MILLIS);
101     }
102
103     private void schedule(AtomicInteger aUnstartedThreads, Runnable aTask,
104         int aUnits) {
105         Thread t = new Thread(aTask);
106         schedule(aUnstartedThreads, t, aUnits);
107         threads.add(t);
108     }
109
110     private void join(AtomicInteger aUnstartedThreads, List<Thread> aThreads) {
111         while (aUnstartedThreads.get() > 0) {
112             sleep(1);
113         }
114         for (Thread t : aThreads) {
115             try {
116                 t.join();
117             } catch (InterruptedException e) {
118                 e.printStackTrace();
119                 threadFailed = true;
120             }
121         }
122         assertFalse(threadFailed);
123     }
124
125     private void join(AtomicInteger aUnstartedThreads) {
126         join(aUnstartedThreads, threads);
127     }
128
129     private void stubDelays() {
130         service = new Z() {
131             @ReadLock
132             public void doX(int aValue) {
133                 sleep(10);
134             }
135
136             @WriteLock
137             public void doY(String aValue) {
138                 sleep(10);
139             }
140
141             @Override
142             public void doNoLocking() {
143                 sleep(10);
144             }
145         };
146
147         createProxy();
148         Answer sleep = new Answer() {
149             @Override
150             public Object answer(InvocationOnMock aInvocation) throws Throwable {
151                 sleep(10);
152                 return null;
153             }
154         };
155     }
156
157     @Test
158     public void testProxyDelegates() {
159         service = mock(Z.class);
160         createProxy();
161         assertTrue(proxyX instanceof X);
162         assertTrue(proxyY instanceof Y);
163
164         proxyX.doX(10);
165         verify(service).doX(10);
166         reset(service);
167
168         proxyY.doY("hello");
169         verify(service).doY("hello");
170         reset(service);
171     }
172
173     private void createProxy() {
174         proxyX = factory.getProxy(service, X.class, Y.class);
175         proxyY = (Y) proxyX;
176     }
177
178     @Test
179     public void testConcurrentReadCalls() {
180         stubDelays();
181         startTiming();
182         final int n = 4;
183         AtomicInteger unstarted = new AtomicInteger();
184         for (int i = 0; i < n; i++) {
185             schedule(unstarted, doX, 0);
186         }
187         join(unstarted);
188         float duration = endTiming();
189         assertTrue(duration < 15);
190     }
191
192     @Test
193     public void testNoConcurrentWrites() {
194         stubDelays();
195         startTiming();
196         final int n = 2;
197         AtomicInteger unstarted = new AtomicInteger();
198         for (int i = 0; i < n; i++) {
199             schedule(unstarted, doY, 0);
200         }
201         join(unstarted);
202         float duration = endTiming();
203         System.out.println("no concurrent writes: duration " + duration);
204         assertTrue(duration >= n * 10);
205     }
206
207     @Test
208     public void testConcurrentWriteAndNoLock() {
209         stubDelays();
210         startTiming();
211         AtomicInteger unstarted = new AtomicInteger();
212
213         schedule(unstarted, doY, 0);
214         for (int i = 0; i < 10; i++) {
215             schedule(unstarted, doNoLock, 0);
216         }
217         join(unstarted);
218         float duration = endTiming();
219         System.out.println("concurrent write and no lock: duration: " +
220             duration);
221         assertTrue(duration < 15);
222     }
223
224     @Test
225     public void testNoConcurrentReadAndWrite() {
226         stubDelays();
227         startTiming();
228
229         AtomicInteger unstartedReaders = new AtomicInteger();
230         for (int i = 0; i < 4; i++) {
231             schedule(unstartedReaders, doX, 0);
232         }
233         List<Thread> readers = threads;
234         threads = new ArrayList<Thread>();
235         // start the write some time later.
236         AtomicInteger unstartedWriters = new AtomicInteger();
237         schedule(unstartedWriters, doY, 5);
238
239         join(unstartedReaders, readers);
240         float duration = endTiming();
241         System.out.println("no concurrent read and write: readers duration: " +
242             duration);
243         assertTrue(duration < 15);
244         join(unstartedWriters, threads);
245         duration = endTiming();
246         System.out.println("no concurrent read and write: writer duration: " +
247             duration);
248         assertTrue(duration >= 20 && duration <= 25);
249     }
250 }