001/* JUnit test the Kieler Layout mechanism. 002 003 Copyright (c) 2011-2017 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 */ 028 029package ptolemy.vergil.basic.layout.kieler.test.junit; 030 031import static org.junit.Assert.assertArrayEquals; 032 033import java.io.File; 034import java.util.Iterator; 035 036import javax.swing.SwingUtilities; 037 038import org.junit.AfterClass; 039import org.junit.BeforeClass; 040 041import ptolemy.actor.gui.Configuration; 042import ptolemy.actor.gui.ConfigurationApplication; 043import ptolemy.actor.gui.Tableau; 044import ptolemy.kernel.CompositeEntity; 045import ptolemy.kernel.util.NamedObj; 046import ptolemy.util.Diff; 047import ptolemy.util.FileUtilities; 048import ptolemy.util.StringUtilities; 049import ptolemy.vergil.basic.BasicGraphFrame; 050import ptolemy.vergil.basic.PtolemyLayoutAction; 051import ptolemy.vergil.basic.layout.kieler.KielerLayoutAction; 052 053/////////////////////////////////////////////////////////////////// 054//// KielerJUnitTest 055/** 056 * Test out Kieler by open models, using Kieler to layout the graph 057 * and then doing undo and redo. 058 * 059 * <p>There are two types of tests.</p> 060 061 * <p>1. {@link #_layoutModelCompareAgainstFile(NamedObj, String)} We 062 * read in a model and run the Kieler layout algorithm on on the model 063 * and compare the results against the original model. This test is 064 * run on regression tests to be sure that the Kieler algorithm has 065 * not changed. Typically, the models are in the 066 * ptolemy/vergil/basic/layout/kieler/test/junit/models 067 * subdirectory.</p> 068 * 069 * <p>2. We read in a model, use the Ptolemy layouter and then the 070 * Kieler layout algorithm. We then do an undo, a redo and an undo 071 * and compare. the model against the model after the Ptolemy 072 * layouter. This test is used run on models in the Ptolemy tree to 073 * make sure that the Kieler layouter and the undo/redo mechanism 074 * works.</p> 075 * 076 * @author Christopher Brooks 077 * @version $Id$ 078 * @since Ptolemy II 10.0 079 * @Pt.ProposedRating Red (cxh) 080 * @Pt.AcceptedRating Red (cxh) 081 */ 082public class KielerJUnitTest { 083 084 /** Test the Kieler layout facility. 085 * 086 * <p>To run, use:</p> 087 * 088 * <pre> 089 * java -classpath \ 090 * $PTII:$PTII/lib/junit-4.8.2.jar:$PTII/lib/kieler.jar \ 091 * ptolemy.vergil.basic.layout.kieler.test.junit.KielerJUnitTest 092 * </pre> 093 * 094 * @param args Not used. 095 */ 096 public static void main(String args[]) { 097 org.junit.runner.JUnitCore.main( 098 "ptolemy.vergil.basic.layout.kieler.test.junit.KielerJUnitTest"); 099 } 100 101 /** Save the previous value of the ptolemy.ptII.doNotExit property 102 * and set it to true while running this test. 103 * 104 * @exception Exception If there is a problem opening the model 105 */ 106 @BeforeClass 107 public static void openSentinelModel() throws Exception { 108 // Avoid calling System.exit(). 109 _previousPropertyValue = StringUtilities 110 .getProperty("ptolemy.ptII.doNotExit"); 111 System.setProperty("ptolemy.ptII.doNotExit", "true"); 112 } 113 114 // FIXME the following two tests are deactivated because they fail 115 // due to the WindowPropertiesAttribute being different between the 116 // saved (to disk) and layouted model and the freshly layouted model. 117 118 // class="ptolemy.actor.gui.WindowPropertiesAttribute" value="{bounds={465, 154, 833, 619}, 119 120 /** 121 * Test the layout facility by reading in a models, stripping 122 * out the graphical elements, laying out the models, comparing 123 * the new results with the known good results and then doing 124 * undo and redo. 125 * @exception Exception If there is a problem reading or laying 126 * out a model. 127 */ 128 // @org.junit.Test 129 // public void runConstDisplay() throws Exception { 130 // _layoutTest( 131 // "$CLASSPATH/ptolemy/vergil/basic/layout/kieler/test/junit/models/ConstDisplay.xml", 132 // true); 133 // } 134 135 /** Test the layout of the ConstConstDisplay model. 136 * @exception Exception If there is a problem reading or laying 137 * out a model. 138 */ 139 // @org.junit.Test 140 // public void runConstConstDisplay() throws Exception { 141 // _layoutTest( 142 // "$CLASSPATH/ptolemy/vergil/basic/layout/kieler/test/junit/models/ConstConstDisplay.xml", 143 // true); 144 // } 145 146 /* ---------------------------- 147 * Actor Tests 148 * ---------------------------- */ 149 150 /** Test the layout of the modulation model. 151 * @exception Exception If there is a problem reading or laying 152 * out a model. 153 */ 154 @org.junit.Test 155 public void runModulation() throws Exception { 156 _layoutTest("$CLASSPATH/ptolemy/moml/demo/modulation.xml", false); 157 } 158 159 /** Reset the ptolemy.ptII.doNotExit property to the previous value. 160 * @exception Throwable If there is a problem setting the property. 161 */ 162 @AfterClass 163 public static void closeSentinelModel() throws Throwable { 164 System.setProperty("ptolemy.ptII.doNotExit", _previousPropertyValue); 165 } 166 167 /////////////////////////////////////////////////////////////////// 168 //// protected methods //// 169 170 /** 171 * Test the layout facility by reading in a model, laying out the 172 * model, comparing the new results with the known good results 173 * and then doing undo and redo. 174 * 175 * <p>This is the main entry point for Kieler layout tests.</p> 176 * 177 * <p>The caller of this method need <b>not</b>be in the Swing 178 * Event Thread.</p> 179 * 180 * @param modelFileName The file name of the test model. 181 * @param compareAgainstOriginal If true, then run the Kieler 182 * Layouter and compare against the original file. If false, run 183 * the Ptolemy layouter, the Kieler layouter, then undo, redo, 184 * undo and compare against the output after the Ptolemy layouter. 185 * @exception Exception If the file name cannot be read or laid out. 186 */ 187 protected void _layoutTest(final String modelFileName, 188 final boolean compareAgainstOriginal) throws Exception { 189 190 // FIXME: this seem wrong: The inner classes are in different 191 // threads and can only access final variables. However, we 192 // use an array as a final variable, but we change the value 193 // of the element of the array. Is this thread safe? 194 final CompositeEntity[] model = new CompositeEntity[1]; 195 196 final Throwable[] throwable = new Throwable[1]; 197 throwable[0] = null; 198 199 // The basic structure of this method is that we call 200 // invokeAndWait() on operations that display graphics and 201 // then sleep this thread. This gives us a way to see the 202 // model be laid out. 203 204 ///// 205 // Open the model. 206 Runnable openModelAction = new Runnable() { 207 @Override 208 public void run() { 209 try { 210 System.out.print(" " + modelFileName + " "); 211 model[0] = ConfigurationApplication 212 .openModelOrEntity(modelFileName); 213 } catch (Throwable throwableCause) { 214 throwable[0] = throwableCause; 215 throw new RuntimeException(throwableCause); 216 } 217 } 218 }; 219 SwingUtilities.invokeAndWait(openModelAction); 220 if (throwable[0] != null || model[0] == null) { 221 throw new Exception( 222 "Failed to open " + modelFileName + throwable[0]); 223 } 224 String baseMoML = model[0].exportMoML(); 225 _basicGraphFrame = _getBasicGraphFrame(model[0]); 226 227 _sleep(); 228 229 ///// 230 // Layout the model using either the Kieler layout mechanism 231 // or the krufty Ptolemy Layout mechanism. 232 // Note that this call is quite important to "normalize" old 233 // models. In older models the location was stored with an expression 234 // like value="0, 0", while in newer models curly braces are used 235 // value="{0, 0}". 236 // If this layout call is not performed, one would have to export 237 // the model and load it again to get a normalized version 238 Runnable layoutModelAction = new Runnable() { 239 @Override 240 public void run() { 241 try { 242 if (compareAgainstOriginal) { 243 _layoutModelCompareAgainstFile(model[0], modelFileName); 244 } else { 245 // Invoke the crufty Ptolemy layout mechanism and export. 246 new PtolemyLayoutAction().doAction(model[0]); 247 _basicGraphFrame.report("Ptolemy Layout done"); 248 } 249 } catch (Throwable throwableCause) { 250 throwable[0] = throwableCause; 251 throw new RuntimeException(throwableCause); 252 } 253 } 254 }; 255 SwingUtilities.invokeAndWait(layoutModelAction); 256 _sleep(); 257 if (throwable[0] != null || model[0] == null) { 258 throw new Exception( 259 "Failed to layout " + modelFileName + throwable[0]); 260 } 261 262 ///// 263 // Optionally invoke the Ptolemy layout mechanism. 264 if (!compareAgainstOriginal) { 265 // We just laid out the model with Ptolemy, now sleep so 266 // that we see the Ptolemy layout and then layout the 267 // model with Kieler. Don't do a comparison yet. 268 _sleep(); 269 // The "original" model is now the model laid out with 270 // the Ptoelmy mechanism. 271 baseMoML = model[0].exportMoML(); 272 Runnable kielerLayoutModelAction = new Runnable() { 273 @Override 274 public void run() { 275 try { 276 // Invoke the Kieler layout mechanism. 277 new KielerLayoutAction().doAction(model[0]); 278 } catch (Throwable throwableCause) { 279 throwable[0] = throwableCause; 280 throw new RuntimeException(throwableCause); 281 } 282 } 283 }; 284 SwingUtilities.invokeAndWait(kielerLayoutModelAction); 285 _sleep(); 286 if (throwable[0] != null || model[0] == null) { 287 throw new Exception( 288 "Failed to layout " + modelFileName + throwable[0]); 289 } 290 } 291 292 ///// 293 // Loop through undo and redo 294 String laidOutMoML = model[0].exportMoML(); 295 for (int i = 1; i <= 2; i++) { 296 Runnable undoAction = new Runnable() { 297 @Override 298 public void run() { 299 try { 300 _undo(model[0]); 301 } catch (Throwable throwableCause) { 302 throwable[0] = throwableCause; 303 throw new RuntimeException(throwableCause); 304 } 305 } 306 }; 307 SwingUtilities.invokeAndWait(undoAction); 308 _sleep(); 309 if (throwable[0] != null || model[0] == null) { 310 throw new Exception( 311 "Failed to undo " + modelFileName + throwable[0]); 312 } 313 314 String undoMoML = model[0].exportMoML(); 315 if (_debug || !baseMoML.equals(undoMoML)) { 316 System.out.println("Difference between original MoML" 317 + " and the exported MoML after Kieler Layout and then undo:"); 318 System.out.println(Diff.diff(baseMoML, undoMoML)); 319 } 320 321 assertArrayEquals(baseMoML.getBytes(), undoMoML.getBytes()); 322 323 Runnable redoAction = new Runnable() { 324 @Override 325 public void run() { 326 try { 327 _redo(model[0]); 328 } catch (Throwable throwableCause) { 329 throwable[0] = throwableCause; 330 throw new RuntimeException(throwableCause); 331 } 332 } 333 }; 334 SwingUtilities.invokeAndWait(redoAction); 335 String redoMoML = model[0].exportMoML(); 336 if (_debug || !laidOutMoML.equals(redoMoML)) { 337 System.out.println("Difference between laid out MoML" 338 + " and the exported MoML after Kieler Layout and then undo, then redo:"); 339 System.out.println(Diff.diff(laidOutMoML, redoMoML)); 340 } 341 342 assertArrayEquals(laidOutMoML.getBytes(), redoMoML.getBytes()); 343 _sleep(); 344 if (throwable[0] != null || model[0] == null) { 345 throw new Exception( 346 "Failed to redo " + modelFileName + throwable[0]); 347 } 348 349 } 350 351 ///// 352 // Close the model. 353 Runnable closeAction = new Runnable() { 354 @Override 355 public void run() { 356 try { 357 ConfigurationApplication 358 .closeModelWithoutSavingOrExiting(model[0]); 359 } catch (Throwable throwableCause) { 360 throwable[0] = throwableCause; 361 throw new RuntimeException(throwableCause); 362 } 363 } 364 }; 365 SwingUtilities.invokeAndWait(closeAction); 366 _sleep(); 367 if (throwable[0] != null || model[0] == null) { 368 throw new Exception( 369 "Failed to close " + modelFileName + throwable[0]); 370 } 371 } 372 373 /** Lay out the model and compare the results against the original 374 * model file name. 375 * 376 * <p>The caller of this method should be in the Swing Event 377 * Thread.</p> 378 * 379 * @param model The model. 380 * @param modelFileName The pathname of the model, used for 381 * comparing. 382 * @exception Exception If thrown while opening or laying 383 * out the model. 384 */ 385 protected void _layoutModelCompareAgainstFile(NamedObj model, 386 String modelFileName) throws Exception { 387 try { 388 // Invoke the Kieler layout mechanism. 389 new KielerLayoutAction().doAction(model); 390 391 // Export the model and compare it with the original. 392 String laidOutMoML = model.exportMoML(); 393 File canonicalModelFile = FileUtilities.nameToFile(modelFileName, 394 null); 395 String canonicalModelFileName = canonicalModelFile 396 .getCanonicalPath(); 397 byte[] baseMoMLBytes = FileUtilities.binaryReadURLToByteArray( 398 canonicalModelFile.toURI().toURL()); 399 400 if (_debug || !new String(baseMoMLBytes).equals(laidOutMoML)) { 401 System.out.println("Difference between " 402 + canonicalModelFileName 403 + " and the exported MoML after Kieler Layout:"); 404 System.out.println( 405 Diff.diff(new String(baseMoMLBytes), laidOutMoML)); 406 } 407 assertArrayEquals(laidOutMoML.getBytes(), baseMoMLBytes); 408 409 } catch (Throwable throwable) { 410 throwable.printStackTrace(); 411 } 412 } 413 414 /** Redo the last operation on the model. 415 * 416 * <p>The caller of this method should be in the Swing Event 417 * Thread.</p> 418 * @param model The model upon which the last operation 419 * should be redone. 420 */ 421 protected void _redo(NamedObj model) { 422 _basicGraphFrame.report("About to redo"); 423 _basicGraphFrame.redo(); 424 _basicGraphFrame.report("Redo done"); 425 } 426 427 /** Sleep the current thread, which is usually not the Swing Event 428 * Dispatch Thread. 429 */ 430 protected static void _sleep() { 431 try { 432 Thread.sleep(1000); 433 } catch (Throwable ex) { 434 //Ignore 435 } 436 } 437 438 /** Undo the last operation on the model. 439 * 440 * <p>The caller of this method should be in the Swing Event 441 * Thread.</p> 442 * @param model The model upon which the last operation 443 * should be redone. 444 */ 445 protected void _undo(NamedObj model) { 446 _basicGraphFrame.report("About to undo"); 447 _basicGraphFrame.undo(); 448 _basicGraphFrame.report("Undo done"); 449 } 450 451 /////////////////////////////////////////////////////////////////// 452 //// private methods //// 453 454 private static BasicGraphFrame _getBasicGraphFrame(NamedObj model) { 455 // See PtolemyLayoutAction for similar code. 456 BasicGraphFrame frame = null; 457 Iterator tableaux = Configuration.findEffigy(model) 458 .entityList(Tableau.class).iterator(); 459 while (tableaux.hasNext()) { 460 Tableau tableau = (Tableau) tableaux.next(); 461 if (tableau.getFrame() instanceof BasicGraphFrame) { 462 frame = (BasicGraphFrame) tableau.getFrame(); 463 break; 464 } 465 } 466 // Fetch everything needed to build the LayoutTarget. 467 //GraphController graphController = ((BasicGraphFrame)frame).getJGraph() 468 // .getGraphPane().getGraphController(); 469 //AbstractBasicGraphModel graphModel = (AbstractBasicGraphModel) ((BasicGraphFrame)frame) 470 // .getJGraph().getGraphPane().getGraphController() 471 // .getGraphModel(); 472 return frame; 473 } 474 475 /////////////////////////////////////////////////////////////////// 476 //// private fields //// 477 478 /** The BasicGraphFrame of the model. */ 479 private BasicGraphFrame _basicGraphFrame; 480 481 /** Set to true for debugging messages. */ 482 private final boolean _debug = false; 483 484 /** The value of the ptolemy.ptII.doNotExit property before we started. 485 */ 486 private static String _previousPropertyValue = ""; 487 488}