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