3789983e89
Reviewed-by: darcy, ihse
320 lines
12 KiB
Java
320 lines
12 KiB
Java
/*
|
|
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/* @test
|
|
* @bug 6206780
|
|
* @summary Test that all public unsynchronized methods of StringBuffer are either directly or indirectly synchronized
|
|
*/
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Modifier;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* TestSynchronization tests whether synchronized methods calls on an object
|
|
* result in synchronized calls. Note that this may not test all cases desired.
|
|
* It only tests whether some synchronization has occurred on the object during
|
|
* the call chain, and can't tell whether the object was locked across all
|
|
* operations that have been performed on the object.
|
|
*/
|
|
public class TestSynchronization {
|
|
|
|
/**
|
|
* Define parameters used in methods of StringBuffer - admittedly a bit of
|
|
* hack but 'purpose-built' for StringBuffer. Something more general could
|
|
* probably be developed if the test needs to be more widely adopted.
|
|
* <p/>
|
|
* boolean char char[] int double float long Object CharSequence String
|
|
* StringBuffer StringBuilder
|
|
* <p/>
|
|
*/
|
|
private static final boolean BOOLEAN_VAL = true;
|
|
private static final char CHAR_VAL = 'x';
|
|
private static final char[] CHAR_ARRAY_VAL = {'c', 'h', 'a', 'r', 'a', 'r',
|
|
'r', 'a', 'y'};
|
|
private static final int INT_VAL = 1;
|
|
private static final double DOUBLE_VAL = 1.0d;
|
|
private static final float FLOAT_VAL = 1.0f;
|
|
private static final long LONG_VAL = 1L;
|
|
private static final Object OBJECT_VAL = new Object();
|
|
private static final String STRING_VAL = "String value";
|
|
private static final StringBuilder STRING_BUILDER_VAL =
|
|
new StringBuilder("StringBuilder value");
|
|
private static final StringBuffer STRING_BUFFER_VAL =
|
|
new StringBuffer("StringBuffer value");
|
|
private static final CharSequence[] CHAR_SEQUENCE_VAL = {STRING_VAL,
|
|
STRING_BUILDER_VAL, STRING_BUFFER_VAL};
|
|
|
|
public static void main(String... args) throws Exception {
|
|
// First, test the tester
|
|
testClass(MyTestClass.class, /*
|
|
* self-test
|
|
*/ true);
|
|
// Finally, test StringBuffer
|
|
testClass(StringBuffer.class, /*
|
|
* self-test
|
|
*/ false);
|
|
}
|
|
|
|
/**
|
|
* Test all the public, unsynchronized methods of the given class. If
|
|
* isSelfTest is true, this is a self-test to ensure that the test program
|
|
* itself is working correctly. Should help ensure correctness of this
|
|
* program if it changes.
|
|
* <p/>
|
|
* @param aClass - the class to test
|
|
* @param isSelfTest - true if this is the special self-test class
|
|
* @throws SecurityException
|
|
*/
|
|
private static void testClass(Class<?> aClass, boolean isSelfTest) throws
|
|
Exception {
|
|
// Get all unsynchronized public methods via reflection. We don't need
|
|
// to test synchronized methods. By definition. they are already doing
|
|
// the right thing.
|
|
List<Method> methods = Arrays.asList(aClass.getDeclaredMethods());
|
|
for (Method m : methods) {
|
|
// skip synthetic methods, like default interface methods and lambdas
|
|
if (m.isSynthetic()) {
|
|
continue;
|
|
}
|
|
int modifiers = m.getModifiers();
|
|
if (Modifier.isPublic(modifiers)
|
|
&& !Modifier.isSynchronized(modifiers)) {
|
|
try {
|
|
testMethod(aClass, m);
|
|
} catch (TestFailedException e) {
|
|
if (isSelfTest) {
|
|
String methodName = e.getMethod().getName();
|
|
switch (methodName) {
|
|
case "should_pass":
|
|
throw new RuntimeException(
|
|
"Test failed: self-test failed. The 'should_pass' method did not pass the synchronization test. Check the test code.");
|
|
case "should_fail":
|
|
break;
|
|
default:
|
|
throw new RuntimeException(
|
|
"Test failed: something is amiss with the test. A TestFailedException was generated on a call to "
|
|
+ methodName + " which we didn't expect to test in the first place.");
|
|
}
|
|
} else {
|
|
throw new RuntimeException("Test failed: the method "
|
|
+ e.getMethod().toString()
|
|
+ " should be synchronized, but isn't.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void invokeMethod(Class<?> aClass, final Method m,
|
|
final Object[] args) throws TestFailedException, Exception {
|
|
//System.out.println( "Invoking " + m.toString() + " with parameters " + Arrays.toString(args));
|
|
final Constructor<?> objConstructor;
|
|
Object obj = null;
|
|
|
|
objConstructor = aClass.getConstructor(String.class);
|
|
obj = objConstructor.newInstance("LeftPalindrome-emordnilaP-thgiR");
|
|
|
|
// test method m for synchronization
|
|
if (!isSynchronized(m, obj, args)) {
|
|
throw new TestFailedException(m);
|
|
}
|
|
}
|
|
|
|
private static void testMethod(Class<?> aClass, Method m) throws
|
|
Exception {
|
|
/*
|
|
* Construct call with arguments of the correct type. Note that the
|
|
* values are somewhat irrelevant. If the call actually succeeds, it
|
|
* means we aren't synchronized and the test has failed.
|
|
*/
|
|
Class<?>[] pTypes = m.getParameterTypes();
|
|
List<Integer> charSequenceArgs = new ArrayList<>();
|
|
Object[] args = new Object[pTypes.length];
|
|
for (int i = 0; i < pTypes.length; i++) {
|
|
// determine the type and create the corresponding actual argument
|
|
Class<?> pType = pTypes[i];
|
|
if (pType.equals(boolean.class)) {
|
|
args[i] = BOOLEAN_VAL;
|
|
} else if (pType.equals(char.class)) {
|
|
args[i] = CHAR_VAL;
|
|
} else if (pType.equals(int.class)) {
|
|
args[i] = INT_VAL;
|
|
} else if (pType.equals(double.class)) {
|
|
args[i] = DOUBLE_VAL;
|
|
} else if (pType.equals(float.class)) {
|
|
args[i] = FLOAT_VAL;
|
|
} else if (pType.equals(long.class)) {
|
|
args[i] = LONG_VAL;
|
|
} else if (pType.equals(Object.class)) {
|
|
args[i] = OBJECT_VAL;
|
|
} else if (pType.equals(StringBuilder.class)) {
|
|
args[i] = STRING_BUILDER_VAL;
|
|
} else if (pType.equals(StringBuffer.class)) {
|
|
args[i] = STRING_BUFFER_VAL;
|
|
} else if (pType.equals(String.class)) {
|
|
args[i] = STRING_VAL;
|
|
} else if (pType.isArray() && pType.getComponentType().equals(char.class)) {
|
|
args[i] = CHAR_ARRAY_VAL;
|
|
} else if (pType.equals(CharSequence.class)) {
|
|
charSequenceArgs.add(new Integer(i));
|
|
} else {
|
|
throw new RuntimeException("Test Failed: not accounting for method call with parameter type of " + pType.getName() + " You must update the test.");
|
|
}
|
|
}
|
|
/*
|
|
* If there are no CharSequence args, we can simply invoke our method
|
|
* and test it
|
|
*/
|
|
if (charSequenceArgs.isEmpty()) {
|
|
invokeMethod(aClass, m, args);
|
|
} else {
|
|
/*
|
|
* Iterate through the different CharSequence types and invoke the
|
|
* method for each type.
|
|
*/
|
|
if (charSequenceArgs.size() > 1) {
|
|
throw new RuntimeException("Test Failed: the test cannot handle a method with multiple CharSequence arguments. You must update the test to handle the method "
|
|
+ m.toString());
|
|
}
|
|
for (int j = 0; j < CHAR_SEQUENCE_VAL.length; j++) {
|
|
args[charSequenceArgs.get(0)] = CHAR_SEQUENCE_VAL[j];
|
|
invokeMethod(aClass, m, args);
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("serial")
|
|
private static class TestFailedException extends Exception {
|
|
|
|
final Method m;
|
|
|
|
public Method getMethod() {
|
|
return m;
|
|
}
|
|
|
|
public TestFailedException(Method m) {
|
|
this.m = m;
|
|
}
|
|
}
|
|
|
|
static class InvokeTask implements Runnable {
|
|
|
|
private final Method m;
|
|
private final Object target;
|
|
private final Object[] args;
|
|
|
|
InvokeTask(Method m, Object target, Object... args) {
|
|
this.m = m;
|
|
this.target = target;
|
|
this.args = args;
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
m.invoke(target, args);
|
|
} catch (IllegalAccessException | IllegalArgumentException |
|
|
InvocationTargetException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* isSynchronized tests whether the given method is synchronized or not by
|
|
* invoking it in a thread and testing the thread state after starting the
|
|
* thread
|
|
* <p/>
|
|
* @param m the method to test
|
|
* @param target the object the method is executed on
|
|
* @param args the arguments passed to the method
|
|
* @return true iff the method is synchronized
|
|
*/
|
|
private static boolean isSynchronized(Method m, Object target,
|
|
Object... args) {
|
|
Thread t = new Thread(new InvokeTask(m, target, args));
|
|
|
|
Boolean isSynchronized = null;
|
|
|
|
synchronized (target) {
|
|
t.start();
|
|
|
|
while (isSynchronized == null) {
|
|
switch (t.getState()) {
|
|
case NEW:
|
|
case RUNNABLE:
|
|
case WAITING:
|
|
case TIMED_WAITING:
|
|
Thread.yield();
|
|
break;
|
|
case BLOCKED:
|
|
isSynchronized = true;
|
|
break;
|
|
case TERMINATED:
|
|
isSynchronized = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
t.join();
|
|
} catch (InterruptedException ex) {
|
|
ex.printStackTrace();
|
|
}
|
|
|
|
return isSynchronized;
|
|
}
|
|
|
|
/*
|
|
* This class is used to test the synchronization tester above. It has a
|
|
* method, should_pass, that is unsynchronized but calls a synchronized
|
|
* method. It has another method, should_fail, which isn't synchronized and
|
|
* doesn't call a synchronized method. The former should pass and the latter
|
|
* should fail.
|
|
*/
|
|
private static class MyTestClass {
|
|
|
|
@SuppressWarnings("unused")
|
|
public MyTestClass(String s) {
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
public void should_pass() {
|
|
// call sync method
|
|
sync_shouldnt_be_tested();
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
public void should_fail() {
|
|
}
|
|
|
|
public synchronized void sync_shouldnt_be_tested() {
|
|
}
|
|
}
|
|
}
|