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