jgish | 1cb7fcf | 2012-10-29 16:51:59 -0700 | [diff] [blame] | 1 | /* |
katleman | d08780c | 2012-12-20 16:24:50 -0800 | [diff] [blame^] | 2 | * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. |
jgish | 1cb7fcf | 2012-10-29 16:51:59 -0700 | [diff] [blame] | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| 4 | * |
| 5 | * This code is free software; you can redistribute it and/or modify it |
| 6 | * under the terms of the GNU General Public License version 2 only, as |
| 7 | * published by the Free Software Foundation. |
| 8 | * |
| 9 | * This code is distributed in the hope that it will be useful, but WITHOUT |
| 10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 12 | * version 2 for more details (a copy is included in the LICENSE file that |
| 13 | * accompanied this code). |
| 14 | * |
| 15 | * You should have received a copy of the GNU General Public License version |
| 16 | * 2 along with this work; if not, write to the Free Software Foundation, |
| 17 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| 18 | * |
| 19 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| 20 | * or visit www.oracle.com if you need additional information or have any |
| 21 | * questions. |
| 22 | */ |
| 23 | |
| 24 | /* @test |
| 25 | * @bug 6206780 |
| 26 | * @summary Test that all public unsynchronized methods of StringBuffer are either directly or indirectly synchronized |
| 27 | */ |
| 28 | import java.lang.reflect.Constructor; |
| 29 | import java.lang.reflect.InvocationTargetException; |
| 30 | import java.lang.reflect.Method; |
| 31 | import java.lang.reflect.Modifier; |
| 32 | import java.util.ArrayList; |
| 33 | import java.util.Arrays; |
| 34 | import java.util.List; |
| 35 | |
| 36 | /** |
| 37 | * TestSynchronization tests whether synchronized methods calls on an object |
| 38 | * result in synchronized calls. Note that this may not test all cases desired. |
| 39 | * It only tests whether some synchronization has occurred on the object during |
| 40 | * the call chain, and can't tell whether the object was locked across all |
| 41 | * operations that have been performed on the object. |
| 42 | */ |
| 43 | public class TestSynchronization { |
| 44 | |
| 45 | /** |
| 46 | * Define parameters used in methods of StringBuffer - admittedly a bit of |
| 47 | * hack but 'purpose-built' for StringBuffer. Something more general could |
| 48 | * probably be developed if the test needs to be more widely adopted. |
| 49 | * <p/> |
| 50 | * boolean char char[] int double float long Object CharSequence String |
| 51 | * StringBuffer StringBuilder |
| 52 | * <p/> |
| 53 | */ |
| 54 | private static final boolean BOOLEAN_VAL = true; |
| 55 | private static final char CHAR_VAL = 'x'; |
| 56 | private static final char[] CHAR_ARRAY_VAL = {'c', 'h', 'a', 'r', 'a', 'r', |
| 57 | 'r', 'a', 'y'}; |
| 58 | private static final int INT_VAL = 1; |
| 59 | private static final double DOUBLE_VAL = 1.0d; |
| 60 | private static final float FLOAT_VAL = 1.0f; |
| 61 | private static final long LONG_VAL = 1L; |
| 62 | private static final Object OBJECT_VAL = new Object(); |
| 63 | private static final String STRING_VAL = "String value"; |
| 64 | private static final StringBuilder STRING_BUILDER_VAL = |
| 65 | new StringBuilder("StringBuilder value"); |
| 66 | private static final StringBuffer STRING_BUFFER_VAL = |
| 67 | new StringBuffer("StringBuffer value"); |
| 68 | private static final CharSequence[] CHAR_SEQUENCE_VAL = {STRING_VAL, |
| 69 | STRING_BUILDER_VAL, STRING_BUFFER_VAL}; |
| 70 | |
| 71 | public static void main(String... args) throws Exception { |
| 72 | // First, test the tester |
| 73 | testClass(MyTestClass.class, /* |
| 74 | * self-test |
| 75 | */ true); |
| 76 | // Finally, test StringBuffer |
| 77 | testClass(StringBuffer.class, /* |
| 78 | * self-test |
| 79 | */ false); |
| 80 | } |
| 81 | |
| 82 | /** |
| 83 | * Test all the public, unsynchronized methods of the given class. If |
| 84 | * isSelfTest is true, this is a self-test to ensure that the test program |
| 85 | * itself is working correctly. Should help ensure correctness of this |
| 86 | * program if it changes. |
| 87 | * <p/> |
| 88 | * @param aClass - the class to test |
| 89 | * @param isSelfTest - true if this is the special self-test class |
| 90 | * @throws SecurityException |
| 91 | */ |
| 92 | private static void testClass(Class<?> aClass, boolean isSelfTest) throws |
| 93 | Exception { |
| 94 | // Get all unsynchronized public methods via reflection. We don't need |
| 95 | // to test synchronized methods. By definition. they are already doing |
| 96 | // the right thing. |
| 97 | List<Method> methods = Arrays.asList(aClass.getDeclaredMethods()); |
| 98 | for (Method m : methods) { |
| 99 | int modifiers = m.getModifiers(); |
| 100 | if (Modifier.isPublic(modifiers) |
| 101 | && !Modifier.isSynchronized(modifiers)) { |
| 102 | try { |
| 103 | testMethod(aClass, m); |
| 104 | } catch (TestFailedException e) { |
| 105 | if (isSelfTest) { |
| 106 | String methodName = e.getMethod().getName(); |
| 107 | switch (methodName) { |
| 108 | case "should_pass": |
| 109 | throw new RuntimeException( |
| 110 | "Test failed: self-test failed. The 'should_pass' method did not pass the synchronization test. Check the test code."); |
| 111 | case "should_fail": |
| 112 | break; |
| 113 | default: |
| 114 | throw new RuntimeException( |
| 115 | "Test failed: something is amiss with the test. A TestFailedException was generated on a call to " |
| 116 | + methodName + " which we didn't expect to test in the first place."); |
| 117 | } |
| 118 | } else { |
| 119 | throw new RuntimeException("Test failed: the method " |
| 120 | + e.getMethod().toString() |
| 121 | + " should be synchronized, but isn't."); |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | private static void invokeMethod(Class<?> aClass, final Method m, |
| 129 | final Object[] args) throws TestFailedException, Exception { |
| 130 | //System.out.println( "Invoking " + m.toString() + " with parameters " + Arrays.toString(args)); |
| 131 | final Constructor<?> objConstructor; |
| 132 | Object obj = null; |
| 133 | |
| 134 | objConstructor = aClass.getConstructor(String.class); |
| 135 | obj = objConstructor.newInstance("LeftPalindrome-emordnilaP-thgiR"); |
| 136 | |
| 137 | // test method m for synchronization |
| 138 | if (!isSynchronized(m, obj, args)) { |
| 139 | throw new TestFailedException(m); |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | private static void testMethod(Class<?> aClass, Method m) throws |
| 144 | Exception { |
| 145 | /* |
| 146 | * Construct call with arguments of the correct type. Note that the |
| 147 | * values are somewhat irrelevant. If the call actually succeeds, it |
| 148 | * means we aren't synchronized and the test has failed. |
| 149 | */ |
| 150 | Class<?>[] pTypes = m.getParameterTypes(); |
| 151 | List<Integer> charSequenceArgs = new ArrayList<>(); |
| 152 | Object[] args = new Object[pTypes.length]; |
| 153 | for (int i = 0; i < pTypes.length; i++) { |
| 154 | // determine the type and create the corresponding actual argument |
| 155 | Class<?> pType = pTypes[i]; |
| 156 | if (pType.equals(boolean.class)) { |
| 157 | args[i] = BOOLEAN_VAL; |
| 158 | } else if (pType.equals(char.class)) { |
| 159 | args[i] = CHAR_VAL; |
| 160 | } else if (pType.equals(int.class)) { |
| 161 | args[i] = INT_VAL; |
| 162 | } else if (pType.equals(double.class)) { |
| 163 | args[i] = DOUBLE_VAL; |
| 164 | } else if (pType.equals(float.class)) { |
| 165 | args[i] = FLOAT_VAL; |
| 166 | } else if (pType.equals(long.class)) { |
| 167 | args[i] = LONG_VAL; |
| 168 | } else if (pType.equals(Object.class)) { |
| 169 | args[i] = OBJECT_VAL; |
| 170 | } else if (pType.equals(StringBuilder.class)) { |
| 171 | args[i] = STRING_BUILDER_VAL; |
| 172 | } else if (pType.equals(StringBuffer.class)) { |
| 173 | args[i] = STRING_BUFFER_VAL; |
| 174 | } else if (pType.equals(String.class)) { |
| 175 | args[i] = STRING_VAL; |
| 176 | } else if (pType.isArray() && pType.getComponentType().equals(char.class)) { |
| 177 | args[i] = CHAR_ARRAY_VAL; |
| 178 | } else if (pType.equals(CharSequence.class)) { |
| 179 | charSequenceArgs.add(new Integer(i)); |
| 180 | } else { |
| 181 | throw new RuntimeException("Test Failed: not accounting for method call with parameter type of " + pType.getName() + " You must update the test."); |
| 182 | } |
| 183 | } |
| 184 | /* |
| 185 | * If there are no CharSequence args, we can simply invoke our method |
| 186 | * and test it |
| 187 | */ |
| 188 | if (charSequenceArgs.isEmpty()) { |
| 189 | invokeMethod(aClass, m, args); |
| 190 | } else { |
| 191 | /* |
| 192 | * Iterate through the different CharSequence types and invoke the |
| 193 | * method for each type. |
| 194 | */ |
| 195 | if (charSequenceArgs.size() > 1) { |
| 196 | throw new RuntimeException("Test Failed: the test cannot handle a method with multiple CharSequence arguments. You must update the test to handle the method " |
| 197 | + m.toString()); |
| 198 | } |
| 199 | for (int j = 0; j < CHAR_SEQUENCE_VAL.length; j++) { |
| 200 | args[charSequenceArgs.get(0)] = CHAR_SEQUENCE_VAL[j]; |
| 201 | invokeMethod(aClass, m, args); |
| 202 | } |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | @SuppressWarnings("serial") |
| 207 | private static class TestFailedException extends Exception { |
| 208 | |
| 209 | final Method m; |
| 210 | |
| 211 | public Method getMethod() { |
| 212 | return m; |
| 213 | } |
| 214 | |
| 215 | public TestFailedException(Method m) { |
| 216 | this.m = m; |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | static class InvokeTask implements Runnable { |
| 221 | |
| 222 | private final Method m; |
| 223 | private final Object target; |
| 224 | private final Object[] args; |
| 225 | |
| 226 | InvokeTask(Method m, Object target, Object... args) { |
| 227 | this.m = m; |
| 228 | this.target = target; |
| 229 | this.args = args; |
| 230 | } |
| 231 | |
| 232 | @Override |
| 233 | public void run() { |
| 234 | try { |
| 235 | m.invoke(target, args); |
| 236 | } catch (IllegalAccessException | IllegalArgumentException | |
| 237 | InvocationTargetException e) { |
| 238 | e.printStackTrace(); |
| 239 | } |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | /** |
| 244 | * isSynchronized tests whether the given method is synchronized or not by |
| 245 | * invoking it in a thread and testing the thread state after starting the |
| 246 | * thread |
| 247 | * <p/> |
| 248 | * @param m the method to test |
| 249 | * @param target the object the method is executed on |
| 250 | * @param args the arguments passed to the method |
| 251 | * @return true iff the method is synchronized |
| 252 | */ |
| 253 | private static boolean isSynchronized(Method m, Object target, |
| 254 | Object... args) { |
| 255 | Thread t = new Thread(new InvokeTask(m, target, args)); |
| 256 | |
| 257 | Boolean isSynchronized = null; |
| 258 | |
| 259 | synchronized (target) { |
| 260 | t.start(); |
| 261 | |
| 262 | while (isSynchronized == null) { |
| 263 | switch (t.getState()) { |
| 264 | case NEW: |
| 265 | case RUNNABLE: |
| 266 | case WAITING: |
| 267 | case TIMED_WAITING: |
| 268 | Thread.yield(); |
| 269 | break; |
| 270 | case BLOCKED: |
| 271 | isSynchronized = true; |
| 272 | break; |
| 273 | case TERMINATED: |
| 274 | isSynchronized = false; |
| 275 | break; |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | try { |
| 281 | t.join(); |
| 282 | } catch (InterruptedException ex) { |
| 283 | ex.printStackTrace(); |
| 284 | } |
| 285 | |
| 286 | return isSynchronized; |
| 287 | } |
| 288 | |
| 289 | /* |
| 290 | * This class is used to test the synchronization tester above. It has a |
| 291 | * method, should_pass, that is unsynchronized but calls a synchronized |
| 292 | * method. It has another method, should_fail, which isn't synchronized and |
| 293 | * doesn't call a synchronized method. The former should pass and the latter |
| 294 | * should fail. |
| 295 | */ |
| 296 | private static class MyTestClass { |
| 297 | |
| 298 | @SuppressWarnings("unused") |
| 299 | public MyTestClass(String s) { |
| 300 | } |
| 301 | |
| 302 | @SuppressWarnings("unused") |
| 303 | public void should_pass() { |
| 304 | // call sync method |
| 305 | sync_shouldnt_be_tested(); |
| 306 | } |
| 307 | |
| 308 | @SuppressWarnings("unused") |
| 309 | public void should_fail() { |
| 310 | } |
| 311 | |
| 312 | public synchronized void sync_shouldnt_be_tested() { |
| 313 | } |
| 314 | } |
| 315 | } |