1/*
2 * Copyright (C) 2007 The Android Open Source Project
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
17package tests.api.org.apache.harmony.kernel.dalvik;
18
19import java.lang.reflect.Field;
20
21import junit.framework.Assert;
22import junit.framework.TestCase;
23import sun.misc.Unsafe;
24import dalvik.annotation.AndroidOnly;
25import dalvik.annotation.TestLevel;
26import dalvik.annotation.TestTargetClass;
27import dalvik.annotation.TestTargetNew;
28
29/**
30 * Tests for the <code>park()</code> functionality of {@link Unsafe}.
31 */
32@TestTargetClass(Unsafe.class)
33public class ThreadsTest extends TestCase {
34    private static Unsafe UNSAFE = null;
35    private static RuntimeException INITIALIZEFAILED = null;
36
37    static {
38        /*
39         * Set up {@link #UNSAFE}. This subverts the access check to
40         * get the unique Unsafe instance. We can do this because
41         * there's no security manager installed when running the
42         * test.
43         */
44        try {
45            Field field = Unsafe.class.getDeclaredField("THE_ONE");
46            field.setAccessible(true);
47
48            UNSAFE = (Unsafe) field.get(null);
49        } catch (NoSuchFieldException ex) {
50            INITIALIZEFAILED = new RuntimeException(ex);
51        } catch (IllegalAccessException ex) {
52            INITIALIZEFAILED = new RuntimeException(ex);
53        }
54    }
55
56    /** Test the case where the park times out. */
57    @TestTargetNew(
58        level = TestLevel.PARTIAL,
59        notes = "",
60        method = "unpark",
61        args = {Object.class}
62    )
63    @AndroidOnly("Accesses Android-specific private field")
64    public void test_parkFor_1() {
65        Parker parker = new Parker(false, 500);
66        Thread parkerThread = new Thread(parker);
67        Thread waiterThread =
68            new Thread(new WaitAndUnpark(1000, parkerThread));
69
70        parkerThread.start();
71        waiterThread.start();
72        parker.assertDurationIsInRange(500);
73    }
74
75    /** Test the case where the unpark happens before the timeout. */
76    @TestTargetNew(
77        level = TestLevel.PARTIAL,
78        notes = "",
79        method = "unpark",
80        args = {Object.class}
81    )
82    @AndroidOnly("Accesses Android-specific private field")
83    public void test_parkFor_2() {
84        Parker parker = new Parker(false, 1000);
85        Thread parkerThread = new Thread(parker);
86        Thread waiterThread =
87            new Thread(new WaitAndUnpark(300, parkerThread));
88
89        parkerThread.start();
90        waiterThread.start();
91        parker.assertDurationIsInRange(300);
92    }
93
94    /** Test the case where the thread is preemptively unparked. */
95    @TestTargetNew(
96        level = TestLevel.PARTIAL,
97        notes = "",
98        method = "unpark",
99        args = {Object.class}
100    )
101    @AndroidOnly("Accesses Android-specific private field")
102    public void test_parkFor_3() {
103        Parker parker = new Parker(false, 1000);
104        Thread parkerThread = new Thread(parker);
105
106        UNSAFE.unpark(parkerThread);
107        parkerThread.start();
108        parker.assertDurationIsInRange(0);
109    }
110
111    /** Test the case where the park times out. */
112    @TestTargetNew(
113        level = TestLevel.PARTIAL,
114        notes = "",
115        method = "unpark",
116        args = {Object.class}
117    )
118    @AndroidOnly("Accesses Android-specific private field")
119    public void test_parkUntil_1() {
120        Parker parker = new Parker(true, 500);
121        Thread parkerThread = new Thread(parker);
122        Thread waiterThread =
123            new Thread(new WaitAndUnpark(1000, parkerThread));
124
125        parkerThread.start();
126        waiterThread.start();
127        parker.assertDurationIsInRange(500);
128    }
129
130    /** Test the case where the unpark happens before the timeout. */
131    @TestTargetNew(
132        level = TestLevel.PARTIAL,
133        notes = "",
134        method = "unpark",
135        args = {Object.class}
136    )
137    @AndroidOnly("Accesses Android-specific private field")
138    public void test_parkUntil_2() {
139        Parker parker = new Parker(true, 1000);
140        Thread parkerThread = new Thread(parker);
141        Thread waiterThread =
142            new Thread(new WaitAndUnpark(300, parkerThread));
143
144        parkerThread.start();
145        waiterThread.start();
146        parker.assertDurationIsInRange(300);
147    }
148
149    /** Test the case where the thread is preemptively unparked. */
150    @TestTargetNew(
151        level = TestLevel.PARTIAL,
152        notes = "",
153        method = "unpark",
154        args = {Object.class}
155    )
156    @AndroidOnly("Accesses Android-specific private field")
157    public void test_parkUntil_3() {
158        Parker parker = new Parker(true, 1000);
159        Thread parkerThread = new Thread(parker);
160
161        UNSAFE.unpark(parkerThread);
162        parkerThread.start();
163        parker.assertDurationIsInRange(0);
164    }
165
166    // TODO: Add more tests.
167
168    /**
169     * Helper <code>Runnable</code> for tests, which parks for or until
170     * the indicated value, noting the duration of time actually parked.
171     */
172    private static class Parker implements Runnable {
173        /** whether {@link #amount} is milliseconds to wait in an
174         * absolute fashion (<code>true</code>) or nanoseconds to wait
175         * in a relative fashion (<code>false</code>) */
176        private final boolean absolute;
177
178        /** amount to wait (see above) */
179        private final long amount;
180
181        /** whether the run has completed */
182        private boolean completed;
183
184        /** recorded start time */
185        private long startMillis;
186
187        /** recorded end time */
188        private long endMillis;
189
190        /**
191         * Construct an instance.
192         *
193         * @param absolute whether to use an absolute time or not; in
194         * either case, this constructor takes a duration to park for
195         * @param parkMillis the number of milliseconds to be parked
196         */
197        public Parker(boolean absolute, long parkMillis) {
198            this.absolute = absolute;
199
200            // Multiply by 1000000 because parkFor() takes nanoseconds.
201            this.amount = absolute ? parkMillis : parkMillis * 1000000;
202        }
203
204        public void run() {
205            boolean absolute = this.absolute;
206            long amount = this.amount;
207            long start = System.currentTimeMillis();
208
209            if (absolute) {
210                UNSAFE.park(true, start + amount);
211            } else {
212                UNSAFE.park(false, amount);
213            }
214
215            long end = System.currentTimeMillis();
216
217            synchronized (this) {
218                startMillis = start;
219                endMillis = end;
220                completed = true;
221                notifyAll();
222            }
223        }
224
225        /**
226         * Wait for the test to complete and return the duration.
227         *
228         * @param maxWaitMillis the maximum amount of time to
229         * wait for the test to complete
230         * @return the duration in milliseconds
231         */
232        public long getDurationMillis(long maxWaitMillis) {
233            synchronized (this) {
234                if (! completed) {
235                    try {
236                        wait(maxWaitMillis);
237                    } catch (InterruptedException ex) {
238                        // Ignore it.
239                    }
240                    if (! completed) {
241                        Assert.fail("parker hanging");
242                    }
243                }
244
245                return endMillis - startMillis;
246            }
247        }
248
249        /**
250         * Asserts that the actual duration is within 5% of the
251         * given expected time.
252         *
253         * @param expectedMillis the expected duration, in milliseconds
254         */
255        public void assertDurationIsInRange(long expectedMillis) {
256            /*
257             * Allow a bit more slop for the maximum on "expected
258             * instantaneous" results.
259             */
260            long minimum = (long) ((double) expectedMillis * 0.95);
261            long maximum =
262                Math.max((long) ((double) expectedMillis * 1.05), 10);
263            long waitMillis = Math.max(expectedMillis * 10, 10);
264            long duration = getDurationMillis(waitMillis);
265
266            if (duration < minimum) {
267                Assert.fail("expected duration: " + expectedMillis +
268                        "; actual too short: " + duration);
269            } else if (duration > maximum) {
270                Assert.fail("expected duration: " + expectedMillis +
271                        "; actual too long: " + duration);
272            }
273        }
274    }
275
276    /**
277     * Helper <code>Runnable</code> for tests, which waits for the
278     * specified amount of time and then unparks an indicated thread.
279     */
280    private static class WaitAndUnpark implements Runnable {
281        private final long waitMillis;
282        private final Thread thread;
283
284        public WaitAndUnpark(long waitMillis, Thread thread) {
285            this.waitMillis = waitMillis;
286            this.thread = thread;
287        }
288
289        public void run() {
290            try {
291                Thread.sleep(waitMillis);
292            } catch (InterruptedException ex) {
293                throw new RuntimeException("shouldn't happen", ex);
294            }
295
296            UNSAFE.unpark(thread);
297        }
298    }
299
300    @Override
301    protected void setUp() throws Exception {
302        if (INITIALIZEFAILED != null) {
303            throw INITIALIZEFAILED;
304        }
305    }
306}
307