001/* Handle exceptions thrown in tests. 002 003 Copyright (c) 2006-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.actor.lib; 029 030import ptolemy.actor.AbstractInitializableAttribute; 031import ptolemy.actor.parameters.SharedParameter; 032import ptolemy.data.BooleanToken; 033import ptolemy.data.IntToken; 034import ptolemy.data.expr.Parameter; 035import ptolemy.data.expr.StringParameter; 036import ptolemy.data.type.BaseType; 037import ptolemy.kernel.CompositeEntity; 038import ptolemy.kernel.util.ExceptionHandler; 039import ptolemy.kernel.util.IllegalActionException; 040import ptolemy.kernel.util.NameDuplicationException; 041import ptolemy.kernel.util.NamedObj; 042import ptolemy.util.MessageHandler; 043 044/////////////////////////////////////////////////////////////////// 045//// TestExceptionAttribute 046 047/** 048 This actor tests for exceptions that are expected to occur when 049 running a test model. When an exception is 050 thrown by the model, this actor is invoked. It has two 051 working modes, training mode and non-training mode. If in training mode, 052 this actor handles an exception by recording the exception message. If 053 not in training mode, this actor first compares the previously stored 054 (assumed correct) message to the exception message and then throws an 055 exception if the two messages are not the same. 056 Also, if a test runs to completion without throwing an exception, this actor 057 throws an exception in its wrapup() method. An exception is expected. 058 059 @author Edward A. Lee 060 @version $Id$ 061 @since Ptolemy II 10.0 062 @Pt.ProposedRating Yellow (hyzheng) 063 @Pt.AcceptedRating Yellow (hyzheng) 064 */ 065public class TestExceptionAttribute extends AbstractInitializableAttribute 066 implements ExceptionHandler { 067 068 /** Create a new actor in the specified container with the specified 069 * name. The name must be unique within the container or an exception 070 * is thrown. The container argument must not be null, or a 071 * NullPointerException will be thrown. 072 * 073 * @param container The container. 074 * @param name The name of this actor within the container. 075 * @exception IllegalActionException If this actor cannot be contained 076 * by the proposed container (see the setContainer() method). 077 * @exception NameDuplicationException If the name coincides with 078 * an entity already in the container. 079 */ 080 public TestExceptionAttribute(CompositeEntity container, String name) 081 throws NameDuplicationException, IllegalActionException { 082 super(container, name); 083 correctExceptionMessage = new StringParameter(this, 084 "correctExceptionMessage"); 085 correctExceptionMessage.setExpression(""); 086 087 matchPrefixOfLength = new Parameter(this, "matchPrefixOfLength"); 088 matchPrefixOfLength.setExpression("0"); 089 matchPrefixOfLength.setTypeEquals(BaseType.INT); 090 091 trainingMode = new SharedParameter(this, "trainingMode", getClass(), 092 "false"); 093 trainingMode.setTypeEquals(BaseType.BOOLEAN); 094 _ranInitialize = false; 095 _invoked = false; 096 097 // In order for this to show up in the vergil library, it has to have 098 // an icon description. 099 _attachText("_iconDescription", "<svg>\n" 100 + "<polygon points=\"0,0 22,-8 12,-28 32,-18 40,-40 48,-18 68,-28 58,-8 80,0 58,8 68,28 48,18 40,40 32,18 12,28 22,8 0,0\" " 101 + "style=\"fill:pink;stroke:red\"/>\n" 102 + "<text x=\"35\" y=\"10\" style=\"font-size:30;fill:red\">!</text>\n" 103 + "</svg>\n"); 104 } 105 106 /////////////////////////////////////////////////////////////////// 107 //// parameters //// 108 109 /** The correct exception message to be compared against. */ 110 public StringParameter correctExceptionMessage; 111 112 /** If greater than zero, then check that the first <i>n</i> 113 * characters of the exception message match, where <i>n</i> 114 * is the value of this parameter. Otherwise, if this is zero 115 * or negative, then check all the characters. This is an 116 * integer that defaults to zero. 117 */ 118 public Parameter matchPrefixOfLength; 119 120 /** If true, then collect the exception message and set the 121 * correctExceptionMessage parameter with the content of the 122 * exception. This parameter is a boolean, and it defaults to false. 123 * It is a shared parameter, meaning 124 * that changing it for any one instance in a model will change 125 * it for all instances in the model. 126 */ 127 public SharedParameter trainingMode; 128 129 /////////////////////////////////////////////////////////////////// 130 //// public methods //// 131 132 /** Initialize. */ 133 @Override 134 public void initialize() throws IllegalActionException { 135 super.initialize(); 136 _ranInitialize = true; 137 _invoked = false; 138 } 139 140 /** Handle an exception thrown in a test. If in training 141 * mode, simply record the exception message. If not in training mode, 142 * first compare the stored good message against the exception message. 143 * If they are the same, do nothing. Otherwise, throw the exception again. 144 * @param context The object in which the error occurred. 145 * @param exception The exception to be handled. 146 * @return True if the exception message is the same as the saved message. 147 * @exception IllegalActionException If cannot get a valid token from 148 * the trainingMode parameter or the exception message is not the same as 149 * the stored message. 150 */ 151 @Override 152 public boolean handleException(NamedObj context, Throwable exception) 153 throws IllegalActionException { 154 _invoked = true; 155 boolean training = ((BooleanToken) trainingMode.getToken()) 156 .booleanValue(); 157 if (training) { 158 correctExceptionMessage.setExpression(exception.getMessage()); 159 correctExceptionMessage.setPersistent(true); 160 } else { 161 String correct = correctExceptionMessage.stringValue(); 162 if (correct.length() == 0) { 163 throw new IllegalActionException(this, exception, 164 "No training message to check against. Perhaps you need to set training mode?"); 165 } 166 int prefixLength = ((IntToken) matchPrefixOfLength.getToken()) 167 .intValue(); 168 if (prefixLength <= 0) { 169 String message = exception.getMessage(); 170 if (message == null) { 171 throw new IllegalActionException(this, exception, 172 "Expected:\n" 173 + correctExceptionMessage.stringValue() 174 + "\nBut got null error message:\n" + exception); 175 } else if (!message.equals(correctExceptionMessage.stringValue())) { 176 throw new IllegalActionException(this, exception, 177 "Expected:\n" 178 + correctExceptionMessage.stringValue() 179 + "\nBut got:\n" + exception.getMessage()); 180 } 181 } else { 182 if (correct.length() < prefixLength) { 183 prefixLength = correct.length(); 184 } 185 String prefix = correctExceptionMessage.stringValue() 186 .substring(0, prefixLength); 187 if (!exception.getMessage().startsWith(prefix)) { 188 throw new IllegalActionException(this, exception, 189 "Expected a message starting with:\n" + prefix 190 + "\nBut got:\n" + exception.getMessage()); 191 } 192 } 193 } 194 return true; 195 } 196 197 /** Call the super.wrapup() method. Check whether this actor has 198 * been invoked to handle exceptions. If not, throw an exception. 199 * Otherwise, do nothing. 200 * @exception IllegalActionException If this actor has not been 201 * invoked to handle exceptions. 202 */ 203 @Override 204 public void wrapup() throws IllegalActionException { 205 super.wrapup(); 206 if (((BooleanToken) trainingMode.getToken()).booleanValue()) { 207 if (MessageHandler.isNonInteractive()) { 208 throw new IllegalActionException(this, 209 TRAINING_MODE_ERROR_MESSAGE); 210 } else { 211 System.err.println("Warning: '" + getFullName() 212 + "' is in training mode, set the trainingMode " 213 + "parameter to false before checking in"); 214 } 215 } 216 if (!_invoked) { 217 if (_ranInitialize) { 218 throw new IllegalActionException(this, " was initialized and " 219 + "should " 220 + "have handled an exception but did not see any."); 221 } else { 222 System.out.println(getFullName() + ": initialize() was not " 223 + "invoked, but wrapup() was. This is unusual, but " 224 + "not always a problem. For example, exporting " 225 + "JNLP will do this."); 226 } 227 } else { 228 _ranInitialize = false; 229 _invoked = false; 230 } 231 } 232 233 /////////////////////////////////////////////////////////////////// 234 //// public variables //// 235 236 /** Exception message that is used if we are running under 237 * the nightly build and the trainingMode parameter is true. 238 */ 239 public static final String TRAINING_MODE_ERROR_MESSAGE = "Training Mode set for test actor and isRunningNightlyBuild()\n" 240 + " returned true, indicating that the\n" 241 + " ptolemy.ptII.isRunningNightlyBuild property is set.\n" 242 + " The trainingMode parameter should not be set in files\n" 243 + " that are checked into the nightly build!" 244 + " To run the tests in nightly build mode, use" 245 + " make nightly"; 246 247 /////////////////////////////////////////////////////////////////// 248 //// private variables //// 249 250 private boolean _invoked = false; 251 252 /** True if initialize() was invoked. 253 * Exporting to JNLP can result in preinitialize() being invoked 254 * and then wrapup(). 255 */ 256 private boolean _ranInitialize = false; 257}