1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.lib;
  28 
  29 import java.io.PrintWriter;
  30 import java.lang.reflect.InvocationTargetException;
  31 import java.lang.reflect.Method;
  32 import java.util.Enumeration;
  33 import java.util.Hashtable;
  34 import java.util.Map;
  35 import java.util.Vector;
  36 import com.sun.javatest.Status;
  37 import com.sun.javatest.Test;
  38 
  39 /**
  40  * A handler for the set of test cases in a test.
  41  * Test cases are those methods with no args that return a
  42  * {@link com.sun.javatest.Status status}.
  43  * Test cases can be explicitly selected into or excluded from the
  44  * set.
  45  */
  46 public class TestCases {
  47     /**
  48      * Exception used to report internal errors.
  49      */
  50     public static class Fault extends Exception {
  51         /**
  52          * Construct a new Fault object that signals failure
  53          * with a corresponding message.
  54          *
  55          * @param s the string containing a comment
  56          */
  57         public Fault(String s) {
  58             super(s);
  59         }
  60     }
  61 
  62     /**
  63      * Create an object to handle the test cases of the given test.
  64      * @param t The test containing the test cases.
  65      * @param log An optional stream to which to write log messages.
  66      *   Use null if messages are not desired.
  67      */
  68     public TestCases(Test t, PrintWriter log) {
  69         test = t;
  70         this.log = log;
  71         testClass = t.getClass();
  72     }
  73 
  74     /**
  75      * Explicitly select a set of test cases by name. Subsequent calls
  76      * are cumulative; if no selections are made, the default is all
  77      * test cases are selected. Excluded tests will be excluded from the
  78      * selection; the order of select and exclude calls does not matter.
  79      * @param testCaseNames a comma-separated list of test cases names.
  80      * Each name must identify a method in the test object, that takes
  81      * no arguments and returns a {@link com.sun.javatest.Status status}.
  82      * @throws TestCases.Fault if any of the test case names are invalid.
  83      */
  84     public void select(String testCaseNames) throws Fault {
  85         select(split(testCaseNames));
  86     }
  87 
  88 
  89     /**
  90      * Explicitly select a set of test cases by name. Subsequent calls
  91      * are cumulative; if no selections are made, the default is all
  92      * test cases are selected. Excluded tests will be excluded from the
  93      * selection; the order of select and exclude calls does not matter.
  94      * @param testCaseNames an array of test cases names.
  95      * Each name must identify a method in the test object, that takes
  96      * no arguments and returns a {@link com.sun.javatest.Status status}.
  97      * @throws TestCases.Fault if any of the test case names are invalid.
  98      */
  99     public void select(String[] testCaseNames) throws Fault  {
 100         for (int i = 0; i < testCaseNames.length; i++) {
 101             String t = testCaseNames[i];
 102             selectedCases.put(t, getTestCase(t));
 103         }
 104     }
 105 
 106 
 107     /**
 108      * Explicitly exclude a set of test cases by name. Subsequent calls
 109      * are cumulative; by default, no test cases are excluded.
 110      * @param testCaseNames a comma-separated list of test cases names.
 111      * Each name must identify a method in the test object, that takes
 112      * no arguments and returns a {@link com.sun.javatest.Status status}.
 113      * @throws TestCases.Fault if any of the test case names are invalid.
 114      */
 115     public void exclude(String testCaseNames) throws Fault  {
 116         exclude(split(testCaseNames));
 117     }
 118 
 119 
 120     /**
 121      * Explicitly exclude a set of test cases by name. Subsequent calls
 122      * are cumulative; by default, no test cases are excluded.
 123      * @param testCaseNames an array of test cases names.
 124      * Each name must identify a method in the test object, that takes
 125      * no arguments and returns a {@link com.sun.javatest.Status status}.
 126      * @throws TestCases.Fault if any of the test case names are invalid.
 127      */
 128     public void exclude(String[] testCaseNames) throws Fault  {
 129         for (int i = 0; i < testCaseNames.length; i++) {
 130             String t = testCaseNames[i];
 131             excludedCases.put(t, getTestCase(t));
 132         }
 133     }
 134 
 135 
 136     /**
 137      * Return an enumeration of the selected test cases, based on the
 138      * select and exclude calls that have been made, if any.
 139      * @return An enumeration of the test cases.
 140      */
 141     public Enumeration enumerate() {
 142         Vector<Method> v = new Vector<>();
 143         if (selectedCases.isEmpty()) {
 144             Method[] methods = testClass.getMethods();
 145             for (int i = 0; i < methods.length; i++) {
 146                 Method m = methods[i];
 147                 if (excludedCases.get(m.getName()) == null) {
 148                     Class[] paramTypes = m.getParameterTypes();
 149                     Class returnType = m.getReturnType();
 150                     if ((paramTypes.length == 0) && Status.class.isAssignableFrom(returnType))
 151                         v.addElement(m);
 152                 }
 153             }
 154         }
 155         else {
 156             for (Method m : selectedCases.values()) {
 157                 if (excludedCases.get(m.getName()) == null)
 158                     v.addElement(m);
 159                 }
 160         }
 161         return v.elements();
 162     }
 163 
 164 
 165     /**
 166      * Invoke each of the selected test cases, based upon the select and exclude
 167      * calls that have been made, if any.
 168      * If the test object provides a public method
 169      * {@link com.sun.javatest.Status}invokeTestCase({@link java.lang.reflect.Method})
 170      * that method will be called to invoke the test cases; otherwise, the test
 171      * cases will be invoked directly.
 172      * It is an error if no test cases are selected, (or if they have all been excluded.)
 173      * @return the combined result of executing all the test cases.
 174      */
 175     public Status invokeTestCases() {
 176         // see if test object provides  Status invokeTestCase(Method m)
 177         Method invoker;
 178         try {
 179             invoker = testClass.getMethod("invokeTestCase", new Class[] {Method.class});
 180             if (!Status.class.isAssignableFrom(invoker.getReturnType()))
 181                 invoker = null;
 182         }
 183         catch (NoSuchMethodException e) {
 184             invoker = null;
 185         }
 186 
 187         MultiStatus ms = new MultiStatus(log);
 188         for (Enumeration e = enumerate(); e.hasMoreElements(); ) {
 189             Method m = (Method)(e.nextElement());
 190             Status s;
 191             try {
 192                 if (invoker != null)
 193                     s = (Status)invoker.invoke(test, new Object[] {m});
 194                 else
 195                     s = (Status)m.invoke(test, noArgs);
 196             }
 197             catch (IllegalAccessException ex) {
 198                 s = Status.failed("Could not access test case: " + m.getName());
 199             }
 200             catch (InvocationTargetException ex) {
 201                 printStackTrace(ex.getTargetException());
 202                 s = Status.failed("Exception from test case: " +
 203                                        ex.getTargetException().toString());
 204             }
 205             catch (ThreadDeath t) {
 206                 printStackTrace(t);
 207                 throw t;
 208             }
 209             catch (Throwable t) {
 210                 printStackTrace(t);
 211                 s = Status.failed("Unexpected Throwable: " + t);
 212             }
 213 
 214             ms.add(m.getName(), s);
 215         }
 216         if (ms.getTestCount() == 0)
 217             return Status.passed("Test passed by default: no test cases executed.");
 218         else
 219             return ms.getStatus();
 220     }
 221 
 222     /**
 223      * Print a stack trace for an exception to the log.
 224      * @param t The Throwable for which to print the trace
 225      */
 226     protected void printStackTrace(Throwable t) {
 227         if (log != null)
 228             t.printStackTrace(log);
 229     }
 230 
 231     /**
 232      * Look up a test case in the test object.
 233      * @param name the name of the test case; it must identify a method
 234      *          Status name()
 235      * @return the selected method
 236      * @throws Fault if the name does not identify an appropriate method.
 237      */
 238     private Method getTestCase(String name) throws Fault {
 239         try {
 240             Method m = testClass.getMethod(name, noArgTypes);
 241             if (!Status.class.isAssignableFrom(m.getReturnType()))
 242                 throw new Fault("Method for test case '" + name + "' has wrong return type" );
 243             return m;
 244         }
 245         catch (NoSuchMethodException e) {
 246             throw new Fault("Could not find test case: " + name);
 247         }
 248         catch (SecurityException e) {
 249             throw new Fault(e.toString());
 250         }
 251     }
 252 
 253     private String[] split(String s) {
 254         Vector<String> v = new Vector<>();
 255         int start = 0;
 256         for (int i = s.indexOf(','); i != -1; i = s.indexOf(',', start)) {
 257             v.addElement(s.substring(start, i));
 258             start = i + 1;
 259         }
 260         if (start != s.length())
 261             v.addElement(s.substring(start));
 262         String[] ss = new String[v.size()];
 263         v.copyInto(ss);
 264         return ss;
 265     }
 266 
 267     private Object test;
 268     private Class<?> testClass;
 269     private Map<String, Method> selectedCases = new Hashtable<>();
 270     private Map<String, Method> excludedCases = new Hashtable<>();
 271     private PrintWriter log;
 272 
 273     private static final Object[] noArgs = { };
 274     private static final Class[] noArgTypes = { };
 275 }