blob: 71c1518ce1b0bf6665c92e4cf60a693653a73a09 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2004-2005 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
20 * CA 95054 USA or visit www.sun.com if you need additional information or
21 * have any 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
34import java.io.*;
35import java.lang.reflect.*;
36import java.util.*;
37import javax.management.*;
38
39public 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}