1/* 2 * Copyright 2001-2008 Artima, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package org.scalatest.tools 17 18import org.scalatest._ 19import java.awt.BorderLayout 20import java.awt.Container 21import java.awt.Dimension 22import java.awt.GridLayout 23import java.awt.Point 24import java.awt.event.ActionEvent 25import java.awt.event.ActionListener 26import java.awt.event.KeyEvent 27import java.io.ByteArrayOutputStream 28import java.io.File 29import java.io.PrintWriter 30import java.net.URL 31import javax.swing.AbstractAction 32import javax.swing.DefaultListModel 33import javax.swing.ImageIcon 34import javax.swing.JButton 35import javax.swing.JCheckBoxMenuItem 36import javax.swing.JDialog 37import javax.swing.JFrame 38import javax.swing.WindowConstants 39import javax.swing.JLabel 40import javax.swing.JList 41import javax.swing.JMenu 42import javax.swing.JMenuBar 43import javax.swing.JMenuItem 44import javax.swing.JOptionPane 45import javax.swing.JPanel 46import javax.swing.JScrollPane 47import javax.swing.JEditorPane 48import javax.swing.KeyStroke 49import javax.swing.ListSelectionModel 50import javax.swing.border.BevelBorder 51import javax.swing.border.EmptyBorder 52import javax.swing.event.ListSelectionEvent 53import javax.swing.event.ListSelectionListener 54import Runner.usingEventDispatchThread 55import Runner.withClassLoaderAndDispatchReporter 56import java.util.concurrent.Semaphore 57import java.awt.event.WindowAdapter 58import java.awt.event.WindowEvent 59import java.awt.EventQueue 60import org.scalatest.prop.PropertyTestFailedException 61import org.scalatest.events._ 62import EventToPresent.eventToEventToPresent 63 64/** 65 * The main class for Runner's GUI. 66 * 67 * eventTypesToCollect are the types of events that should be collected as a run runs. 68 * This comes from the set of config options following the -g in the invocation of Runner. 69 * If it is -gZ, for example, only test starting events will be collected as the runs run. 70 * We don't collect options that aren't selected, because long runs can generate a lot of 71 * events that would take up a lot of memory. 72 * 73 * @author Bill Venners 74 */ 75private[scalatest] class RunnerJFrame(val eventTypesToCollect: Set[EventToPresent], 76 reporterConfigurations: ReporterConfigurations, suitesList: List[String], junitsList: List[String], runpathList: List[String], filter: Filter, 77 propertiesMap: Map[String, String], concurrent: Boolean, memberOfList: List[String], beginsWithList: List[String], 78 testNGList: List[String], passFailReporter: Option[Reporter], numThreads: Int) extends 79 JFrame(Resources("ScalaTestTitle")) with RunDoneListener with RunnerGUI { 80 81 // This should only be updated by the event handler thread. 82 private var currentState: RunnerGUIState = RunningState 83 84 // The default options in the graphic view. Just show runs 85 // and failures. This is also a selection in the View menu. 86 private val runsAndFailures: Set[EventToPresent] = 87 Set( 88 PresentRunStarting, 89 PresentTestFailed, 90 PresentSuiteAborted, 91 PresentRunStopped, 92 PresentRunAborted, 93 PresentRunCompleted 94 ) 95 96 // These are the actual options to view in the list of events. 97 // This must be the same set or a subset of eventTypesToCollect, 98 // because you can't view something that wasn't collected. 99 // This should only be updated by the event handler thread. 100 private var viewOptions = runsAndFailures 101 102 private val optionsMap: Map[EventToPresent, JCheckBoxMenuItem] = initializeOptionsMap 103 104 private val aboutBox: AboutJDialog = initializeAboutBox() 105 106 // The list of events collected from the most recent run 107 // The most recently added event is at the head of the list. 108 // This is volatile because it is sorted outside the event handler thread. 109 @volatile private var collectedEvents: List[EventHolder] = Nil 110 111 // The eventsListModel and eventsJList are used to display the current 112 // collected events of types selected by the view menu 113 private val eventsListModel: DefaultListModel = new DefaultListModel() 114 private val eventsJList: JList = new JList(eventsListModel) 115 116 // The detailsJEditorPane displays the text details of a event. 117 private val detailsJEditorPane: JEditorPane = new JEditorPane("text/html", null) 118 119 private val progressBar: ColorBar = new ColorBar() 120 private val statusJPanel: StatusJPanel = new StatusJPanel() 121 private val rerunColorBox: ColorBar = new ColorBar() 122 private val runJButton: JButton = new JButton(Resources("Run")) 123 private val rerunJButton: JButton = new JButton(Resources("Rerun")) 124 125 private var testsCompletedCount: Int = 0 126 private var rerunTestsCompletedCount: Int = 0 127 128 private val graphicRunReporter: Reporter = new GraphicRunReporter 129 private val graphicRerunReporter: Reporter = new GraphicRerunReporter 130 131 private val stopper = new SimpleStopper 132 133 private val exitSemaphore = new Semaphore(1) 134 135 private var nextRunStamp = 1 136 137 initialize() 138 139 private def initialize() = { 140 141 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE) 142 143 val ambientURL: URL = classOf[Suite].getClassLoader().getResource("images/greendot.gif") 144 val ambientIcon: ImageIcon = new ImageIcon(ambientURL) 145 setIconImage(ambientIcon.getImage()) 146 147 setupMenus() 148 149 runJButton.setMnemonic(KeyEvent.VK_R) 150 runJButton.addActionListener( 151 new ActionListener() { 152 def actionPerformed(ae: ActionEvent) { 153 currentState = currentState.runButtonPressed(RunnerJFrame.this) 154 } 155 } 156 ) 157 158 val progressBarHolder: JPanel = new JPanel() 159 progressBarHolder.setLayout(new BorderLayout()) 160 progressBarHolder.setBorder(new BevelBorder(BevelBorder.LOWERED)) 161 progressBarHolder.add(progressBar, BorderLayout.CENTER) 162 val pBarRunBtnJPanel: JPanel = new JPanel() 163 164 pBarRunBtnJPanel.setLayout(new BorderLayout(5, 5)) 165 pBarRunBtnJPanel.add(progressBarHolder, BorderLayout.CENTER) 166 pBarRunBtnJPanel.add(runJButton, BorderLayout.EAST) 167 168 val progressJPanel: JPanel = new JPanel() 169 170 progressJPanel.setLayout(new GridLayout(2, 1)) 171 progressJPanel.add(statusJPanel) 172 progressJPanel.add(pBarRunBtnJPanel) 173 val eventsJLabel: JLabel = new JLabel(Resources("eventsLabel")) 174 175 val southHuggingEventsLabelJPanel: JPanel = new JPanel() 176 177 southHuggingEventsLabelJPanel.setLayout(new BorderLayout()) 178 southHuggingEventsLabelJPanel.add(eventsJLabel, BorderLayout.SOUTH) 179 southHuggingEventsLabelJPanel.setBorder(new EmptyBorder(0, 1, 0, 0)) 180 181 eventsJList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION) 182 eventsJList.setCellRenderer(new IconEmbellishedListCellRenderer()) 183 val eventsJScrollPane: JScrollPane = new JScrollPane(eventsJList) 184 185 val eventsJPanel: JPanel = new JPanel() 186 187 eventsJPanel.setLayout(new BorderLayout()) 188 eventsJPanel.add(southHuggingEventsLabelJPanel, BorderLayout.NORTH) 189 eventsJPanel.add(eventsJScrollPane, BorderLayout.CENTER) 190 val detailsJLabel: JLabel = new JLabel(Resources("detailsLabel")) 191 192 val southHuggingDetailsLabelJPanel: JPanel = new JPanel() 193 194 southHuggingDetailsLabelJPanel.setLayout(new BorderLayout()) 195 southHuggingDetailsLabelJPanel.add(detailsJLabel, BorderLayout.SOUTH) 196 southHuggingDetailsLabelJPanel.setBorder(new EmptyBorder(0, 1, 0, 0)) 197 198 rerunJButton.setMnemonic(KeyEvent.VK_E) 199 rerunJButton.setEnabled(false) 200 201 val rerunColorBoxHolder: JPanel = new JPanel() 202 203 rerunColorBoxHolder.setLayout(new BorderLayout()) 204 rerunColorBoxHolder.setBorder(new BevelBorder(BevelBorder.LOWERED)) 205 rerunColorBoxHolder.add(rerunColorBox, BorderLayout.CENTER) 206 val rerunJPanel: JPanel = new JPanel() 207 208 rerunJPanel.setLayout(new GridLayout(1, 2, 5, 5)) 209 rerunJPanel.add(rerunColorBoxHolder) 210 rerunJPanel.add(rerunJButton) 211 rerunJPanel.setBorder(new EmptyBorder(0, 0, 5, 0)) 212 val detailsNorthJPanel: JPanel = new JPanel() 213 214 detailsNorthJPanel.setLayout(new BorderLayout()) 215 detailsNorthJPanel.add(BorderLayout.WEST, southHuggingDetailsLabelJPanel) 216 detailsNorthJPanel.add(BorderLayout.EAST, rerunJPanel) 217 218 detailsJEditorPane.setEditable(false) 219 220 val detailsJScrollPane: JScrollPane = new JScrollPane(detailsJEditorPane) 221 222 val detailsJPanel: JPanel = new JPanel() 223 224 detailsJPanel.setLayout(new BorderLayout()) 225 detailsJPanel.add(detailsNorthJPanel, BorderLayout.NORTH) 226 detailsJPanel.add(detailsJScrollPane, BorderLayout.CENTER) 227 val eventsDetailsPanel: JPanel = new JPanel() 228 229 eventsDetailsPanel.setLayout(new GridLayout(2, 1, 5, 5)) 230 eventsDetailsPanel.add(eventsJPanel) 231 eventsDetailsPanel.add(detailsJPanel) 232 val reporterJPanel: JPanel = new JPanel() 233 234 reporterJPanel.setLayout(new BorderLayout(5, 5)) 235 reporterJPanel.add(progressJPanel, BorderLayout.NORTH) 236 reporterJPanel.add(eventsDetailsPanel, BorderLayout.CENTER) 237 eventsJList.addListSelectionListener( 238 new ListSelectionListener() { 239 def valueChanged(e: ListSelectionEvent) { 240 241 val holder: EventHolder = eventsJList.getSelectedValue().asInstanceOf[EventHolder] 242 243 if (holder == null) { 244 245 // This means nothing is currently selected 246 detailsJEditorPane.setText("") 247 currentState = currentState.listSelectionChanged(RunnerJFrame.this) 248 } 249 else { 250 251 val event: Event = holder.event 252 val isRerun: Boolean = holder.isRerun 253 254 val fontSize = eventsJList.getFont.getSize 255 256 val title = 257 if (isRerun) 258 Resources("RERUN_" + RunnerJFrame.getUpperCaseName(event)) 259 else 260 Resources(RunnerJFrame.getUpperCaseName(event)) 261 262 val isFailureEvent = 263 event match { 264 case _: TestFailed => true 265 case _: SuiteAborted => true 266 case _: RunAborted => true 267 case _ => false 268 } 269 270 val fileAndLineOption: Option[String] = 271 holder.throwable match { 272 case Some(throwable) => 273 throwable match { 274 case stackDepth: StackDepth => 275 stackDepth.failedCodeFileNameAndLineNumberString 276 case _ => None 277 } 278 case None => None 279 } 280 281 val throwableTitle = 282 holder.throwable match { 283 case Some(throwable) => Some(throwable.getClass.getName) 284 case None => None 285 } 286 287 // Any stack trace elements lower than a TestFailedException's failedCodeStackDepth 288 // will show up as gray in the displayed stack trace, because those are ScalaTest methods. 289 // The rest will show up as black. 290 val (grayStackTraceElements, blackStackTraceElements) = 291 holder.throwable match { 292 case Some(throwable) => 293 val stackTraceElements = throwable.getStackTrace.toList 294 throwable match { 295 case tfe: TestFailedException => 296 (stackTraceElements.take(tfe.failedCodeStackDepth), stackTraceElements.drop(tfe.failedCodeStackDepth)) 297 case _ => (List(), stackTraceElements) 298 } 299 case None => (List(), List()) 300 } 301 302 def getHTMLForStackTrace(stackTraceList: List[StackTraceElement]) = 303 stackTraceList.map((ste: StackTraceElement) => <span>{ ste.toString }</span><br />) 304 305 def getHTMLForCause(throwable: Throwable): scala.xml.NodeBuffer = { 306 val cause = throwable.getCause 307 if (cause != null) { 308 <table> 309 <tr valign="top"> 310 <td align="right"><span class="label">{ Resources("DetailsCause") + ":" }</span></td> 311 <td align="left">{ cause.getClass.getName }</td> 312 </tr> 313 <tr valign="top"> 314 <td align="right"><span class="label">{ Resources("DetailsMessage") + ":" }</span></td> 315 <td align="left"><span>{ if (cause.getMessage != null) cause.getMessage else Resources("None") }</span></td> 316 </tr> 317 </table> 318 <table> 319 <tr valign="top"> 320 <td align="left" colspan="2">{ getHTMLForStackTrace(cause.getStackTrace.toList) }</td> 321 </tr> 322 </table> &+ getHTMLForCause(cause) 323 } 324 else new scala.xml.NodeBuffer 325 } 326 327 val mainMessage = 328 holder.message match { 329 case Some(msg) => 330 val trimmed = msg.trim 331 if (trimmed.length > 0) Some(trimmed) else None 332 case _ => None 333 } 334 335 import EventHolder.suiteAndTestName 336 337 val name = 338 holder.event match { 339 case event: RunStarting => None 340 case event: RunStopped => None 341 case event: RunAborted => None 342 case event: RunCompleted => None 343 case event: InfoProvided => 344 event.nameInfo match { 345 case Some(NameInfo(suiteName, suiteClassName, testName)) => 346 testName match { 347 case Some(tn) => Some(suiteAndTestName(suiteName, tn)) 348 case None => Some(suiteName) 349 } 350 case None => None 351 } 352 case event: SuiteStarting => Some(event.suiteName) 353 case event: SuiteCompleted => Some(event.suiteName) 354 case event: SuiteAborted => Some(event.suiteName) 355 case event: TestStarting => Some(suiteAndTestName(event.suiteName, event.testName)) 356 case event: TestPending => Some(suiteAndTestName(event.suiteName, event.testName)) 357 case event: TestIgnored => Some(suiteAndTestName(event.suiteName, event.testName)) 358 case event: TestSucceeded => Some(suiteAndTestName(event.suiteName, event.testName)) 359 case event: TestFailed => Some(suiteAndTestName(event.suiteName, event.testName)) 360 } 361 362 val duration = 363 holder.event match { 364 case event: RunStarting => None 365 case event: RunStopped => event.duration 366 case event: RunAborted => event.duration 367 case event: RunCompleted => event.duration 368 case event: InfoProvided => None 369 case event: SuiteStarting => None 370 case event: SuiteCompleted => event.duration 371 case event: SuiteAborted => event.duration 372 case event: TestStarting => None 373 case event: TestPending => None 374 case event: TestIgnored => None 375 case event: TestSucceeded => event.duration 376 case event: TestFailed => event.duration 377 } 378 379 val propCheckArgs: List[Any] = 380 holder.throwable match { 381 case Some(ex: PropertyTestFailedException) => ex.args 382 case _ => List() 383 } 384 385 val propCheckLabels: List[String] = 386 holder.throwable match { 387 case Some(ex: PropertyTestFailedException) => ex.labels 388 case _ => List() 389 } 390 391 val detailsHTML = 392 <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> 393 <head> 394 <style type="text/css"> 395 body {{ font-family: sans-serif; font-size: { fontSize }pt; }} 396 .label {{ color: #444444; font-weight: bold; }} 397 .gray {{ color: black; }} 398 .dark {{ font-weight: bold; color: #111111; }} 399 </style> 400 </head> 401 <body> 402 <table> 403 <tr valign="top"><td align="right"><span class="label">{ Resources("DetailsEvent") + ":" }</span></td><td align="left"><span>{ title }</span></td></tr> 404 { 405 if (name.isDefined) { 406 <tr valign="top"><td align="right"><span class="label">{ Resources("DetailsName") + ":" }</span></td><td align="left">{ name.get }</td></tr> 407 } 408 else <!-- --> 409 } 410 { 411 if (mainMessage.isDefined) { 412 <tr valign="top"><td align="right"><span class="label">{ Resources("DetailsMessage") + ":" }</span></td><td align="left"> 413 { // Put <br>'s in for line returns at least, so property check failure messages look better 414 def lineSpans = for (line <- mainMessage.get.split('\n')) yield <span>{ line }<br/></span> 415 if (isFailureEvent) { 416 <span class="dark">{ lineSpans }</span> 417 } else { 418 <span>{ lineSpans }</span> 419 } 420 } 421 </td></tr> 422 } 423 else <!-- --> 424 } 425 { 426 fileAndLineOption match { 427 case Some(fileAndLine) => 428 <tr valign="top"><td align="right"><span class="label">{ Resources("LineNumber") + ":" }</span></td><td align="left"><span class="dark">{ "(" + fileAndLine + ")" }</span></td></tr> 429 case None => 430 } 431 } 432 { 433 if (!propCheckArgs.isEmpty) { 434 for ((propCheckArg, argIndex) <- propCheckArgs.zipWithIndex) yield 435 <tr valign="top"><td align="right"><span class="label">{ Resources("argN", argIndex.toString) + ":" }</span></td><td align="left"><span class="dark">{ propCheckArg.toString }</span></td></tr> 436 } 437 else new scala.xml.NodeBuffer 438 } 439 { 440 if (!propCheckLabels.isEmpty) { 441 val labelOrLabels = if (propCheckLabels.length > 1) Resources("DetailsLabels") else Resources("DetailsLabel") 442 val labelHTML = for (propCheckLabel <- propCheckLabels) yield { 443 <span class="dark">{ propCheckLabel }</span><br></br> 444 } 445 <tr valign="top"><td align="right"><span class="label">{ labelOrLabels + ":" }</span></td><td align="left"><span class="dark">{ labelHTML }</span></td></tr> 446 } 447 else new scala.xml.NodeBuffer 448 } 449 { 450 holder.summary match { 451 case Some(summary) => 452 453 <tr valign="top"><td align="right"><span class="label">{ Resources("DetailsSummary") + ":" }</span></td><td align="left"><strong>{ Resources("totalNumberOfTestsRun", summary.testsCompletedCount.toString) }</strong></td></tr> 454 <tr valign="top"><td align="right"><span class="label"> </span></td><td align="left"><strong>{ Resources("suiteSummary", summary.suitesCompletedCount.toString, summary.suitesAbortedCount.toString) }</strong></td></tr> 455 <tr valign="top"><td align="right"><span class="label"> </span></td><td align="left"><strong> 456 { 457 Resources( 458 "testSummary", 459 summary.testsSucceededCount.toString, 460 summary.testsFailedCount.toString, 461 summary.testsIgnoredCount.toString, 462 summary.testsPendingCount.toString 463 ) 464 } 465 </strong></td></tr> 466 467 case None => new scala.xml.NodeBuffer 468 } 469 } 470 { 471 duration match { 472 case Some(milliseconds) => 473 <tr valign="top"><td align="right"><span class="label">{ Resources("DetailsDuration") + ":" }</span></td><td align="left">{ PrintReporter.makeDurationString(milliseconds) }</td></tr> 474 case None => new scala.xml.NodeBuffer 475 } 476 } 477 <tr valign="top"><td align="right"><span class="label">{ Resources("DetailsDate") + ":" }</span></td><td align="left">{ new java.util.Date(event.timeStamp) }</td></tr> 478 <tr valign="top"><td align="right"><span class="label">{ Resources("DetailsThread") + ":" }</span></td><td align="left">{ event.threadName }</td></tr> 479 { 480 throwableTitle match { 481 case Some(title) => 482 <tr valign="top"><td align="right"><span class="label">{ Resources("DetailsThrowable") + ":" }</span></td><td align="left">{ title }</td></tr> 483 case None => new scala.xml.NodeBuffer 484 } 485 } 486 </table> 487 <table> 488 <tr valign="top"><td align="left" colspan="2"> 489 { grayStackTraceElements.map((ste: StackTraceElement) => <span class="gray">{ ste.toString }</span><br />) } 490 { blackStackTraceElements.map((ste: StackTraceElement) => <span>{ ste.toString }</span><br />) } 491 </td></tr> 492 </table> 493 { 494 holder.throwable match { 495 case Some(t) => getHTMLForCause(t) 496 case None => 497 } 498 } 499 </body> 500 </html> 501 502 detailsJEditorPane.setText(detailsHTML.toString) 503 detailsJEditorPane.setCaretPosition(0) 504 currentState = currentState.listSelectionChanged(RunnerJFrame.this) 505 } 506 } 507 } 508 ) 509 510 rerunJButton.addActionListener( 511 new ActionListener() { 512 def actionPerformed(ae: ActionEvent) { 513 currentState = currentState.rerunButtonPressed(RunnerJFrame.this) 514 } 515 } 516 ) 517 val mainJPanel: JPanel = new JPanel() 518 519 mainJPanel.setLayout(new BorderLayout(5, 5)) 520 mainJPanel.setBorder(new EmptyBorder(5, 5, 5, 5)) 521 mainJPanel.add(reporterJPanel, BorderLayout.CENTER) 522 val pane: Container = getContentPane() 523 524 pane.setLayout(new GridLayout(1, 1)) 525 pane.add(mainJPanel) 526 527 // Set the size of both buttons to the max of the localized labels Run, Rerun, and Stop 528 val runButtonSize: Dimension = runJButton.getPreferredSize() 529 val rerunButtonSize: Dimension = rerunJButton.getPreferredSize() 530 // Create a throw away button to get the size of Stop 531 val stopButtonSize: Dimension = new JButton(Resources("Stop")).getPreferredSize() 532 533 val preferredSize = new Dimension( 534 runButtonSize.width.max(rerunButtonSize.width.max(stopButtonSize.width)), 535 runButtonSize.height.max(rerunButtonSize.height.max(stopButtonSize.height)) 536 ) 537 538 runJButton.setPreferredSize(preferredSize) 539 rerunJButton.setPreferredSize(preferredSize) 540 541 exitSemaphore.acquire() 542 addWindowListener( 543 new WindowAdapter { 544 override def windowClosed(e: WindowEvent) { exitSemaphore.release() } 545 } 546 ) 547 548 pack() 549 550 val dim: Dimension = getSize() 551 dim.height = dim.height / 5 + dim.height 552 dim.width = dim.height / 3 * 4 553 setSize(dim) 554 } 555 556 private[scalatest] def blockUntilWindowClosed() { 557 exitSemaphore.acquire() 558 } 559 560 // This initialize method idiom is a way to get rid of a var 561 // when you have verbose initialization. 562 private def initializeAboutBox() = { 563 val title2: String = Resources("AboutBoxTitle") 564 new AboutJDialog(RunnerJFrame.this, title2) 565 } 566 567 private def setupMenus() { 568 569 val menuBar: JMenuBar = new JMenuBar() 570 571 // The ScalaTest menu 572 val scalaTestMenu: JMenu = new JMenu(Resources("ScalaTestMenu")) 573 scalaTestMenu.setMnemonic(KeyEvent.VK_S) 574 menuBar.add(scalaTestMenu) 575 576 // The ScalaTest.About menu item 577 val aboutItem: JMenuItem = new JMenuItem(Resources("About"), KeyEvent.VK_A) 578 scalaTestMenu.add(aboutItem) 579 aboutItem.addActionListener( 580 new ActionListener() { 581 def actionPerformed(ae: ActionEvent) { 582 val location: Point = getLocation() 583 location.x += 20 584 location.y += 6 585 aboutBox.setLocation(location) 586 aboutBox.setVisible(true) 587 } 588 } 589 ) 590 591 scalaTestMenu.addSeparator() 592 593 // The ScalaTest.Exit menu item 594 val exitItem: JMenuItem = new JMenuItem(Resources("Exit"), KeyEvent.VK_X) 595 scalaTestMenu.add(exitItem) 596 exitItem.addActionListener( 597 new ActionListener() { 598 def actionPerformed(ae: ActionEvent) { 599 dispose() 600 // Only exit if started from main(), not run(). If starting from run(), 601 // we want to return a pass/fail status from run(). Actually, if we 602 // have a passFailReporter, then that means we want to indicate status, 603 // so that's why it is used here to determine whether or not to exit. 604 passFailReporter match { 605 case Some(_) => 606 case None => System.exit(0) 607 } 608 } 609 } 610 ) 611 612 // The View menu 613 val viewMenu = new JMenu(Resources("ViewMenu")) 614 viewMenu.setMnemonic(KeyEvent.VK_V) 615 616 // the View.Runs and Failures menu item 617 val runsFailuresItem: JMenuItem = new JMenuItem(Resources("runsFailures"), KeyEvent.VK_F) 618 runsFailuresItem.setAccelerator(KeyStroke.getKeyStroke("control F")) 619 viewMenu.add(runsFailuresItem) 620 runsFailuresItem.addActionListener( 621 new ActionListener() { 622 def actionPerformed(ae: ActionEvent) { 623 viewOptions = runsAndFailures intersect eventTypesToCollect 624 updateViewOptionsAndEventsList() 625 } 626 } 627 ) 628 629 val allEventsItem: JMenuItem = new JMenuItem(Resources("allEvents"), KeyEvent.VK_A) 630 allEventsItem.setAccelerator(KeyStroke.getKeyStroke("control L")) 631 viewMenu.add(allEventsItem) 632 allEventsItem.addActionListener( 633 new ActionListener() { 634 def actionPerformed(ae: ActionEvent) { 635 viewOptions = eventTypesToCollect 636 updateViewOptionsAndEventsList() 637 } 638 } 639 ) 640 641 viewMenu.addSeparator() 642 643 // Add the checkboxes in the correct order 644 viewMenu.add(optionsMap(PresentRunStarting)) 645 viewMenu.add(optionsMap(PresentTestStarting)) 646 viewMenu.add(optionsMap(PresentTestSucceeded)) 647 viewMenu.add(optionsMap(PresentTestFailed)) 648 viewMenu.add(optionsMap(PresentTestIgnored)) 649 viewMenu.add(optionsMap(PresentSuiteStarting)) 650 viewMenu.add(optionsMap(PresentSuiteCompleted)) 651 viewMenu.add(optionsMap(PresentSuiteAborted)) 652 viewMenu.add(optionsMap(PresentInfoProvided)) 653 viewMenu.add(optionsMap(PresentRunStopped)) 654 viewMenu.add(optionsMap(PresentRunCompleted)) 655 viewMenu.add(optionsMap(PresentRunAborted)) 656 657 menuBar.add(viewMenu) 658 setJMenuBar(menuBar) 659 } 660 661 private def initializeOptionsMap(): Map[EventToPresent, JCheckBoxMenuItem] = { 662 663 // TODO: Why am I using an immutable map here. Better a val with a mutable map I'd think. 664 var map: Map[EventToPresent, JCheckBoxMenuItem] = Map() 665 666 for (option <- EventToPresent.allEventsToPresent) { 667 668 val rawOptionName = RunnerJFrame.getUpperCaseName(option) 669 val menuItemText: String = Resources("MENU_PRESENT_" + rawOptionName) 670 671 val itemAction: AbstractAction = 672 new AbstractAction(menuItemText) { 673 def actionPerformed(ae: ActionEvent) { 674 675 val checkBox: JCheckBoxMenuItem = ae.getSource().asInstanceOf[JCheckBoxMenuItem] 676 val option = getValue("option").asInstanceOf[EventToPresent] 677 678 if (viewOptions.contains(option)) 679 viewOptions = viewOptions - option 680 else 681 viewOptions = viewOptions + option 682 683 val checked: Boolean = viewOptions.contains(option) 684 checkBox.setState(checked) 685 686 // Now, since the configuration changed, we need to update the 687 // list display appropriately: 688 refreshEventsJList() 689 } 690 } 691 692 val checked: Boolean = viewOptions.contains(option) 693 val checkBox: JCheckBoxMenuItem = new JCheckBoxMenuItem(itemAction) 694 695 checkBox.setState(checked) 696 697 // Put the option into the checkbox's AbstractAction, so it can be 698 // taken out when the checkbox is checked or unchecked. 699 itemAction.putValue("option", option) 700 701 map = map + (option -> checkBox) 702 } 703 704 map 705 } 706 707 def requestStop() { 708 stopper.requestStop() 709 } 710 711 private def updateViewOptionsAndEventsList() { 712 713 for (option <- EventToPresent.allEventsToPresent) { 714 715 val box: JCheckBoxMenuItem = optionsMap(option) 716 717 if (eventTypesToCollect.contains(option)) { 718 box.setEnabled(true) 719 if (viewOptions.contains(option)) 720 box.setSelected(true) 721 else 722 box.setSelected(false) 723 } 724 else { 725 box.setSelected(false) 726 box.setEnabled(false) 727 } 728 } 729 730 // Now, since the configuration changed, we need to update the 731 // list display appropriately: 732 refreshEventsJList() 733 } 734 735 private def reorderCollectedEvents() { 736 collectedEvents = collectedEvents.sortWith((a, b) => a.event.ordinal > b.event.ordinal) 737 } 738 739 private def refreshEventsJList() { 740 741 val formerlySelectedItem: EventHolder = eventsJList.getSelectedValue().asInstanceOf[EventHolder] 742 743 // clear the list of events and the detail area 744 eventsListModel.clear() 745 detailsJEditorPane.setText("") 746 747 for (holder <- collectedEvents.reverse; if viewOptions.contains(eventToEventToPresent(holder.event))) { 748 val shouldAddElement = holder.event.formatter match { 749 case Some(MotionToSuppress) => false 750 case _ => true 751 } 752 if (shouldAddElement) eventsListModel.addElement(holder) 753 } 754 755 // Isn't there a risk that the formerly selected item will no longer exist in the list? 756 // Does this result in an exception? Of course the stupid JavaDoc API docs is silent on this. 757 // TODO: try this and fix if need be 758 eventsJList.setSelectedValue(formerlySelectedItem, true) 759 } 760 761 private def registerEvent(event: Event): EventHolder = { 762 registerRunOrRerunEvent(event, false) 763 } 764 765 private def registerRerunEvent(event: Event): EventHolder = { 766 registerRunOrRerunEvent(event, true) 767 } 768 769 private def registerRunOrRerunEvent(event: Event, isRerun: Boolean): EventHolder = { 770 771 val (message, throwable) = 772 event match { 773 case e: TestFailed => (Some(e.message), e.throwable) 774 case e: SuiteAborted => (Some(e.message), e.throwable) 775 case e: RunAborted => (Some(e.message), e.throwable) 776 case e: InfoProvided => (Some(e.message), e.throwable) 777 case _ => (None, None) 778 } 779 780 val rerunner = 781 event match { 782 case e: TestStarting => e.rerunner 783 case e: TestSucceeded => e.rerunner 784 case e: TestFailed => e.rerunner 785 case e: SuiteStarting => e.rerunner 786 case e: SuiteCompleted => e.rerunner 787 case e: SuiteAborted => e.rerunner 788 case _ => None 789 } 790 791 val summary = 792 event match { 793 case e: RunCompleted => e.summary 794 case e: RunAborted => e.summary 795 case e: RunStopped => e.summary 796 case _ => None 797 } 798 799 val eventHolder: EventHolder = new EventHolder(event, message, throwable, rerunner, summary, isRerun) 800 801 if (eventTypesToCollect.contains(eventToEventToPresent(event))) { 802 collectedEvents = eventHolder :: collectedEvents 803 if (viewOptions.contains(eventToEventToPresent(event))) { 804 val shouldAddElement = event.formatter match { 805 case Some(MotionToSuppress) => false 806 case _ => true 807 } 808 if (shouldAddElement) eventsListModel.addElement(eventHolder) 809 } 810 } 811 812 eventHolder 813 } 814 815 private class GraphicRunReporter extends Reporter { 816 817 override def apply(event: Event) { 818 event match { 819 820 case RunStarting(ordinal, testCount, configMap, formatter, payload, threadName, timeStamp) => 821 822 // Create the Event outside of the event handler thread, because otherwise 823 // the event handler thread shows up as the originating thread of this event, 824 // and that looks bad and is wrong to boot. 825 val eventHolder: EventHolder = new EventHolder(event, None, None, None) 826 827 usingEventDispatchThread { 828 testsCompletedCount = 0 829 progressBar.setMax(testCount) 830 progressBar.setValue(0) 831 progressBar.setGreen() 832 833 statusJPanel.reset() 834 statusJPanel.setTestsExpected(testCount) 835 836 // This should already have been cleared by prepUIForStarting, but 837 // doing it again here for the heck of it. 838 collectedEvents = eventHolder :: Nil 839 eventsListModel.clear() 840 841 detailsJEditorPane.setText("") 842 843 if (viewOptions.contains(PresentRunStarting)) 844 eventsListModel.addElement(eventHolder) 845 } 846 847 case RunCompleted(ordinal, duration, summary, formatter, payload, threadName, timeStamp) => 848 849 // Create the Report outside of the event handler thread, because otherwise 850 // the event handler thread shows up as the originating thread of this event, 851 // and that looks bad and is wrong to boot. 852 // Reordering this outside the event handler thread so that the GUI won't be frozen 853 // during a long sort. The RunCompleted event isn't yet in the list when the sort happens, so 854 // it will just be added at the end. 855 reorderCollectedEvents() 856 usingEventDispatchThread { 857 registerEvent(event) 858 refreshEventsJList() 859 } 860 861 case RunAborted(ordinal, message, throwable, duration, summary, formatter, payload, threadName, timeStamp) => 862 863 usingEventDispatchThread { 864 progressBar.setRed() 865 registerEvent(event) 866 // Must do this here, not in RunningState.runFinished, because the runFinished 867 // invocation can happen before this runCompleted invocation, which means that 868 // the first error in the run may not be in the JList model yet. So must wait until 869 // a run completes. I was doing it in runCompleted, which works, but for long runs 870 // you must wait a long time for that thing to be selected. Nice if it gets selected 871 // right away. 872 selectFirstFailureIfExistsAndNothingElseAlreadySelected() 873 } 874 875 case RunStopped(ordinal, duration, summary, formatter, payload, threadName, timeStamp) => 876 877 // Create the Report outside of the event handler thread, because otherwise 878 // the event handler thread shows up as the originating thread of this event, 879 // and that looks bad and is actually wrong. 880 usingEventDispatchThread { 881 registerEvent(event) 882 } 883 884 case SuiteStarting(ordinal, suiteName, suiteClassName, formatter, rerunner, payload, threadName, timeStamp) => 885 886 usingEventDispatchThread { 887 registerEvent(event) 888 } 889 890 case SuiteCompleted(ordinal, suiteName, suiteClassName, duration, formatter, rerunner, payload, threadName, timeStamp) => 891 892 usingEventDispatchThread { 893 registerEvent(event) 894 } 895 896 case SuiteAborted(ordinal, message, suiteName, suiteClassName, throwable, duration, formatter, rerunner, payload, threadName, timeStamp) => 897 898 usingEventDispatchThread { 899 progressBar.setRed() 900 registerEvent(event) 901 // Must do this here, not in RunningState.runFinished, because the runFinished 902 // invocation can happen before this runCompleted invocation, which means that 903 // the first error in the run may not be in the JList model yet. So must wait until 904 // a run completes. I was doing it in runCompleted, which works, but for long runs 905 // you must wait a long time for that thing to be selected. Nice if it gets selected 906 // right away. 907 selectFirstFailureIfExistsAndNothingElseAlreadySelected() 908 } 909 910 case TestStarting(ordinal, suiteName, suiteClassName, testName, formatter, rerunner, payload, threadName, timeStamp) => 911 912 usingEventDispatchThread { 913 registerEvent(event) 914 } 915 916 case TestIgnored(ordinal, suiteName, suiteClassName, testName, formatter, payload, threadName, timeStamp) => 917 918 usingEventDispatchThread { 919 registerEvent(event) 920 } 921 922 case TestPending(ordinal, suiteName, suiteClassName, testName, formatter, payload, threadName, timeStamp) => 923 924 usingEventDispatchThread { 925 testsCompletedCount += 1 926 statusJPanel.setTestsRun(testsCompletedCount, true) 927 progressBar.setValue(testsCompletedCount) 928 registerEvent(event) 929 } 930 931 case TestSucceeded(ordinal, suiteName, suiteClassName, testName, duration, formatter, rerunner, payload, threadName, timeStamp) => 932 933 usingEventDispatchThread { 934 testsCompletedCount += 1 935 statusJPanel.setTestsRun(testsCompletedCount, true) 936 progressBar.setValue(testsCompletedCount) 937 registerEvent(event) 938 } 939 940 case TestFailed(ordinal, message, suiteName, suiteClassName, testName, throwable, duration, formatter, rerunner, payload, threadName, timeStamp) => 941 942 usingEventDispatchThread { 943 testsCompletedCount += 1 944 // Passing in false here increments the test failed count 945 // in the statusJPanel, which updates the counter on the GUI 946 statusJPanel.setTestsRun(testsCompletedCount, false) 947 progressBar.setValue(testsCompletedCount) 948 progressBar.setRed() 949 registerEvent(event) 950 // Must do this here, not in RunningState.runFinished, because the runFinished 951 // invocation can happen before this runCompleted invocation, which means that 952 // the first error in the run may not be in the JList model yet. So must wait until 953 // a run completes. I was doing it in runCompleted, which works, but for long runs 954 // you must wait a long time for that thing to be selected. Nice if it gets selected 955 // right away. 956 selectFirstFailureIfExistsAndNothingElseAlreadySelected() 957 } 958 959 case InfoProvided(ordinal, message, nameInfo, aboutAPendingTest, throwable, formatter, payload, threadName, timeStamp) => 960 961 usingEventDispatchThread { 962 registerEvent(event) 963 } 964 } 965 } 966 } 967 968 // Invoked when a test is done. This is used to turn the Run button back on after 969 // a Stop request has disabled it. When this method is invoked by the runner, it 970 // means that the run has finished, so that it is OK to enable Run again. 971 override def done() { 972 usingEventDispatchThread { 973 currentState = currentState.runFinished(RunnerJFrame.this) 974 } 975 } 976 977 // Called from the main thread initially, thereafter from the event handler thread 978 override def runFromGUI() { 979 (new RunnerThread).start() 980 } 981 982 // Must be called from event handler thread 983 override def rerunFromGUI(rerunner: Rerunner) { 984 (new RerunnerThread(rerunner)).start() 985 } 986 987 // This must be called by the event handler thread 988 def prepUIForRunning() { 989 val stopText: String = Resources("Stop") 990 val rerunText: String = Resources("Rerun") 991 runJButton.setText(stopText) 992 rerunJButton.setText(rerunText) 993 runJButton.setEnabled(true) 994 rerunJButton.setEnabled(false) 995 rerunColorBox.setGray() 996 progressBar.setGray() 997 statusJPanel.reset() 998 statusJPanel.setTestsExpected(0) 999 collectedEvents = Nil 1000 eventsListModel.clear() 1001 detailsJEditorPane.setText("") 1002 } 1003 1004 // This must be called by the event handler thread 1005 def prepUIWhileRunning() { 1006 val stopText: String = Resources("Stop") 1007 val rerunText: String = Resources("Rerun") 1008 runJButton.setText(stopText) 1009 rerunJButton.setText(rerunText) 1010 runJButton.setEnabled(true) 1011 rerunJButton.setEnabled(false) 1012 rerunColorBox.setGray() 1013 } 1014 1015 // This must be called by the event handler thread 1016 def prepUIForRerunning() { 1017 val runText: String = Resources("Run") 1018 val stopText: String = Resources("Stop") 1019 runJButton.setText(runText) 1020 rerunJButton.setText(stopText) 1021 runJButton.setEnabled(false) 1022 rerunJButton.setEnabled(true) 1023 rerunColorBox.setGray() 1024 1025 // Clear the selection, so it can scroll to an error 1026 eventsJList.clearSelection() 1027 } 1028 1029 // This must be called by the event handler thread 1030 def prepUIWhileRerunning() { 1031 val runText: String = Resources("Run") 1032 val stopText: String = Resources("Stop") 1033 runJButton.setText(runText) 1034 rerunJButton.setText(stopText) 1035 runJButton.setEnabled(false) 1036 rerunJButton.setEnabled(true) 1037 } 1038 1039 // This must be called by the event handler thread 1040 def prepUIForReady() { 1041 val runText: String = Resources("Run") 1042 val rerunText: String = Resources("Rerun") 1043 runJButton.setText(runText) 1044 rerunJButton.setText(rerunText) 1045 runJButton.setEnabled(true) 1046 val holder: EventHolder = eventsJList.getSelectedValue.asInstanceOf[EventHolder] 1047 rerunJButton.setEnabled(holder != null && holder.rerunner.isDefined) 1048 } 1049 1050 // This must be called by the event handler thread 1051 def prepUIForStopping() { 1052 val stopText: String = Resources("Stop") 1053 val rerunText: String = Resources("Rerun") 1054 runJButton.setText(stopText) 1055 rerunJButton.setText(rerunText) 1056 runJButton.setEnabled(false) 1057 rerunJButton.setEnabled(false) 1058 } 1059 1060 // This must be called by the event handler thread 1061 def prepUIForReStopping() { 1062 val runText: String = Resources("Run") 1063 val stopText: String = Resources("Stop") 1064 runJButton.setText(runText) 1065 rerunJButton.setText(stopText) 1066 runJButton.setEnabled(false) 1067 rerunJButton.setEnabled(false) 1068 } 1069 1070 private def getModelAsList: List[EventHolder] = { 1071 val model = eventsJList.getModel 1072 val listBuf = new scala.collection.mutable.ListBuffer[EventHolder] 1073 for (i <- 0 until model.getSize) { 1074 listBuf += model.getElementAt(i).asInstanceOf[EventHolder] 1075 } 1076 listBuf.toList 1077 } 1078 1079 private def isFailureEvent(eventHolder: EventHolder) = 1080 eventHolder.event match { 1081 case _: TestFailed => true 1082 case _: SuiteAborted => true 1083 case _: RunAborted => true 1084 case _ => false 1085 } 1086 1087 // This must be called by the event handler thread 1088 /* 1089 After a rerun, there will usually always be a selected item, 1090 which is what was selected to rerun. If the rerun resulted in 1091 an error, it would be nice to select that first error and scroll down. 1092 So this will do that, which means it doesn't care if something is 1093 already selected. It will always select the first error in the 1094 last rerun if one exists. This is called as errors come in during 1095 a rerun. The error's eventHolder is passed. It will be selected only 1096 if it is the first error in the last rerun. Any other time this method will 1097 do nothing. The reason is that reruns can take a while, and the user may be 1098 selecting and exploring the results as it runs. So I don't want to keep forcing 1099 a different selection. Only the first time an error comes in in a rerun will it happen. 1100 (During a run, the first error will be selected only if there is no other selection. But 1101 here it happens even if something else is selected, because during a rerun normally the 1102 thing you wanted to rerun will already be selected.) 1103 */ 1104 private def selectFirstErrorInLastRerunIfThisIsThatError(candidateEventHolder: EventHolder) { 1105 1106 // First get the model into a List 1107 val modelList = getModelAsList 1108 1109 if (modelList.exists(_.isRerun)) { 1110 val listOfEventsForLastRerunExcludingRunStarting = 1111 modelList.reverse.takeWhile(eventHolder => eventHolder.isRerun && !eventHolder.event.isInstanceOf[RunStarting]) 1112 val firstTestFailedEventInLastRerun = 1113 listOfEventsForLastRerunExcludingRunStarting.reverse.find(isFailureEvent(_)) 1114 firstTestFailedEventInLastRerun match { 1115 case Some(eventHolder) => 1116 if (eventHolder == candidateEventHolder) // Only select it if the one passed is the first one 1117 eventsJList.setSelectedValue(eventHolder, true) 1118 case None => // do nothing if no failure events in last rerun 1119 } 1120 } 1121 } 1122 1123 private def scrollTheRerunStartingEventToTheTopOfVisibleEvents() { 1124 1125 def indexOfRunStartingEventForLastRerunOption: Option[Int] = { 1126 var i = eventsListModel.getSize - 1 1127 var found = false 1128 while (i >= 0 && !found) { 1129 val holder = eventsListModel.getElementAt(i).asInstanceOf[EventHolder] 1130 if (holder.event.isInstanceOf[RunStarting]) { 1131 found = true 1132 } 1133 if (!found) i -= 1 1134 } 1135 if (found) Some(i) else None 1136 } 1137 1138 val selectedEventHandler = eventsJList.getSelectedValue.asInstanceOf[EventHolder] 1139 1140 if (selectedEventHandler == null || selectedEventHandler.event.isInstanceOf[RunStarting]) { // only scroll if there's no selection, which means no error happened 1141 1142 val firstVisibleIndex = eventsJList.getFirstVisibleIndex 1143 val lastVisibleIndex = eventsJList.getLastVisibleIndex 1144 1145 if (lastVisibleIndex > firstVisibleIndex) { // should always be true, but this is better than an assert because things will keep going 1146 1147 val numCellsVisible = lastVisibleIndex - firstVisibleIndex 1148 1149 val indexOfLastEvent = eventsListModel.getSize - 1 1150 1151 indexOfRunStartingEventForLastRerunOption match { 1152 case Some(indexOfRunStartingEventForLastRerun) => 1153 1154 val indexToEnsureIsVisible = 1155 if (indexOfRunStartingEventForLastRerun + numCellsVisible < indexOfLastEvent) 1156 indexOfRunStartingEventForLastRerun + numCellsVisible 1157 else 1158 indexOfLastEvent 1159 1160 eventsJList.ensureIndexIsVisible(indexToEnsureIsVisible) 1161 1162 // Select one event after the rerun starting event, if it is a test starting, test succeeded, or suite starting event, 1163 // because this should be the one they requested was rerun. So that's the most intuitive one to select 1164 // after a run if there was no error. (Test succeeded is possible because Spec's will send MotionToSupress formatters that 1165 // say not to display test starting events.) 1166 val indexOfSecondEventInRerun = indexOfRunStartingEventForLastRerun + 1 1167 if (indexOfSecondEventInRerun <= indexOfLastEvent) { // Should always be true, but an if is better than an assert 1168 1169 val firstEventAfterRerunStarting = eventsListModel.getElementAt(indexOfSecondEventInRerun).asInstanceOf[EventHolder] 1170 if (firstEventAfterRerunStarting.event.isInstanceOf[TestStarting] || 1171 firstEventAfterRerunStarting.event.isInstanceOf[SuiteStarting] || 1172 firstEventAfterRerunStarting.event.isInstanceOf[TestSucceeded]) { 1173 eventsJList.setSelectedIndex(indexOfSecondEventInRerun) 1174 } 1175 // If they have display only Runs and Failures selected, it won't show successful tests. In that case 1176 // just select the run starting event. 1177 else eventsJList.setSelectedIndex(indexOfRunStartingEventForLastRerun) 1178 } 1179 case None => 1180 } 1181 } 1182 } 1183 } 1184 1185 // This must be called by the event handler thread 1186 private def selectFirstFailureIfExistsAndNothingElseAlreadySelected() { 1187 1188 val holder: EventHolder = eventsJList.getSelectedValue.asInstanceOf[EventHolder] 1189 1190 if (holder == null) { // Only do this if something isn't already selected 1191 1192 // First get the model into a List 1193 val modelList = getModelAsList 1194 1195 val firstFailureEvent = modelList.find(isFailureEvent(_)) 1196 firstFailureEvent match { 1197 case Some(eventHolder) => eventsJList.setSelectedValue(eventHolder, true) 1198 case None => // do nothing if no failure events in the run 1199 } 1200 } 1201 } 1202 1203 def getSelectedRerunner(): Option[Rerunner] = { 1204 val holder: EventHolder = eventsJList.getSelectedValue().asInstanceOf[EventHolder] 1205 if (holder == null) 1206 None 1207 else 1208 holder.rerunner 1209 } 1210 1211 private class GraphicRerunReporter extends Reporter { 1212 1213 // This is written by the event handler thread to avoid having the event handler thread spend time 1214 // determining if an error has previously occurred by looking through the events. This way if a 1215 // rerun has a lot of errors, you don't hang up the GUI giving the event handler thread too much 1216 // work to do. 1217 var anErrorHasOccurredAlready = false 1218 1219 def apply(event: Event) { 1220 1221 event match { 1222 case RunStarting(ordinal, testCount, configMap, formatter, payload, threadName, timeStamp) => 1223 1224 // Create the Report outside of the event handler thread, because otherwise 1225 // the event handler thread shows up as the originating thread of this event, 1226 // and that looks bad and is actually wrong. 1227 1228 usingEventDispatchThread { 1229 rerunTestsCompletedCount = 0 1230 rerunColorBox.setMax(testCount) 1231 rerunColorBox.setValue(0) 1232 rerunColorBox.setGreen() 1233 1234 registerRerunEvent(event) 1235 anErrorHasOccurredAlready = false; 1236 } 1237 1238 case RunCompleted(ordinal, duration, summary, formatter, payload, threadName, timeStamp) => 1239 1240 // Create the Report outside of the event handler thread, because otherwise 1241 // the event handler thread shows up as the originating thread of this event, 1242 // and that looks bad and is actually wrong. 1243 1244 usingEventDispatchThread { 1245 registerRerunEvent(event) 1246 scrollTheRerunStartingEventToTheTopOfVisibleEvents() 1247 } 1248 1249 case RunAborted(ordinal, message, throwable, duration, summary, formatter, payload, threadName, timeStamp) => 1250 1251 usingEventDispatchThread { 1252 rerunColorBox.setRed() 1253 val eventHolder = registerRerunEvent(event) 1254 if (!anErrorHasOccurredAlready) { 1255 selectFirstErrorInLastRerunIfThisIsThatError(eventHolder) 1256 anErrorHasOccurredAlready = true 1257 } 1258 } 1259 1260 case RunStopped(ordinal, duration, summary, formatter, payload, threadName, timeStamp) => 1261 1262 // Create the Report outside of the event handler thread, because otherwise 1263 // the event handler thread shows up as the originating thread of this event, 1264 // and that looks bad and is actually wrong. 1265 usingEventDispatchThread { 1266 registerRerunEvent(event) 1267 scrollTheRerunStartingEventToTheTopOfVisibleEvents() 1268 } 1269 1270 case SuiteStarting(ordinal, suiteName, suiteClassName, formatter, rerunner, payload, threadName, timeStamp) => 1271 1272 usingEventDispatchThread { 1273 registerRerunEvent(event) 1274 } 1275 1276 case SuiteCompleted(ordinal, suiteName, suiteClassName, duration, formatter, rerunner, payload, threadName, timeStamp) => 1277 1278 usingEventDispatchThread { 1279 registerRerunEvent(event) 1280 } 1281 1282 case SuiteAborted(ordinal, message, suiteName, suiteClassName, throwable, duration, formatter, rerunner, payload, threadName, timeStamp) => 1283 1284 usingEventDispatchThread { 1285 rerunColorBox.setRed() 1286 val eventHolder = registerRerunEvent(event) 1287 if (!anErrorHasOccurredAlready) { 1288 selectFirstErrorInLastRerunIfThisIsThatError(eventHolder) 1289 anErrorHasOccurredAlready = true 1290 } 1291 } 1292 1293 case TestStarting(ordinal, suiteName, suiteClassName, testName, formatter, rerunner, payload, threadName, timeStamp) => 1294 1295 usingEventDispatchThread { 1296 registerRerunEvent(event) 1297 } 1298 1299 case TestIgnored(ordinal, suiteName, suiteClassName, testName, formatter, payload, threadName, timeStamp) => 1300 1301 usingEventDispatchThread { 1302 rerunColorBox.setValue(rerunTestsCompletedCount) 1303 registerRerunEvent(event) 1304 } 1305 1306 case TestPending(ordinal, suiteName, suiteClassName, testName, formatter, payload, threadName, timeStamp) => 1307 1308 usingEventDispatchThread { 1309 rerunColorBox.setValue(rerunTestsCompletedCount) 1310 registerRerunEvent(event) 1311 } 1312 1313 case TestSucceeded(ordinal, suiteName, suiteClassName, testName, duration, formatter, rerunner, payload, threadName, timeStamp) => 1314 1315 usingEventDispatchThread { 1316 rerunTestsCompletedCount += 1 1317 rerunColorBox.setValue(rerunTestsCompletedCount) 1318 registerRerunEvent(event) 1319 } 1320 1321 case TestFailed(ordinal, message, suiteName, suiteClassName, testName, throwable, duration, formatter, rerunner, payload, threadName, timeStamp) => 1322 1323 usingEventDispatchThread { 1324 rerunTestsCompletedCount += 1 1325 rerunColorBox.setValue(rerunTestsCompletedCount) 1326 rerunColorBox.setRed() 1327 val eventHolder = registerRerunEvent(event) 1328 if (!anErrorHasOccurredAlready) { 1329 selectFirstErrorInLastRerunIfThisIsThatError(eventHolder) 1330 anErrorHasOccurredAlready = true 1331 } 1332 } 1333 1334 case InfoProvided(ordinal, message, nameInfo, aboutAPendingTest, throwable, formatter, payload, threadName, timeStamp) => 1335 1336 usingEventDispatchThread { 1337 registerRerunEvent(event) 1338 } 1339 } 1340 } 1341 } 1342 1343 // Invoked by ReadyState if can't run when the Run or Rerun buttons 1344 // are pressed. May never happen. If so, delete this. Before it was 1345 // commented that the problem could occur when they change the prefs. 1346 def showErrorDialog(title: String, msg: String) { 1347 val jOptionPane: JOptionPane = new NarrowJOptionPane(msg, JOptionPane.ERROR_MESSAGE) 1348 val jd: JDialog = jOptionPane.createDialog(RunnerJFrame.this, title) 1349 jd.setVisible(true) 1350 } 1351 1352 private class RunnerThread extends Thread { 1353 1354 override def run() { 1355 1356 withClassLoaderAndDispatchReporter(runpathList, reporterConfigurations, Some(graphicRunReporter), passFailReporter) { 1357 (loader, dispatchReporter) => { 1358 try { 1359 Runner.doRunRunRunDaDoRunRun(dispatchReporter, suitesList, junitsList, stopper, filter, 1360 propertiesMap, concurrent, memberOfList, beginsWithList, testNGList, runpathList, loader, RunnerJFrame.this, nextRunStamp, numThreads) 1361 } 1362 finally { 1363 stopper.reset() 1364 nextRunStamp += 1 1365 } 1366 } 1367 } 1368 } 1369 } 1370 1371 private class RerunnerThread(rerun: Rerunner) extends Thread { 1372 1373 if (rerun == null) 1374 throw new NullPointerException 1375 1376 override def run() { 1377 1378 val distributor: Option[Distributor] = None 1379 1380 val tracker = new Tracker(new Ordinal(nextRunStamp)) 1381 1382 withClassLoaderAndDispatchReporter(runpathList, reporterConfigurations, Some(graphicRerunReporter), None) { 1383 (loader, dispatchReporter) => { 1384 try { 1385 rerun(dispatchReporter, stopper, filter, propertiesMap, 1386 distributor, tracker, loader) 1387 } 1388 catch { 1389 case e: Throwable => { 1390 dispatchReporter.apply(RunAborted(tracker.nextOrdinal(), Resources.bigProblems(e), Some(e))) 1391 } 1392 } 1393 finally { 1394 stopper.reset() 1395 RunnerJFrame.this.done() 1396 nextRunStamp += 1 1397 } 1398 } 1399 } 1400 } 1401 } 1402} 1403 1404private[tools] object RunnerJFrame { 1405 1406 def getUpperCaseName(event: Event): String = getUpperCaseName(eventToEventToPresent(event)) 1407 1408 def getUpperCaseName(eventToPresent: EventToPresent) = 1409 eventToPresent match { 1410 case PresentRunStarting => "RUN_STARTING" 1411 case PresentTestStarting => "TEST_STARTING" 1412 case PresentTestFailed => "TEST_FAILED" 1413 case PresentTestSucceeded => "TEST_SUCCEEDED" 1414 case PresentTestIgnored => "TEST_IGNORED" 1415 case PresentTestPending => "TEST_PENDING" 1416 case PresentSuiteStarting => "SUITE_STARTING" 1417 case PresentSuiteAborted => "SUITE_ABORTED" 1418 case PresentSuiteCompleted => "SUITE_COMPLETED" 1419 case PresentInfoProvided => "INFO_PROVIDED" 1420 case PresentRunStopped => "RUN_STOPPED" 1421 case PresentRunAborted => "RUN_ABORTED" 1422 case PresentRunCompleted => "RUN_COMPLETED" 1423 } 1424} 1425