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}