1 /* 2 * Copyright (c) 2004, 2005, Oracle and/or its affiliates. All rights reserved. 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 /* 25 * @test 26 * @bug 6204469 6273765 27 * @summary Test various aspects of the Descriptor interface 28 * @author Eamonn McManus 29 * @run clean DescriptorTest 30 * @run build DescriptorTest 31 * @run main DescriptorTest 32 */ 33 34 import java.io.*; 35 import java.lang.reflect.*; 36 import java.util.*; 37 import javax.management.*; 38 39 public class DescriptorTest { 40 private static String failureMessage; 41 42 // Warning: many tests here know the contents of these variables 43 // so if you change them you must change the tests 44 private static final String[] testFieldNames = { 45 "a", "C", "aa", "int", "nul", 46 }; 47 private static final Object[] testFieldValues = { 48 "b", "D", "bb", 5, null, 49 }; 50 private static final String[] testFieldStrings = { 51 "a=b", "C=D", "aa=bb", "int=(5)", "nul=", 52 }; 53 54 public static void main(String[] args) throws Exception { 55 genericTests(ImmutableDescriptor.class); 56 genericTests(javax.management.modelmbean.DescriptorSupport.class); 57 if (failureMessage != null) 58 throw new Exception("TEST FAILED: " + failureMessage); 59 else 60 System.out.println("Test passed"); 61 } 62 63 private static void genericTests(Class<? extends Descriptor> descrClass) { 64 System.out.println("--- generic tests for " + descrClass.getName() + 65 " ---"); 66 for (Case<Class<? extends Descriptor>, ?, ?> test : 67 genericDescriptorTests) 68 test.run(descrClass); 69 } 70 71 /* 72 Testing has three parts. We take the input parameter, of type P, 73 and give it to the "prepare" method. That returns us a test 74 parameter, of type T. We give that to the "test" method. That 75 in turn returns us a check value, of type C. We give this to the 76 "check" method. If the "check" method returns null, the test passes. 77 If the "check" method returns a string, that string explains the 78 test failure. If any of the methods throws an exception, the 79 test fails. 80 */ 81 private static abstract class Case<P, T, C> { 82 Case(String name) { 83 this.name = name; 84 } 85 86 void run(P p) { 87 System.out.println("test: " + name); 88 try { 89 T t = prepare(p); 90 C c = test(t); 91 String failed = check(c); 92 if (failed != null) { 93 System.out.println("FAILED: " + name + ": " + failed); 94 failureMessage = failed; 95 } 96 } catch (Exception e) { 97 System.out.println("FAILED: " + name + ": exception:"); 98 e.printStackTrace(System.out); 99 failureMessage = e.toString(); 100 } 101 } 102 103 abstract T prepare(P p) throws Exception; 104 abstract C test(T t) throws Exception; 105 abstract String check(C c) throws Exception; 106 107 private final String name; 108 } 109 110 /* 111 Test case where the preparation step consists of constructing an 112 instance of the given Descriptor subclass containing test values, 113 then giving that to the "test" method. 114 */ 115 private static abstract class ProtoCase<C> 116 extends Case<Class<? extends Descriptor>, Descriptor, C> { 117 118 ProtoCase(String name) { 119 super(name); 120 } 121 122 Descriptor prepare(Class<? extends Descriptor> descrClass) 123 throws Exception { 124 Constructor<? extends Descriptor> con = 125 descrClass.getConstructor(String[].class, Object[].class); 126 return con.newInstance(testFieldNames, testFieldValues); 127 } 128 } 129 130 /* 131 Test case where the "test" method must return a value of type C 132 which we will compare against the testValue parameter given to 133 the test constructor. 134 */ 135 private static abstract class ValueProtoCase<C> extends ProtoCase<C> { 136 ValueProtoCase(String name, C testValue) { 137 super(name); 138 this.testValue = testValue; 139 } 140 141 String check(C c) { 142 final boolean array = (testValue instanceof Object[]); 143 final boolean equal = 144 array ? 145 Arrays.deepEquals((Object[]) testValue, (Object[]) c) : 146 testValue.equals(c); 147 if (equal) 148 return null; 149 return "wrong value: " + string(c) + " should be " + 150 string(testValue); 151 } 152 153 private final C testValue; 154 } 155 156 /* 157 Test case where the dontChange method does some operation on the 158 test Descriptor that is not supposed to change the contents of 159 the Descriptor. This should work for both mutable and immutable 160 Descriptors, since immutable Descriptors are supposed to do 161 nothing (rather than throw an exception) for mutation operations 162 that would not in fact change the contents. 163 */ 164 private static abstract class UnchangedCase extends ProtoCase<Descriptor> { 165 UnchangedCase(String name) { 166 super(name); 167 } 168 169 Descriptor test(Descriptor d) { 170 dontChange(d); 171 return d; 172 } 173 174 String check(Descriptor d) { 175 String[] dnames = d.getFieldNames(); 176 if (!strings(dnames).equals(strings(testFieldNames))) 177 return "descriptor names changed: " + strings(dnames); 178 Object[] values = d.getFieldValues(testFieldNames); 179 if (values.length != testFieldValues.length) 180 return "getFieldValues: bogus length: " + values.length; 181 for (int i = 0; i < values.length; i++) { 182 Object expected = testFieldValues[i]; 183 Object found = values[i]; 184 if ((expected == null) ? 185 found != null : 186 !expected.equals(found)) 187 return "descriptor value changed: " + testFieldNames[i] + 188 " was " + expected + " now " + found; 189 } 190 return null; 191 } 192 193 abstract void dontChange(Descriptor d); 194 } 195 196 /* 197 Test case where the change(d) method attempts to make some 198 change to the Descriptor d. The behaviour depends on whether 199 the Descriptor is mutable or not. If the Descriptor is 200 immutable, then the change attempt must throw a 201 RuntimeOperationsException wrapping an 202 UnsupportedOperationException. If the Descriptor is mutable, 203 then the change attempt must succeed, and the Descriptor must 204 then look like the fieldsAndValues parameter to the constructor. 205 This is simply an alternating set of field names and corresponding 206 values. So for example if it is 207 208 "a", "b", "x", 5 209 210 that represents a Descriptor with fields "a" and "x" whose 211 corresponding values are "x" and Integer.valueOf(5). 212 */ 213 private static abstract class ChangedCase extends ProtoCase<Object> { 214 ChangedCase(String name, Object... fieldsAndValues) { 215 super(name); 216 if (fieldsAndValues.length % 2 != 0) 217 throw new AssertionError("test wrong: odd fieldsAndValues"); 218 this.fieldsAndValues = fieldsAndValues; 219 this.immutableTest = new UnsupportedExceptionCase(name) { 220 void provoke(Descriptor d) { 221 ChangedCase.this.change(d); 222 } 223 }; 224 } 225 226 Object test(Descriptor d) { 227 if (immutable(d)) 228 return immutableTest.test(d); 229 else { 230 change(d); 231 return d; 232 } 233 } 234 235 String check(Object c) { 236 if (c instanceof Exception) 237 return immutableTest.check((Exception) c); 238 else if (!(c instanceof Descriptor)) { 239 return "test returned strange value: " + 240 c.getClass() + ": " + c; 241 } else { 242 Descriptor d = (Descriptor) c; 243 String[] names = new String[fieldsAndValues.length / 2]; 244 Object[] expected = new Object[names.length]; 245 for (int i = 0; i < fieldsAndValues.length; i += 2) { 246 names[i / 2] = (String) fieldsAndValues[i]; 247 expected[i / 2] = fieldsAndValues[i + 1]; 248 } 249 String[] foundNames = d.getFieldNames(); 250 if (!strings(foundNames).equals(strings(names))) { 251 return "wrong field names after change: found " + 252 strings(foundNames) + ", expected " + strings(names); 253 } 254 Object[] found = d.getFieldValues(names); 255 if (!Arrays.deepEquals(expected, found)) { 256 return "wrong value after change: for fields " + 257 Arrays.asList(names) + " values are " + 258 Arrays.asList(found) + ", should be " + 259 Arrays.asList(expected); 260 } 261 return null; 262 } 263 } 264 265 abstract void change(Descriptor d); 266 267 private final Object[] fieldsAndValues; 268 private final ExceptionCase immutableTest; 269 } 270 271 /* 272 Test case where an operation provoke(d) on the test Descriptor d 273 is supposed to provoke an exception. The exception must be a 274 RuntimeOperationsException wrapping another exception whose type 275 is determined by the exceptionClass() method. 276 */ 277 private static abstract class ExceptionCase extends ProtoCase<Exception> { 278 279 ExceptionCase(String name) { 280 super(name); 281 } 282 283 Exception test(Descriptor d) { 284 try { 285 provoke(d); 286 return null; 287 } catch (Exception e) { 288 return e; 289 } 290 } 291 292 String check(Exception e) { 293 if (e == null) 294 return "did not throw exception: " + expected(); 295 if (!(e instanceof RuntimeOperationsException)) { 296 StringWriter sw = new StringWriter(); 297 PrintWriter pw = new PrintWriter(sw); 298 e.printStackTrace(pw); 299 pw.flush(); 300 return "wrong exception: " + expected() + ": found: " + sw; 301 } 302 Throwable cause = e.getCause(); 303 if (!exceptionClass().isInstance(cause)) 304 return "wrong wrapped exception: " + cause + ": " + expected(); 305 return null; 306 } 307 308 String expected() { 309 return "expected " + RuntimeOperationsException.class.getName() + 310 " wrapping " + exceptionClass().getName(); 311 } 312 313 abstract Class<? extends Exception> exceptionClass(); 314 abstract void provoke(Descriptor d); 315 } 316 317 private static abstract class IllegalExceptionCase extends ExceptionCase { 318 IllegalExceptionCase(String name) { 319 super(name); 320 } 321 322 Class<IllegalArgumentException> exceptionClass() { 323 return IllegalArgumentException.class; 324 } 325 } 326 327 private static abstract class UnsupportedExceptionCase 328 extends ExceptionCase { 329 UnsupportedExceptionCase(String name) { 330 super(name); 331 } 332 333 Class<UnsupportedOperationException> exceptionClass() { 334 return UnsupportedOperationException.class; 335 } 336 } 337 338 /* 339 List of test cases. We will run through these once for 340 ImmutableDescriptor and once for DescriptorSupport. 341 342 Expect a compiler [unchecked] warning for this initialization. 343 Writing 344 345 new Case<Class<? extends Descriptor>, ?, ?>[] = {...} 346 347 would cause a compiler error since you can't have arrays of 348 parameterized types unless all the parameters are just "?". 349 This hack with varargs gives us a compiler warning instead. 350 Writing just: 351 352 new Case<?, ?, ?>[] = {...} 353 354 would compile here, but not where we call test.run, since you 355 cannot pass an object to the run(P) method if P is "?". 356 */ 357 private static final Case<Class<? extends Descriptor>, ?, ?> 358 genericDescriptorTests[] = constantArray( 359 360 // TEST VALUES RETURNED BY GETTERS 361 362 new Case<Class<? extends Descriptor>, Descriptor, Object[]>( 363 "getFieldValues on empty Descriptor") { 364 Descriptor prepare(Class<? extends Descriptor> c) 365 throws Exception { 366 Constructor<? extends Descriptor> con = 367 c.getConstructor(String[].class); 368 return con.newInstance(new Object[] {new String[0]}); 369 } 370 Object[] test(Descriptor d) { 371 return d.getFieldValues("foo", "bar"); 372 } 373 String check(Object[] v) { 374 if (v.length == 2 && v[0] == null && v[1] == null) 375 return null; 376 return "value should be array with null elements: " + 377 Arrays.deepToString(v); 378 } 379 }, 380 381 new ValueProtoCase<Set<String>>("getFieldNames", 382 strings(testFieldNames)) { 383 Set<String> test(Descriptor d) { 384 return set(d.getFieldNames()); 385 } 386 }, 387 new ValueProtoCase<Set<String>>("getFields", 388 strings(testFieldStrings)) { 389 Set<String> test(Descriptor d) { 390 return set(d.getFields()); 391 } 392 }, 393 new ValueProtoCase<Object>("getFieldValue with exact case", "b") { 394 Object test(Descriptor d) { 395 return d.getFieldValue("a"); 396 } 397 }, 398 new ValueProtoCase<Object>("getFieldValue with lower case for upper", 399 "D") { 400 Object test(Descriptor d) { 401 return d.getFieldValue("c"); 402 } 403 }, 404 new ValueProtoCase<Object>("getFieldValue with upper case for lower", 405 "bb") { 406 Object test(Descriptor d) { 407 return d.getFieldValue("AA"); 408 } 409 }, 410 new ValueProtoCase<Object>("getFieldValue with mixed case for lower", 411 "bb") { 412 Object test(Descriptor d) { 413 return d.getFieldValue("aA"); 414 } 415 }, 416 new ValueProtoCase<Set<?>>("getFieldValues with null arg", 417 set(testFieldValues)) { 418 Set<?> test(Descriptor d) { 419 return set(d.getFieldValues((String[]) null)); 420 } 421 }, 422 new ValueProtoCase<Object[]>("getFieldValues with not all values", 423 new Object[] {"b", "D", 5}) { 424 Object[] test(Descriptor d) { 425 return d.getFieldValues("a", "c", "int"); 426 } 427 }, 428 new ValueProtoCase<Object[]>("getFieldValues with all values " + 429 "lower case", 430 new Object[]{"bb", "D", "b", 5}) { 431 Object[] test(Descriptor d) { 432 return d.getFieldValues("aa", "c", "a", "int"); 433 } 434 }, 435 new ValueProtoCase<Object[]>("getFieldValues with all values " + 436 "upper case", 437 new Object[] {5, "b", "D", "bb"}) { 438 Object[] test(Descriptor d) { 439 return d.getFieldValues("int", "A", "C", "AA"); 440 } 441 }, 442 new ValueProtoCase<Object[]>("getFieldValues with null name", 443 new Object[] {null}) { 444 Object[] test(Descriptor d) { 445 return d.getFieldValues((String) null); 446 } 447 }, 448 new ValueProtoCase<Object[]>("getFieldValues with empty name", 449 new Object[] {null}) { 450 Object[] test(Descriptor d) { 451 return d.getFieldValues(""); 452 } 453 }, 454 new ValueProtoCase<Object[]>("getFieldValues with no names", 455 new Object[0]) { 456 Object[] test(Descriptor d) { 457 return d.getFieldValues(); 458 } 459 }, 460 461 // TEST OPERATIONS THAT DON'T CHANGE THE DESCRIPTOR 462 // Even for immutable descriptors, these are allowed 463 464 new UnchangedCase("removeField with nonexistent field") { 465 void dontChange(Descriptor d) { 466 d.removeField("noddy"); 467 } 468 }, 469 new UnchangedCase("removeField with null field") { 470 void dontChange(Descriptor d) { 471 d.removeField(null); 472 } 473 }, 474 new UnchangedCase("removeField with empty field") { 475 void dontChange(Descriptor d) { 476 d.removeField(""); 477 } 478 }, 479 new UnchangedCase("setField leaving string unchanged") { 480 void dontChange(Descriptor d) { 481 d.setField("a", "b"); 482 } 483 }, 484 new UnchangedCase("setField leaving int unchanged") { 485 void dontChange(Descriptor d) { 486 d.setField("int", 5); 487 } 488 }, 489 // We do not test whether you can do a setField/s with an 490 // unchanged value but the case of the name different. 491 // From the spec, that should probably be illegal, but 492 // it's such a corner case that we leave it alone. 493 494 new UnchangedCase("setFields with empty arrays") { 495 void dontChange(Descriptor d) { 496 d.setFields(new String[0], new Object[0]); 497 } 498 }, 499 new UnchangedCase("setFields with unchanged values") { 500 void dontChange(Descriptor d) { 501 d.setFields(new String[] {"a", "int"}, 502 new Object[] {"b", 5}); 503 } 504 }, 505 506 // TEST OPERATIONS THAT DO CHANGE THE DESCRIPTOR 507 // For immutable descriptors, these should provoke an exception 508 509 new ChangedCase("removeField with exact case", 510 "a", "b", "C", "D", "int", 5, "nul", null) { 511 void change(Descriptor d) { 512 d.removeField("aa"); 513 } 514 }, 515 new ChangedCase("removeField with upper case for lower", 516 "a", "b", "C", "D", "int", 5, "nul", null) { 517 void change(Descriptor d) { 518 d.removeField("AA"); 519 } 520 }, 521 new ChangedCase("removeField with lower case for upper", 522 "a", "b", "aa", "bb", "int", 5, "nul", null) { 523 void change(Descriptor d) { 524 d.removeField("c"); 525 } 526 }, 527 new ChangedCase("setField keeping lower case", 528 "a", "x", "C", "D", "aa", "bb", "int", 5, 529 "nul", null) { 530 void change(Descriptor d) { 531 d.setField("a", "x"); 532 } 533 }, 534 535 // spec says we should conserve the original case of the field name: 536 new ChangedCase("setField changing lower case to upper", 537 "a", "x", "C", "D", "aa", "bb", "int", 5, 538 "nul", null) { 539 void change(Descriptor d) { 540 d.setField("A", "x"); 541 } 542 }, 543 new ChangedCase("setField changing upper case to lower", 544 "a", "b", "C", "x", "aa", "bb", "int", 5, 545 "nul", null) { 546 void change(Descriptor d) { 547 d.setField("c", "x"); 548 } 549 }, 550 new ChangedCase("setField adding new field", 551 "a", "b", "C", "D", "aa", "bb", "int", 5, "xX", "yY", 552 "nul", null) { 553 void change(Descriptor d) { 554 d.setField("xX", "yY"); 555 } 556 }, 557 new ChangedCase("setField changing type of field", 558 "a", true, "C", "D", "aa", "bb", "int", 5, 559 "nul", null) { 560 void change(Descriptor d) { 561 d.setField("a", true); 562 } 563 }, 564 new ChangedCase("setField changing non-null to null", 565 "a", null, "C", "D", "aa", "bb", "int", 5, 566 "nul", null) { 567 void change(Descriptor d) { 568 d.setField("a", null); 569 } 570 }, 571 new ChangedCase("setField changing null to non-null", 572 "a", "b", "C", "D", "aa", "bb", "int", 5, 573 "nul", 3.14) { 574 void change(Descriptor d) { 575 d.setField("nul", 3.14); 576 } 577 }, 578 579 // TEST EXCEPTION BEHAVIOUR COMMON BETWEEN MUTABLE AND IMMUTABLE 580 581 new IllegalExceptionCase("getFieldValue with null name") { 582 void provoke(Descriptor d) { 583 d.getFieldValue(null); 584 } 585 }, 586 new IllegalExceptionCase("getFieldValue with empty name") { 587 void provoke(Descriptor d) { 588 d.getFieldValue(""); 589 } 590 }, 591 new IllegalExceptionCase("setField with null name") { 592 void provoke(Descriptor d) { 593 d.setField(null, "x"); 594 } 595 }, 596 new IllegalExceptionCase("setField with empty name") { 597 void provoke(Descriptor d) { 598 d.setField("", "x"); 599 } 600 }, 601 new IllegalExceptionCase("setFields with null fieldNames") { 602 void provoke(Descriptor d) { 603 d.setFields(null, new Object[] {"X"}); 604 } 605 }, 606 new IllegalExceptionCase("setFields with null fieldValues") { 607 void provoke(Descriptor d) { 608 d.setFields(new String[] {"X"}, null); 609 } 610 }, 611 new IllegalExceptionCase("setFields with null fieldNames and " + 612 "fieldValues") { 613 void provoke(Descriptor d) { 614 d.setFields(null, null); 615 } 616 }, 617 new IllegalExceptionCase("setFields with more fieldNames than " + 618 "fieldValues") { 619 void provoke(Descriptor d) { 620 d.setFields(new String[] {"A", "B"}, new String[] {"C"}); 621 } 622 }, 623 new IllegalExceptionCase("setFields with more fieldValues than " + 624 "fieldNames") { 625 void provoke(Descriptor d) { 626 d.setFields(new String[] {"A"}, new String[] {"B", "C"}); 627 } 628 }, 629 new IllegalExceptionCase("setFields with null element of fieldNames") { 630 void provoke(Descriptor d) { 631 d.setFields(new String[] {null}, new String[] {"X"}); 632 } 633 } 634 635 ); 636 637 static <T> T[] constantArray(T... array) { 638 return array; 639 } 640 641 static String string(Object x) { 642 if (x instanceof Object[]) 643 return Arrays.asList((Object[]) x).toString(); 644 else 645 return String.valueOf(x); 646 } 647 648 static Set<String> strings(String... values) { 649 return new TreeSet<String>(Arrays.asList(values)); 650 } 651 652 static <T> Set<T> set(T[] values) { 653 return new HashSet<T>(Arrays.asList(values)); 654 } 655 656 static boolean immutable(Descriptor d) { 657 return (d instanceof ImmutableDescriptor); 658 // good enough for our purposes 659 } 660 }