| /* |
| * Copyright (c) 2004, 2015, 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 6204469 6273765 |
| * @summary Test various aspects of the Descriptor interface |
| * @author Eamonn McManus |
| * |
| * @run clean DescriptorTest |
| * @run build DescriptorTest |
| * @run main DescriptorTest |
| */ |
| |
| import java.io.*; |
| import java.lang.reflect.*; |
| import java.util.*; |
| import javax.management.*; |
| |
| public class DescriptorTest { |
| private static String failureMessage; |
| |
| // Warning: many tests here know the contents of these variables |
| // so if you change them you must change the tests |
| private static final String[] testFieldNames = { |
| "a", "C", "aa", "int", "nul", |
| }; |
| private static final Object[] testFieldValues = { |
| "b", "D", "bb", 5, null, |
| }; |
| private static final String[] testFieldStrings = { |
| "a=b", "C=D", "aa=bb", "int=(5)", "nul=", |
| }; |
| |
| public static void main(String[] args) throws Exception { |
| genericTests(ImmutableDescriptor.class); |
| genericTests(javax.management.modelmbean.DescriptorSupport.class); |
| if (failureMessage != null) |
| throw new Exception("TEST FAILED: " + failureMessage); |
| else |
| System.out.println("Test passed"); |
| } |
| |
| private static void genericTests(Class<? extends Descriptor> descrClass) { |
| System.out.println("--- generic tests for " + descrClass.getName() + |
| " ---"); |
| for (Case<Class<? extends Descriptor>, ?, ?> test : |
| genericDescriptorTests) |
| test.run(descrClass); |
| } |
| |
| /* |
| Testing has three parts. We take the input parameter, of type P, |
| and give it to the "prepare" method. That returns us a test |
| parameter, of type T. We give that to the "test" method. That |
| in turn returns us a check value, of type C. We give this to the |
| "check" method. If the "check" method returns null, the test passes. |
| If the "check" method returns a string, that string explains the |
| test failure. If any of the methods throws an exception, the |
| test fails. |
| */ |
| private static abstract class Case<P, T, C> { |
| Case(String name) { |
| this.name = name; |
| } |
| |
| void run(P p) { |
| System.out.println("test: " + name); |
| try { |
| T t = prepare(p); |
| C c = test(t); |
| String failed = check(c); |
| if (failed != null) { |
| System.out.println("FAILED: " + name + ": " + failed); |
| failureMessage = failed; |
| } |
| } catch (Exception e) { |
| System.out.println("FAILED: " + name + ": exception:"); |
| e.printStackTrace(System.out); |
| failureMessage = e.toString(); |
| } |
| } |
| |
| abstract T prepare(P p) throws Exception; |
| abstract C test(T t) throws Exception; |
| abstract String check(C c) throws Exception; |
| |
| private final String name; |
| } |
| |
| /* |
| Test case where the preparation step consists of constructing an |
| instance of the given Descriptor subclass containing test values, |
| then giving that to the "test" method. |
| */ |
| private static abstract class ProtoCase<C> |
| extends Case<Class<? extends Descriptor>, Descriptor, C> { |
| |
| ProtoCase(String name) { |
| super(name); |
| } |
| |
| Descriptor prepare(Class<? extends Descriptor> descrClass) |
| throws Exception { |
| Constructor<? extends Descriptor> con = |
| descrClass.getConstructor(String[].class, Object[].class); |
| return con.newInstance(testFieldNames, testFieldValues); |
| } |
| } |
| |
| /* |
| Test case where the "test" method must return a value of type C |
| which we will compare against the testValue parameter given to |
| the test constructor. |
| */ |
| private static abstract class ValueProtoCase<C> extends ProtoCase<C> { |
| ValueProtoCase(String name, C testValue) { |
| super(name); |
| this.testValue = testValue; |
| } |
| |
| String check(C c) { |
| final boolean array = (testValue instanceof Object[]); |
| final boolean equal = |
| array ? |
| Arrays.deepEquals((Object[]) testValue, (Object[]) c) : |
| testValue.equals(c); |
| if (equal) |
| return null; |
| return "wrong value: " + string(c) + " should be " + |
| string(testValue); |
| } |
| |
| private final C testValue; |
| } |
| |
| /* |
| Test case where the dontChange method does some operation on the |
| test Descriptor that is not supposed to change the contents of |
| the Descriptor. This should work for both mutable and immutable |
| Descriptors, since immutable Descriptors are supposed to do |
| nothing (rather than throw an exception) for mutation operations |
| that would not in fact change the contents. |
| */ |
| private static abstract class UnchangedCase extends ProtoCase<Descriptor> { |
| UnchangedCase(String name) { |
| super(name); |
| } |
| |
| Descriptor test(Descriptor d) { |
| dontChange(d); |
| return d; |
| } |
| |
| String check(Descriptor d) { |
| String[] dnames = d.getFieldNames(); |
| if (!strings(dnames).equals(strings(testFieldNames))) |
| return "descriptor names changed: " + strings(dnames); |
| Object[] values = d.getFieldValues(testFieldNames); |
| if (values.length != testFieldValues.length) |
| return "getFieldValues: bogus length: " + values.length; |
| for (int i = 0; i < values.length; i++) { |
| Object expected = testFieldValues[i]; |
| Object found = values[i]; |
| if ((expected == null) ? |
| found != null : |
| !expected.equals(found)) |
| return "descriptor value changed: " + testFieldNames[i] + |
| " was " + expected + " now " + found; |
| } |
| return null; |
| } |
| |
| abstract void dontChange(Descriptor d); |
| } |
| |
| /* |
| Test case where the change(d) method attempts to make some |
| change to the Descriptor d. The behaviour depends on whether |
| the Descriptor is mutable or not. If the Descriptor is |
| immutable, then the change attempt must throw a |
| RuntimeOperationsException wrapping an |
| UnsupportedOperationException. If the Descriptor is mutable, |
| then the change attempt must succeed, and the Descriptor must |
| then look like the fieldsAndValues parameter to the constructor. |
| This is simply an alternating set of field names and corresponding |
| values. So for example if it is |
| |
| "a", "b", "x", 5 |
| |
| that represents a Descriptor with fields "a" and "x" whose |
| corresponding values are "x" and Integer.valueOf(5). |
| */ |
| private static abstract class ChangedCase extends ProtoCase<Object> { |
| ChangedCase(String name, Object... fieldsAndValues) { |
| super(name); |
| if (fieldsAndValues.length % 2 != 0) |
| throw new AssertionError("test wrong: odd fieldsAndValues"); |
| this.fieldsAndValues = fieldsAndValues; |
| this.immutableTest = new UnsupportedExceptionCase(name) { |
| void provoke(Descriptor d) { |
| ChangedCase.this.change(d); |
| } |
| }; |
| } |
| |
| Object test(Descriptor d) { |
| if (immutable(d)) |
| return immutableTest.test(d); |
| else { |
| change(d); |
| return d; |
| } |
| } |
| |
| String check(Object c) { |
| if (c instanceof Exception) |
| return immutableTest.check((Exception) c); |
| else if (!(c instanceof Descriptor)) { |
| return "test returned strange value: " + |
| c.getClass() + ": " + c; |
| } else { |
| Descriptor d = (Descriptor) c; |
| String[] names = new String[fieldsAndValues.length / 2]; |
| Object[] expected = new Object[names.length]; |
| for (int i = 0; i < fieldsAndValues.length; i += 2) { |
| names[i / 2] = (String) fieldsAndValues[i]; |
| expected[i / 2] = fieldsAndValues[i + 1]; |
| } |
| String[] foundNames = d.getFieldNames(); |
| if (!strings(foundNames).equals(strings(names))) { |
| return "wrong field names after change: found " + |
| strings(foundNames) + ", expected " + strings(names); |
| } |
| Object[] found = d.getFieldValues(names); |
| if (!Arrays.deepEquals(expected, found)) { |
| return "wrong value after change: for fields " + |
| Arrays.asList(names) + " values are " + |
| Arrays.asList(found) + ", should be " + |
| Arrays.asList(expected); |
| } |
| return null; |
| } |
| } |
| |
| abstract void change(Descriptor d); |
| |
| private final Object[] fieldsAndValues; |
| private final ExceptionCase immutableTest; |
| } |
| |
| /* |
| Test case where an operation provoke(d) on the test Descriptor d |
| is supposed to provoke an exception. The exception must be a |
| RuntimeOperationsException wrapping another exception whose type |
| is determined by the exceptionClass() method. |
| */ |
| private static abstract class ExceptionCase extends ProtoCase<Exception> { |
| |
| ExceptionCase(String name) { |
| super(name); |
| } |
| |
| Exception test(Descriptor d) { |
| try { |
| provoke(d); |
| return null; |
| } catch (Exception e) { |
| return e; |
| } |
| } |
| |
| String check(Exception e) { |
| if (e == null) |
| return "did not throw exception: " + expected(); |
| if (!(e instanceof RuntimeOperationsException)) { |
| StringWriter sw = new StringWriter(); |
| PrintWriter pw = new PrintWriter(sw); |
| e.printStackTrace(pw); |
| pw.flush(); |
| return "wrong exception: " + expected() + ": found: " + sw; |
| } |
| Throwable cause = e.getCause(); |
| if (!exceptionClass().isInstance(cause)) |
| return "wrong wrapped exception: " + cause + ": " + expected(); |
| return null; |
| } |
| |
| String expected() { |
| return "expected " + RuntimeOperationsException.class.getName() + |
| " wrapping " + exceptionClass().getName(); |
| } |
| |
| abstract Class<? extends Exception> exceptionClass(); |
| abstract void provoke(Descriptor d); |
| } |
| |
| private static abstract class IllegalExceptionCase extends ExceptionCase { |
| IllegalExceptionCase(String name) { |
| super(name); |
| } |
| |
| Class<IllegalArgumentException> exceptionClass() { |
| return IllegalArgumentException.class; |
| } |
| } |
| |
| private static abstract class UnsupportedExceptionCase |
| extends ExceptionCase { |
| UnsupportedExceptionCase(String name) { |
| super(name); |
| } |
| |
| Class<UnsupportedOperationException> exceptionClass() { |
| return UnsupportedOperationException.class; |
| } |
| } |
| |
| /* |
| List of test cases. We will run through these once for |
| ImmutableDescriptor and once for DescriptorSupport. |
| |
| Expect a compiler [unchecked] warning for this initialization. |
| Writing |
| |
| new Case<Class<? extends Descriptor>, ?, ?>[] = {...} |
| |
| would cause a compiler error since you can't have arrays of |
| parameterized types unless all the parameters are just "?". |
| This hack with varargs gives us a compiler warning instead. |
| Writing just: |
| |
| new Case<?, ?, ?>[] = {...} |
| |
| would compile here, but not where we call test.run, since you |
| cannot pass an object to the run(P) method if P is "?". |
| */ |
| private static final Case<Class<? extends Descriptor>, ?, ?> |
| genericDescriptorTests[] = constantArray( |
| |
| // TEST VALUES RETURNED BY GETTERS |
| |
| new Case<Class<? extends Descriptor>, Descriptor, Object[]>( |
| "getFieldValues on empty Descriptor") { |
| Descriptor prepare(Class<? extends Descriptor> c) |
| throws Exception { |
| Constructor<? extends Descriptor> con = |
| c.getConstructor(String[].class); |
| return con.newInstance(new Object[] {new String[0]}); |
| } |
| Object[] test(Descriptor d) { |
| return d.getFieldValues("foo", "bar"); |
| } |
| String check(Object[] v) { |
| if (v.length == 2 && v[0] == null && v[1] == null) |
| return null; |
| return "value should be array with null elements: " + |
| Arrays.deepToString(v); |
| } |
| }, |
| |
| new ValueProtoCase<Set<String>>("getFieldNames", |
| strings(testFieldNames)) { |
| Set<String> test(Descriptor d) { |
| return set(d.getFieldNames()); |
| } |
| }, |
| new ValueProtoCase<Set<String>>("getFields", |
| strings(testFieldStrings)) { |
| Set<String> test(Descriptor d) { |
| return set(d.getFields()); |
| } |
| }, |
| new ValueProtoCase<Object>("getFieldValue with exact case", "b") { |
| Object test(Descriptor d) { |
| return d.getFieldValue("a"); |
| } |
| }, |
| new ValueProtoCase<Object>("getFieldValue with lower case for upper", |
| "D") { |
| Object test(Descriptor d) { |
| return d.getFieldValue("c"); |
| } |
| }, |
| new ValueProtoCase<Object>("getFieldValue with upper case for lower", |
| "bb") { |
| Object test(Descriptor d) { |
| return d.getFieldValue("AA"); |
| } |
| }, |
| new ValueProtoCase<Object>("getFieldValue with mixed case for lower", |
| "bb") { |
| Object test(Descriptor d) { |
| return d.getFieldValue("aA"); |
| } |
| }, |
| new ValueProtoCase<Set<?>>("getFieldValues with null arg", |
| set(testFieldValues)) { |
| Set<?> test(Descriptor d) { |
| return set(d.getFieldValues((String[]) null)); |
| } |
| }, |
| new ValueProtoCase<Object[]>("getFieldValues with not all values", |
| new Object[] {"b", "D", 5}) { |
| Object[] test(Descriptor d) { |
| return d.getFieldValues("a", "c", "int"); |
| } |
| }, |
| new ValueProtoCase<Object[]>("getFieldValues with all values " + |
| "lower case", |
| new Object[]{"bb", "D", "b", 5}) { |
| Object[] test(Descriptor d) { |
| return d.getFieldValues("aa", "c", "a", "int"); |
| } |
| }, |
| new ValueProtoCase<Object[]>("getFieldValues with all values " + |
| "upper case", |
| new Object[] {5, "b", "D", "bb"}) { |
| Object[] test(Descriptor d) { |
| return d.getFieldValues("int", "A", "C", "AA"); |
| } |
| }, |
| new ValueProtoCase<Object[]>("getFieldValues with null name", |
| new Object[] {null}) { |
| Object[] test(Descriptor d) { |
| return d.getFieldValues((String) null); |
| } |
| }, |
| new ValueProtoCase<Object[]>("getFieldValues with empty name", |
| new Object[] {null}) { |
| Object[] test(Descriptor d) { |
| return d.getFieldValues(""); |
| } |
| }, |
| new ValueProtoCase<Object[]>("getFieldValues with no names", |
| new Object[0]) { |
| Object[] test(Descriptor d) { |
| return d.getFieldValues(); |
| } |
| }, |
| |
| // TEST OPERATIONS THAT DON'T CHANGE THE DESCRIPTOR |
| // Even for immutable descriptors, these are allowed |
| |
| new UnchangedCase("removeField with nonexistent field") { |
| void dontChange(Descriptor d) { |
| d.removeField("noddy"); |
| } |
| }, |
| new UnchangedCase("removeField with null field") { |
| void dontChange(Descriptor d) { |
| d.removeField(null); |
| } |
| }, |
| new UnchangedCase("removeField with empty field") { |
| void dontChange(Descriptor d) { |
| d.removeField(""); |
| } |
| }, |
| new UnchangedCase("setField leaving string unchanged") { |
| void dontChange(Descriptor d) { |
| d.setField("a", "b"); |
| } |
| }, |
| new UnchangedCase("setField leaving int unchanged") { |
| void dontChange(Descriptor d) { |
| d.setField("int", 5); |
| } |
| }, |
| // We do not test whether you can do a setField/s with an |
| // unchanged value but the case of the name different. |
| // From the spec, that should probably be illegal, but |
| // it's such a corner case that we leave it alone. |
| |
| new UnchangedCase("setFields with empty arrays") { |
| void dontChange(Descriptor d) { |
| d.setFields(new String[0], new Object[0]); |
| } |
| }, |
| new UnchangedCase("setFields with unchanged values") { |
| void dontChange(Descriptor d) { |
| d.setFields(new String[] {"a", "int"}, |
| new Object[] {"b", 5}); |
| } |
| }, |
| |
| // TEST OPERATIONS THAT DO CHANGE THE DESCRIPTOR |
| // For immutable descriptors, these should provoke an exception |
| |
| new ChangedCase("removeField with exact case", |
| "a", "b", "C", "D", "int", 5, "nul", null) { |
| void change(Descriptor d) { |
| d.removeField("aa"); |
| } |
| }, |
| new ChangedCase("removeField with upper case for lower", |
| "a", "b", "C", "D", "int", 5, "nul", null) { |
| void change(Descriptor d) { |
| d.removeField("AA"); |
| } |
| }, |
| new ChangedCase("removeField with lower case for upper", |
| "a", "b", "aa", "bb", "int", 5, "nul", null) { |
| void change(Descriptor d) { |
| d.removeField("c"); |
| } |
| }, |
| new ChangedCase("setField keeping lower case", |
| "a", "x", "C", "D", "aa", "bb", "int", 5, |
| "nul", null) { |
| void change(Descriptor d) { |
| d.setField("a", "x"); |
| } |
| }, |
| |
| // spec says we should conserve the original case of the field name: |
| new ChangedCase("setField changing lower case to upper", |
| "a", "x", "C", "D", "aa", "bb", "int", 5, |
| "nul", null) { |
| void change(Descriptor d) { |
| d.setField("A", "x"); |
| } |
| }, |
| new ChangedCase("setField changing upper case to lower", |
| "a", "b", "C", "x", "aa", "bb", "int", 5, |
| "nul", null) { |
| void change(Descriptor d) { |
| d.setField("c", "x"); |
| } |
| }, |
| new ChangedCase("setField adding new field", |
| "a", "b", "C", "D", "aa", "bb", "int", 5, "xX", "yY", |
| "nul", null) { |
| void change(Descriptor d) { |
| d.setField("xX", "yY"); |
| } |
| }, |
| new ChangedCase("setField changing type of field", |
| "a", true, "C", "D", "aa", "bb", "int", 5, |
| "nul", null) { |
| void change(Descriptor d) { |
| d.setField("a", true); |
| } |
| }, |
| new ChangedCase("setField changing non-null to null", |
| "a", null, "C", "D", "aa", "bb", "int", 5, |
| "nul", null) { |
| void change(Descriptor d) { |
| d.setField("a", null); |
| } |
| }, |
| new ChangedCase("setField changing null to non-null", |
| "a", "b", "C", "D", "aa", "bb", "int", 5, |
| "nul", 3.14) { |
| void change(Descriptor d) { |
| d.setField("nul", 3.14); |
| } |
| }, |
| |
| // TEST EXCEPTION BEHAVIOUR COMMON BETWEEN MUTABLE AND IMMUTABLE |
| |
| new IllegalExceptionCase("getFieldValue with null name") { |
| void provoke(Descriptor d) { |
| d.getFieldValue(null); |
| } |
| }, |
| new IllegalExceptionCase("getFieldValue with empty name") { |
| void provoke(Descriptor d) { |
| d.getFieldValue(""); |
| } |
| }, |
| new IllegalExceptionCase("setField with null name") { |
| void provoke(Descriptor d) { |
| d.setField(null, "x"); |
| } |
| }, |
| new IllegalExceptionCase("setField with empty name") { |
| void provoke(Descriptor d) { |
| d.setField("", "x"); |
| } |
| }, |
| new IllegalExceptionCase("setFields with null fieldNames") { |
| void provoke(Descriptor d) { |
| d.setFields(null, new Object[] {"X"}); |
| } |
| }, |
| new IllegalExceptionCase("setFields with null fieldValues") { |
| void provoke(Descriptor d) { |
| d.setFields(new String[] {"X"}, null); |
| } |
| }, |
| new IllegalExceptionCase("setFields with null fieldNames and " + |
| "fieldValues") { |
| void provoke(Descriptor d) { |
| d.setFields(null, null); |
| } |
| }, |
| new IllegalExceptionCase("setFields with more fieldNames than " + |
| "fieldValues") { |
| void provoke(Descriptor d) { |
| d.setFields(new String[] {"A", "B"}, new String[] {"C"}); |
| } |
| }, |
| new IllegalExceptionCase("setFields with more fieldValues than " + |
| "fieldNames") { |
| void provoke(Descriptor d) { |
| d.setFields(new String[] {"A"}, new String[] {"B", "C"}); |
| } |
| }, |
| new IllegalExceptionCase("setFields with null element of fieldNames") { |
| void provoke(Descriptor d) { |
| d.setFields(new String[] {null}, new String[] {"X"}); |
| } |
| } |
| |
| ); |
| |
| static <T> T[] constantArray(T... array) { |
| return array; |
| } |
| |
| static String string(Object x) { |
| if (x instanceof Object[]) |
| return Arrays.asList((Object[]) x).toString(); |
| else |
| return String.valueOf(x); |
| } |
| |
| static Set<String> strings(String... values) { |
| return new TreeSet<String>(Arrays.asList(values)); |
| } |
| |
| static <T> Set<T> set(T[] values) { |
| return new HashSet<T>(Arrays.asList(values)); |
| } |
| |
| static boolean immutable(Descriptor d) { |
| return (d instanceof ImmutableDescriptor); |
| // good enough for our purposes |
| } |
| } |