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">&nbsp;</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">&nbsp;</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