All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalatest.tools.RunnerJFrame.scala Maven / Gradle / Ivy

Go to download

ScalaTest is a free, open-source testing toolkit for Scala and Java programmers.

There is a newer version: 2.0.M6-SNAP4
Show newest version
/*
 * Copyright 2001-2008 Artima, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.scalatest.tools

import org.scalatest._
import java.awt.BorderLayout
import java.awt.Container
import java.awt.Dimension
import java.awt.GridLayout
import java.awt.Point
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.KeyEvent
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.PrintWriter
import java.net.URL
import javax.swing.AbstractAction
import javax.swing.DefaultListModel
import javax.swing.ImageIcon
import javax.swing.JButton
import javax.swing.JCheckBoxMenuItem
import javax.swing.JDialog
import javax.swing.JFrame
import javax.swing.WindowConstants
import javax.swing.JLabel
import javax.swing.JList
import javax.swing.JMenu
import javax.swing.JMenuBar
import javax.swing.JMenuItem
import javax.swing.JOptionPane
import javax.swing.JPanel
import javax.swing.JScrollPane
import javax.swing.JEditorPane
import javax.swing.KeyStroke
import javax.swing.ListSelectionModel
import javax.swing.border.BevelBorder
import javax.swing.border.EmptyBorder
import javax.swing.event.ListSelectionEvent
import javax.swing.event.ListSelectionListener
import Runner.usingEventDispatchThread
import Runner.withClassLoaderAndDispatchReporter
import java.util.concurrent.Semaphore
import java.util.regex.Pattern
import java.awt.event.WindowAdapter
import java.awt.event.WindowEvent
import java.awt.EventQueue
import org.scalatest.exceptions.GeneratorDrivenPropertyCheckFailedException
import org.scalatest.events._
import EventToPresent.eventToEventToPresent
import org.scalatest.exceptions.StackDepth

/**
 * The main class for Runner's GUI.
 *
 * eventTypesToCollect are the types of events that should be collected as a run runs.
 * This comes from the set of config options following the -g in the invocation of Runner.
 * If it is -gZ, for example, only test starting events will be collected as the runs run.
 * We don't collect options that aren't selected, because long runs can generate a lot of
 * events that would take up a lot of memory.
 *
 * @author Bill Venners
 */                 
private[scalatest] class RunnerJFrame(
  val eventTypesToCollect: Set[EventToPresent],
  reporterConfigurations: ReporterConfigurations,
  suitesList: List[SuiteParam],
  junitsList: List[String],
  runpathList: List[String], 
  tagsToIncludeSet: Set[String],
  tagsToExcludeSet: Set[String],
  propertiesMap: Map[String, Object],
  concurrent: Boolean,
  memberOfList: List[String], 
  beginsWithList: List[String],
  testNGList: List[String],
  passFailReporter: Option[Reporter],
  concurrentConfig: ConcurrentConfig,
  suffixes: Option[Pattern],
  chosenStyleSet: Set[String]
) extends JFrame(Resources("ScalaTestTitle")) with RunDoneListener with RunnerGUI {
  
  // This should only be updated by the event handler thread.
  private var currentState: RunnerGUIState = RunningState

  // The default options in the graphic view. Just show runs
  // and failures. This is also a selection in the View menu.
  private val runsAndFailures: Set[EventToPresent] =
    Set(
      PresentRunStarting,
      PresentTestFailed,
      PresentSuiteAborted,
      PresentRunStopped,
      PresentRunAborted,
      PresentRunCompleted
    )

  // These are the actual options to view in the list of events.
  // This must be the same set or a subset of eventTypesToCollect,
  // because you can't view something that wasn't collected.
  // This should only be updated by the event handler thread.
  private var viewOptions = runsAndFailures

  private val optionsMap: Map[EventToPresent, JCheckBoxMenuItem] = initializeOptionsMap

  private val aboutBox: AboutJDialog = initializeAboutBox()

  // The list of events collected from the most recent run
  // The most recently added event is at the head of the list.
  // This is volatile because it is sorted outside the event handler thread.
  @volatile private var collectedEvents: List[EventHolder] = Nil

  // The eventsListModel and eventsJList are used to display the current
  // collected events of types selected by the view menu
  private val eventsListModel: DefaultListModel = new DefaultListModel()
  private val eventsJList: JList = new JList(eventsListModel)

  // The detailsJEditorPane displays the text details of a event.
  private val detailsJEditorPane: JEditorPane = new JEditorPane("text/html", null)

  private val progressBar: ColorBar = new ColorBar()
  private val statusJPanel: StatusJPanel = new StatusJPanel()
  private val rerunColorBox: ColorBar = new ColorBar()
  private val runJButton: JButton = new JButton(Resources("Run"))
  private val rerunJButton: JButton = new JButton(Resources("Rerun"))

  private var testsCompletedCount: Int = 0
  private var rerunTestsCompletedCount: Int = 0

  private val graphicRunReporter: Reporter = new GraphicRunReporter
  private val graphicRerunReporter: Reporter = new GraphicRerunReporter
  
  class ResettableStopper extends Stopper {
    @volatile private var stopWasRequested = false
    def stopRequested: Boolean = stopWasRequested
    def requestStop() {
      stopWasRequested = true
    }
    def reset() {
      stopWasRequested = false
    }
  }

  private val stopper = new ResettableStopper

  private val exitSemaphore = new Semaphore(1)

  private var nextRunStamp = 1

  initialize()

  private def initialize() = {

    setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE)

    val ambientURL: URL = classOf[Suite].getClassLoader().getResource("images/greendot.gif")
    val ambientIcon: ImageIcon = new ImageIcon(ambientURL)
    setIconImage(ambientIcon.getImage())

    setupMenus()

    runJButton.setMnemonic(KeyEvent.VK_R)
    runJButton.addActionListener(
      new ActionListener() {
        def actionPerformed(ae: ActionEvent) {
          currentState = currentState.runButtonPressed(RunnerJFrame.this)
        }
      }
    )

    val progressBarHolder: JPanel = new JPanel()
    progressBarHolder.setLayout(new BorderLayout())
    progressBarHolder.setBorder(new BevelBorder(BevelBorder.LOWERED))
    progressBarHolder.add(progressBar, BorderLayout.CENTER)
    val pBarRunBtnJPanel: JPanel = new JPanel()

    pBarRunBtnJPanel.setLayout(new BorderLayout(5, 5))
    pBarRunBtnJPanel.add(progressBarHolder, BorderLayout.CENTER)
    pBarRunBtnJPanel.add(runJButton, BorderLayout.EAST)

    val progressJPanel: JPanel = new JPanel()

    progressJPanel.setLayout(new GridLayout(2, 1))
    progressJPanel.add(statusJPanel)
    progressJPanel.add(pBarRunBtnJPanel)
    val eventsJLabel: JLabel = new JLabel(Resources("eventsLabel"))

    val southHuggingEventsLabelJPanel: JPanel = new JPanel()

    southHuggingEventsLabelJPanel.setLayout(new BorderLayout())
    southHuggingEventsLabelJPanel.add(eventsJLabel, BorderLayout.SOUTH)
    southHuggingEventsLabelJPanel.setBorder(new EmptyBorder(0, 1, 0, 0))

    eventsJList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
    eventsJList.setCellRenderer(new IconEmbellishedListCellRenderer())
    val eventsJScrollPane: JScrollPane = new JScrollPane(eventsJList)

    val eventsJPanel: JPanel = new JPanel()

    eventsJPanel.setLayout(new BorderLayout())
    eventsJPanel.add(southHuggingEventsLabelJPanel, BorderLayout.NORTH)
    eventsJPanel.add(eventsJScrollPane, BorderLayout.CENTER)
    val detailsJLabel: JLabel = new JLabel(Resources("detailsLabel"))

    val southHuggingDetailsLabelJPanel: JPanel = new JPanel()

    southHuggingDetailsLabelJPanel.setLayout(new BorderLayout())
    southHuggingDetailsLabelJPanel.add(detailsJLabel, BorderLayout.SOUTH)
    southHuggingDetailsLabelJPanel.setBorder(new EmptyBorder(0, 1, 0, 0))

    rerunJButton.setMnemonic(KeyEvent.VK_E)
    rerunJButton.setEnabled(false)

    val rerunColorBoxHolder: JPanel = new JPanel()

    rerunColorBoxHolder.setLayout(new BorderLayout())
    rerunColorBoxHolder.setBorder(new BevelBorder(BevelBorder.LOWERED))
    rerunColorBoxHolder.add(rerunColorBox, BorderLayout.CENTER)
    val rerunJPanel: JPanel = new JPanel()

    rerunJPanel.setLayout(new GridLayout(1, 2, 5, 5))
    rerunJPanel.add(rerunColorBoxHolder)
    rerunJPanel.add(rerunJButton)
    rerunJPanel.setBorder(new EmptyBorder(0, 0, 5, 0))
    val detailsNorthJPanel: JPanel = new JPanel()

    detailsNorthJPanel.setLayout(new BorderLayout())
    detailsNorthJPanel.add(BorderLayout.WEST, southHuggingDetailsLabelJPanel)
    detailsNorthJPanel.add(BorderLayout.EAST, rerunJPanel)

    detailsJEditorPane.setEditable(false)

    val detailsJScrollPane: JScrollPane = new JScrollPane(detailsJEditorPane)

    val detailsJPanel: JPanel = new JPanel()

    detailsJPanel.setLayout(new BorderLayout())
    detailsJPanel.add(detailsNorthJPanel, BorderLayout.NORTH)
    detailsJPanel.add(detailsJScrollPane, BorderLayout.CENTER)
    val eventsDetailsPanel: JPanel = new JPanel()

    eventsDetailsPanel.setLayout(new GridLayout(2, 1, 5, 5))
    eventsDetailsPanel.add(eventsJPanel)
    eventsDetailsPanel.add(detailsJPanel)
    val reporterJPanel: JPanel = new JPanel()

    reporterJPanel.setLayout(new BorderLayout(5, 5))
    reporterJPanel.add(progressJPanel, BorderLayout.NORTH)
    reporterJPanel.add(eventsDetailsPanel, BorderLayout.CENTER)
    eventsJList.addListSelectionListener(
      new ListSelectionListener() {
        def valueChanged(e: ListSelectionEvent) {

          val holder: EventHolder = eventsJList.getSelectedValue().asInstanceOf[EventHolder]

          if (holder == null) {

            // This means nothing is currently selected
            detailsJEditorPane.setText("")
            currentState = currentState.listSelectionChanged(RunnerJFrame.this)
          }
          else {

            val event: Event = holder.event
            val isRerun: Boolean = holder.isRerun
  
            val fontSize = eventsJList.getFont.getSize

            val title = 
              if (isRerun)
                Resources("RERUN_" + RunnerJFrame.getUpperCaseName(event))
              else
                Resources(RunnerJFrame.getUpperCaseName(event))

            val isFailureEvent =
              event match {
                case _: TestFailed => true
                case _: SuiteAborted => true
                case _: RunAborted => true
                case _ => false
              }

            val fileAndLineOption: Option[String] = 
              holder.throwable match {
                case Some(throwable) =>
                  throwable match {
                    case stackDepth: StackDepth =>
                      stackDepth.failedCodeFileNameAndLineNumberString
                    case _ => None
                  }
                case None => None
              }

              val throwableTitle = 
                holder.throwable match {
                  case Some(throwable) => Some(throwable.getClass.getName)
                  case None => None
                }

              // Any stack trace elements lower than a TestFailedException's failedCodeStackDepth
              // will show up as gray in the displayed stack trace, because those are ScalaTest methods.
              // The rest will show up as black.
              val (grayStackTraceElements, blackStackTraceElements) =
                holder.throwable match {
                  case Some(throwable) =>
                    val stackTraceElements = throwable.getStackTrace.toList
                    throwable match {
                      case tfe: TestFailedException =>
                        (stackTraceElements.take(tfe.failedCodeStackDepth), stackTraceElements.drop(tfe.failedCodeStackDepth))
                      case _ => (List(), stackTraceElements)
                    } 
                  case None => (List(), List())
                }

            def getHTMLForStackTrace(stackTraceList: List[StackTraceElement]) =
              stackTraceList.map((ste: StackTraceElement) => { ste.toString }
) def getHTMLForCause(throwable: Throwable): scala.xml.NodeBuffer = { val cause = throwable.getCause if (cause != null) {
{ Resources("DetailsCause") + ":" } { cause.getClass.getName }
{ Resources("DetailsMessage") + ":" } { if (cause.getMessage != null) cause.getMessage else Resources("None") }
{ getHTMLForStackTrace(cause.getStackTrace.toList) }
&+ getHTMLForCause(cause) } else new scala.xml.NodeBuffer } val mainMessage = holder.message match { case Some(msg) => val trimmed = msg.trim if (trimmed.length > 0) Some(trimmed) else None case _ => None } import EventHolder.suiteAndTestName def nameFromNameInfo(nameInfo: Option[NameInfo]): Option[String] = nameInfo match { case Some(NameInfo(suiteName, suiteId, suiteClassName, testName)) => testName match { case Some(testName) => Some(suiteAndTestName(suiteName, testName)) case None => Some(suiteName) } case None => None } val name = holder.event match { case event: RunStarting => None case event: RunStopped => None case event: RunAborted => None case event: RunCompleted => None case event: InfoProvided => nameFromNameInfo(event.nameInfo) case event: ScopeOpened => nameFromNameInfo(Some(event.nameInfo)) case event: ScopeClosed => nameFromNameInfo(Some(event.nameInfo)) case event: MarkupProvided => nameFromNameInfo(event.nameInfo) // Should not get here because I'm not registering MarkupInfos case event: SuiteStarting => Some(event.suiteName) case event: SuiteCompleted => Some(event.suiteName) case event: SuiteAborted => Some(event.suiteName) case event: TestStarting => Some(suiteAndTestName(event.suiteName, event.testName)) case event: TestPending => Some(suiteAndTestName(event.suiteName, event.testName)) case event: TestCanceled => Some(suiteAndTestName(event.suiteName, event.testName)) case event: TestIgnored => Some(suiteAndTestName(event.suiteName, event.testName)) case event: TestSucceeded => Some(suiteAndTestName(event.suiteName, event.testName)) case event: TestFailed => Some(suiteAndTestName(event.suiteName, event.testName)) } val duration = holder.event match { case event: RunStarting => None case event: RunStopped => event.duration case event: RunAborted => event.duration case event: RunCompleted => event.duration case event: InfoProvided => None case event: ScopeOpened => None case event: ScopeClosed => None case event: MarkupProvided => None // Shouldn't get here because not registering MarkupInfos case event: SuiteStarting => None case event: SuiteCompleted => event.duration case event: SuiteAborted => event.duration case event: TestStarting => None case event: TestPending => None case event: TestCanceled => event.duration case event: TestIgnored => None case event: TestSucceeded => event.duration case event: TestFailed => event.duration } val detailsHTML = { if (name.isDefined) { } else } { if (mainMessage.isDefined) { } else } { fileAndLineOption match { case Some(fileAndLine) => case None => } } { holder.summary match { case Some(summary) => case None => new scala.xml.NodeBuffer } } { duration match { case Some(milliseconds) => case None => new scala.xml.NodeBuffer } } { throwableTitle match { case Some(title) => case None => new scala.xml.NodeBuffer } }
{ Resources("DetailsEvent") + ":" }{ title }
{ Resources("DetailsName") + ":" }{ name.get }
{ Resources("DetailsMessage") + ":" } { // Put
's in for line returns at least, so property check failure messages look better def lineSpans = for (line <- mainMessage.get.split('\n')) yield { line }
if (isFailureEvent) { { lineSpans } } else { { lineSpans } } }
{ Resources("LineNumber") + ":" }{ "(" + fileAndLine + ")" }
{ Resources("DetailsSummary") + ":" }{ Resources("totalNumberOfTestsRun", summary.testsCompletedCount.toString) }
 { Resources("suiteSummary", summary.suitesCompletedCount.toString, summary.suitesAbortedCount.toString) }
  { Resources( "testSummary", summary.testsSucceededCount.toString, summary.testsFailedCount.toString, summary.testsCanceledCount.toString, summary.testsIgnoredCount.toString, summary.testsPendingCount.toString ) }
{ Resources("DetailsDuration") + ":" }{ PrintReporter.makeDurationString(milliseconds) }
{ Resources("DetailsDate") + ":" }{ new java.util.Date(event.timeStamp) }
{ Resources("DetailsThread") + ":" }{ event.threadName }
{ Resources("DetailsThrowable") + ":" }{ title }
{ grayStackTraceElements.map((ste: StackTraceElement) => { ste.toString }
) } { blackStackTraceElements.map((ste: StackTraceElement) => { ste.toString }
) }
{ holder.throwable match { case Some(t) => getHTMLForCause(t) case None => } } detailsJEditorPane.setText(detailsHTML.toString) detailsJEditorPane.setCaretPosition(0) currentState = currentState.listSelectionChanged(RunnerJFrame.this) } } } ) rerunJButton.addActionListener( new ActionListener() { def actionPerformed(ae: ActionEvent) { currentState = currentState.rerunButtonPressed(RunnerJFrame.this) } } ) val mainJPanel: JPanel = new JPanel() mainJPanel.setLayout(new BorderLayout(5, 5)) mainJPanel.setBorder(new EmptyBorder(5, 5, 5, 5)) mainJPanel.add(reporterJPanel, BorderLayout.CENTER) val pane: Container = getContentPane() pane.setLayout(new GridLayout(1, 1)) pane.add(mainJPanel) // Set the size of both buttons to the max of the localized labels Run, Rerun, and Stop val runButtonSize: Dimension = runJButton.getPreferredSize() val rerunButtonSize: Dimension = rerunJButton.getPreferredSize() // Create a throw away button to get the size of Stop val stopButtonSize: Dimension = new JButton(Resources("Stop")).getPreferredSize() val preferredSize = new Dimension( runButtonSize.width.max(rerunButtonSize.width.max(stopButtonSize.width)), runButtonSize.height.max(rerunButtonSize.height.max(stopButtonSize.height)) ) runJButton.setPreferredSize(preferredSize) rerunJButton.setPreferredSize(preferredSize) exitSemaphore.acquire() addWindowListener( new WindowAdapter { override def windowClosed(e: WindowEvent) { exitSemaphore.release() } } ) pack() val dim: Dimension = getSize() dim.height = dim.height / 5 + dim.height dim.width = dim.height / 3 * 4 setSize(dim) } private[scalatest] def blockUntilWindowClosed() { exitSemaphore.acquire() } // This initialize method idiom is a way to get rid of a var // when you have verbose initialization. private def initializeAboutBox() = { val title2: String = Resources("AboutBoxTitle") new AboutJDialog(RunnerJFrame.this, title2) } private def setupMenus() { val menuBar: JMenuBar = new JMenuBar() // The ScalaTest menu val scalaTestMenu: JMenu = new JMenu(Resources("ScalaTestMenu")) scalaTestMenu.setMnemonic(KeyEvent.VK_S) menuBar.add(scalaTestMenu) // The ScalaTest.About menu item val aboutItem: JMenuItem = new JMenuItem(Resources("About"), KeyEvent.VK_A) scalaTestMenu.add(aboutItem) aboutItem.addActionListener( new ActionListener() { def actionPerformed(ae: ActionEvent) { val location: Point = getLocation() location.x += 20 location.y += 6 aboutBox.setLocation(location) aboutBox.setVisible(true) } } ) scalaTestMenu.addSeparator() // The ScalaTest.Exit menu item val exitItem: JMenuItem = new JMenuItem(Resources("Exit"), KeyEvent.VK_X) scalaTestMenu.add(exitItem) exitItem.addActionListener( new ActionListener() { def actionPerformed(ae: ActionEvent) { dispose() // Only exit if started from main(), not run(). If starting from run(), // we want to return a pass/fail status from run(). Actually, if we // have a passFailReporter, then that means we want to indicate status, // so that's why it is used here to determine whether or not to exit. passFailReporter match { case Some(_) => case None => System.exit(0) } } } ) // The View menu val viewMenu = new JMenu(Resources("ViewMenu")) viewMenu.setMnemonic(KeyEvent.VK_V) // the View.Runs and Failures menu item val runsFailuresItem: JMenuItem = new JMenuItem(Resources("runsFailures"), KeyEvent.VK_F) runsFailuresItem.setAccelerator(KeyStroke.getKeyStroke("control F")) viewMenu.add(runsFailuresItem) runsFailuresItem.addActionListener( new ActionListener() { def actionPerformed(ae: ActionEvent) { viewOptions = runsAndFailures intersect eventTypesToCollect updateViewOptionsAndEventsList() } } ) val allEventsItem: JMenuItem = new JMenuItem(Resources("allEvents"), KeyEvent.VK_A) allEventsItem.setAccelerator(KeyStroke.getKeyStroke("control L")) viewMenu.add(allEventsItem) allEventsItem.addActionListener( new ActionListener() { def actionPerformed(ae: ActionEvent) { viewOptions = eventTypesToCollect updateViewOptionsAndEventsList() } } ) viewMenu.addSeparator() // Add the checkboxes in the correct order viewMenu.add(optionsMap(PresentRunStarting)) viewMenu.add(optionsMap(PresentTestStarting)) viewMenu.add(optionsMap(PresentTestSucceeded)) viewMenu.add(optionsMap(PresentTestFailed)) viewMenu.add(optionsMap(PresentTestIgnored)) viewMenu.add(optionsMap(PresentSuiteStarting)) viewMenu.add(optionsMap(PresentSuiteCompleted)) viewMenu.add(optionsMap(PresentSuiteAborted)) viewMenu.add(optionsMap(PresentInfoProvided)) viewMenu.add(optionsMap(PresentRunStopped)) viewMenu.add(optionsMap(PresentRunCompleted)) viewMenu.add(optionsMap(PresentRunAborted)) menuBar.add(viewMenu) setJMenuBar(menuBar) } private def initializeOptionsMap(): Map[EventToPresent, JCheckBoxMenuItem] = { // TODO: Why am I using an immutable map here. Better a val with a mutable map I'd think. var map: Map[EventToPresent, JCheckBoxMenuItem] = Map() for (option <- EventToPresent.allEventsToPresent) { val rawOptionName = RunnerJFrame.getUpperCaseName(option) val menuItemText: String = Resources("MENU_PRESENT_" + rawOptionName) val itemAction: AbstractAction = new AbstractAction(menuItemText) { def actionPerformed(ae: ActionEvent) { val checkBox: JCheckBoxMenuItem = ae.getSource().asInstanceOf[JCheckBoxMenuItem] val option = getValue("option").asInstanceOf[EventToPresent] if (viewOptions.contains(option)) viewOptions = viewOptions - option else viewOptions = viewOptions + option val checked: Boolean = viewOptions.contains(option) checkBox.setState(checked) // Now, since the configuration changed, we need to update the // list display appropriately: refreshEventsJList() } } val checked: Boolean = viewOptions.contains(option) val checkBox: JCheckBoxMenuItem = new JCheckBoxMenuItem(itemAction) checkBox.setState(checked) // Put the option into the checkbox's AbstractAction, so it can be // taken out when the checkbox is checked or unchecked. itemAction.putValue("option", option) map = map + (option -> checkBox) } map } def requestStop() { stopper.requestStop() } private def updateViewOptionsAndEventsList() { for (option <- EventToPresent.allEventsToPresent) { val box: JCheckBoxMenuItem = optionsMap(option) if (eventTypesToCollect.contains(option)) { box.setEnabled(true) if (viewOptions.contains(option)) box.setSelected(true) else box.setSelected(false) } else { box.setSelected(false) box.setEnabled(false) } } // Now, since the configuration changed, we need to update the // list display appropriately: refreshEventsJList() } private def reorderCollectedEvents() { collectedEvents = collectedEvents.sortWith((a, b) => a.event.ordinal > b.event.ordinal) } private def refreshEventsJList() { val formerlySelectedItem: EventHolder = eventsJList.getSelectedValue().asInstanceOf[EventHolder] // clear the list of events and the detail area eventsListModel.clear() detailsJEditorPane.setText("") for (holder <- collectedEvents.reverse; if viewOptions.contains(eventToEventToPresent(holder.event))) { val shouldAddElement = holder.event.formatter match { case Some(MotionToSuppress) => false case _ => true } if (shouldAddElement) eventsListModel.addElement(holder) } // Isn't there a risk that the formerly selected item will no longer exist in the list? // Does this result in an exception? Of course the stupid JavaDoc API docs is silent on this. // TODO: try this and fix if need be eventsJList.setSelectedValue(formerlySelectedItem, true) } private def registerEvent(event: Event): EventHolder = { registerRunOrRerunEvent(event, false) } private def registerRerunEvent(event: Event): EventHolder = { registerRunOrRerunEvent(event, true) } private def registerRunOrRerunEvent(event: Event, isRerun: Boolean): EventHolder = { val (message, throwable) = event match { case e: TestFailed => (Some(e.message), e.throwable) case e: SuiteAborted => (Some(e.message), e.throwable) case e: RunAborted => (Some(e.message), e.throwable) case e: InfoProvided => (Some(e.message), e.throwable) case _ => (None, None) } val rerunner = event match { case e: TestStarting => e.rerunner case e: TestSucceeded => e.rerunner case e: TestFailed => e.rerunner case e: SuiteStarting => e.rerunner case e: SuiteCompleted => e.rerunner case e: SuiteAborted => e.rerunner case _ => None } val summary = event match { case e: RunCompleted => e.summary case e: RunAborted => e.summary case e: RunStopped => e.summary case _ => None } val eventHolder: EventHolder = new EventHolder(event, message, throwable, rerunner, summary, isRerun) if (eventTypesToCollect.contains(eventToEventToPresent(event))) { collectedEvents = eventHolder :: collectedEvents if (viewOptions.contains(eventToEventToPresent(event))) { val shouldAddElement = event.formatter match { case Some(MotionToSuppress) => false case _ => true } if (shouldAddElement) eventsListModel.addElement(eventHolder) } } eventHolder } private class GraphicRunReporter extends Reporter { override def apply(event: Event) { event match { case RunStarting(ordinal, testCount, configMap, formatter, location, payload, threadName, timeStamp) => // Create the Event outside of the event handler thread, because otherwise // the event handler thread shows up as the originating thread of this event, // and that looks bad and is wrong to boot. val eventHolder: EventHolder = new EventHolder(event, None, None, None) usingEventDispatchThread { testsCompletedCount = 0 progressBar.setMax(testCount) progressBar.setValue(0) progressBar.setGreen() statusJPanel.reset() statusJPanel.setTestsExpected(testCount) // This should already have been cleared by prepUIForStarting, but // doing it again here for the heck of it. collectedEvents = eventHolder :: Nil eventsListModel.clear() detailsJEditorPane.setText("") if (viewOptions.contains(PresentRunStarting)) eventsListModel.addElement(eventHolder) } case RunCompleted(ordinal, duration, summary, formatter, location, payload, threadName, timeStamp) => // Create the Report outside of the event handler thread, because otherwise // the event handler thread shows up as the originating thread of this event, // and that looks bad and is wrong to boot. // Reordering this outside the event handler thread so that the GUI won't be frozen // during a long sort. The RunCompleted event isn't yet in the list when the sort happens, so // it will just be added at the end. reorderCollectedEvents() usingEventDispatchThread { registerEvent(event) refreshEventsJList() } case RunAborted(ordinal, message, throwable, duration, summary, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { progressBar.setRed() registerEvent(event) // Must do this here, not in RunningState.runFinished, because the runFinished // invocation can happen before this runCompleted invocation, which means that // the first error in the run may not be in the JList model yet. So must wait until // a run completes. I was doing it in runCompleted, which works, but for long runs // you must wait a long time for that thing to be selected. Nice if it gets selected // right away. selectFirstFailureIfExistsAndNothingElseAlreadySelected() } case RunStopped(ordinal, duration, summary, formatter, location, payload, threadName, timeStamp) => // Create the Report outside of the event handler thread, because otherwise // the event handler thread shows up as the originating thread of this event, // and that looks bad and is actually wrong. usingEventDispatchThread { registerEvent(event) } case SuiteStarting(ordinal, suiteName, suiteId, suiteClassName, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { registerEvent(event) } case SuiteCompleted(ordinal, suiteName, suiteId, suiteClassName, duration, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { registerEvent(event) } case SuiteAborted(ordinal, message, suiteName, suiteId, suiteClassName, throwable, duration, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { progressBar.setRed() registerEvent(event) // Must do this here, not in RunningState.runFinished, because the runFinished // invocation can happen before this runCompleted invocation, which means that // the first error in the run may not be in the JList model yet. So must wait until // a run completes. I was doing it in runCompleted, which works, but for long runs // you must wait a long time for that thing to be selected. Nice if it gets selected // right away. selectFirstFailureIfExistsAndNothingElseAlreadySelected() } case TestStarting(ordinal, suiteName, suiteId, suiteClassName, testName, testText, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { registerEvent(event) } case TestIgnored(ordinal, suiteName, suiteId, suiteClassName, testName, testText, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { registerEvent(event) } case TestPending(ordinal, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, duration, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { testsCompletedCount += 1 statusJPanel.setTestsRun(testsCompletedCount, true) progressBar.setValue(testsCompletedCount) registerEvent(event) recordedEvents.foreach(registerEvent(_)) } case TestCanceled(ordinal, message, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, throwable, duration, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { testsCompletedCount += 1 statusJPanel.setTestsRun(testsCompletedCount, true) progressBar.setValue(testsCompletedCount) registerEvent(event) recordedEvents.foreach(registerEvent(_)) } case TestSucceeded(ordinal, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, duration, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { testsCompletedCount += 1 statusJPanel.setTestsRun(testsCompletedCount, true) progressBar.setValue(testsCompletedCount) registerEvent(event) recordedEvents.foreach(registerEvent(_)) } case TestFailed(ordinal, message, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, throwable, duration, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { testsCompletedCount += 1 // Passing in false here increments the test failed count // in the statusJPanel, which updates the counter on the GUI statusJPanel.setTestsRun(testsCompletedCount, false) progressBar.setValue(testsCompletedCount) progressBar.setRed() registerEvent(event) recordedEvents.foreach(registerEvent(_)) // Must do this here, not in RunningState.runFinished, because the runFinished // invocation can happen before this runCompleted invocation, which means that // the first error in the run may not be in the JList model yet. So must wait until // a run completes. I was doing it in runCompleted, which works, but for long runs // you must wait a long time for that thing to be selected. Nice if it gets selected // right away. selectFirstFailureIfExistsAndNothingElseAlreadySelected() } case InfoProvided(ordinal, message, nameInfo, throwable, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { registerEvent(event) } case ScopeOpened(ordinal, message, nameInfo, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { registerEvent(event) } case ScopeClosed(ordinal, message, nameInfo, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { registerEvent(event) } case MarkupProvided(ordinal, message, nameInfo, formatter, location, payload, threadName, timeStamp) => } } } // Invoked when a test is done. This is used to turn the Run button back on after // a Stop request has disabled it. When this method is invoked by the runner, it // means that the run has finished, so that it is OK to enable Run again. override def done() { usingEventDispatchThread { currentState = currentState.runFinished(RunnerJFrame.this) } } // Called from the main thread initially, thereafter from the event handler thread override def runFromGUI() { (new RunnerThread).start() } // Must be called from event handler thread override def rerunFromGUI(rerunner: Rerunner) { (new RerunnerThread(rerunner)).start() } // This must be called by the event handler thread def prepUIForRunning() { val stopText: String = Resources("Stop") val rerunText: String = Resources("Rerun") runJButton.setText(stopText) rerunJButton.setText(rerunText) runJButton.setEnabled(true) rerunJButton.setEnabled(false) rerunColorBox.setGray() progressBar.setGray() statusJPanel.reset() statusJPanel.setTestsExpected(0) collectedEvents = Nil eventsListModel.clear() detailsJEditorPane.setText("") } // This must be called by the event handler thread def prepUIWhileRunning() { val stopText: String = Resources("Stop") val rerunText: String = Resources("Rerun") runJButton.setText(stopText) rerunJButton.setText(rerunText) runJButton.setEnabled(true) rerunJButton.setEnabled(false) rerunColorBox.setGray() } // This must be called by the event handler thread def prepUIForRerunning() { val runText: String = Resources("Run") val stopText: String = Resources("Stop") runJButton.setText(runText) rerunJButton.setText(stopText) runJButton.setEnabled(false) rerunJButton.setEnabled(true) rerunColorBox.setGray() // Clear the selection, so it can scroll to an error eventsJList.clearSelection() } // This must be called by the event handler thread def prepUIWhileRerunning() { val runText: String = Resources("Run") val stopText: String = Resources("Stop") runJButton.setText(runText) rerunJButton.setText(stopText) runJButton.setEnabled(false) rerunJButton.setEnabled(true) } // This must be called by the event handler thread def prepUIForReady() { val runText: String = Resources("Run") val rerunText: String = Resources("Rerun") runJButton.setText(runText) rerunJButton.setText(rerunText) runJButton.setEnabled(true) val holder: EventHolder = eventsJList.getSelectedValue.asInstanceOf[EventHolder] rerunJButton.setEnabled(holder != null && holder.rerunner.isDefined) } // This must be called by the event handler thread def prepUIForStopping() { val stopText: String = Resources("Stop") val rerunText: String = Resources("Rerun") runJButton.setText(stopText) rerunJButton.setText(rerunText) runJButton.setEnabled(false) rerunJButton.setEnabled(false) } // This must be called by the event handler thread def prepUIForReStopping() { val runText: String = Resources("Run") val stopText: String = Resources("Stop") runJButton.setText(runText) rerunJButton.setText(stopText) runJButton.setEnabled(false) rerunJButton.setEnabled(false) } private def getModelAsList: List[EventHolder] = { val model = eventsJList.getModel val listBuf = new scala.collection.mutable.ListBuffer[EventHolder] for (i <- 0 until model.getSize) { listBuf += model.getElementAt(i).asInstanceOf[EventHolder] } listBuf.toList } private def isFailureEvent(eventHolder: EventHolder) = eventHolder.event match { case _: TestFailed => true case _: SuiteAborted => true case _: RunAborted => true case _ => false } // This must be called by the event handler thread /* After a rerun, there will usually always be a selected item, which is what was selected to rerun. If the rerun resulted in an error, it would be nice to select that first error and scroll down. So this will do that, which means it doesn't care if something is already selected. It will always select the first error in the last rerun if one exists. This is called as errors come in during a rerun. The error's eventHolder is passed. It will be selected only if it is the first error in the last rerun. Any other time this method will do nothing. The reason is that reruns can take a while, and the user may be selecting and exploring the results as it runs. So I don't want to keep forcing a different selection. Only the first time an error comes in in a rerun will it happen. (During a run, the first error will be selected only if there is no other selection. But here it happens even if something else is selected, because during a rerun normally the thing you wanted to rerun will already be selected.) */ private def selectFirstErrorInLastRerunIfThisIsThatError(candidateEventHolder: EventHolder) { // First get the model into a List val modelList = getModelAsList if (modelList.exists(_.isRerun)) { val listOfEventsForLastRerunExcludingRunStarting = modelList.reverse.takeWhile(eventHolder => eventHolder.isRerun && !eventHolder.event.isInstanceOf[RunStarting]) val firstTestFailedEventInLastRerun = listOfEventsForLastRerunExcludingRunStarting.reverse.find(isFailureEvent(_)) firstTestFailedEventInLastRerun match { case Some(eventHolder) => if (eventHolder == candidateEventHolder) // Only select it if the one passed is the first one eventsJList.setSelectedValue(eventHolder, true) case None => // do nothing if no failure events in last rerun } } } private def scrollTheRerunStartingEventToTheTopOfVisibleEvents() { def indexOfRunStartingEventForLastRerunOption: Option[Int] = { var i = eventsListModel.getSize - 1 var found = false while (i >= 0 && !found) { val holder = eventsListModel.getElementAt(i).asInstanceOf[EventHolder] if (holder.event.isInstanceOf[RunStarting]) { found = true } if (!found) i -= 1 } if (found) Some(i) else None } val selectedEventHandler = eventsJList.getSelectedValue.asInstanceOf[EventHolder] if (selectedEventHandler == null || selectedEventHandler.event.isInstanceOf[RunStarting]) { // only scroll if there's no selection, which means no error happened val firstVisibleIndex = eventsJList.getFirstVisibleIndex val lastVisibleIndex = eventsJList.getLastVisibleIndex if (lastVisibleIndex > firstVisibleIndex) { // should always be true, but this is better than an assert because things will keep going val numCellsVisible = lastVisibleIndex - firstVisibleIndex val indexOfLastEvent = eventsListModel.getSize - 1 indexOfRunStartingEventForLastRerunOption match { case Some(indexOfRunStartingEventForLastRerun) => val indexToEnsureIsVisible = if (indexOfRunStartingEventForLastRerun + numCellsVisible < indexOfLastEvent) indexOfRunStartingEventForLastRerun + numCellsVisible else indexOfLastEvent eventsJList.ensureIndexIsVisible(indexToEnsureIsVisible) // Select one event after the rerun starting event, if it is a test starting, test succeeded, or suite starting event, // because this should be the one they requested was rerun. So that's the most intuitive one to select // after a run if there was no error. (Test succeeded is possible because Spec's will send MotionToSupress formatters that // say not to display test starting events.) val indexOfSecondEventInRerun = indexOfRunStartingEventForLastRerun + 1 if (indexOfSecondEventInRerun <= indexOfLastEvent) { // Should always be true, but an if is better than an assert val firstEventAfterRerunStarting = eventsListModel.getElementAt(indexOfSecondEventInRerun).asInstanceOf[EventHolder] if (firstEventAfterRerunStarting.event.isInstanceOf[TestStarting] || firstEventAfterRerunStarting.event.isInstanceOf[SuiteStarting] || firstEventAfterRerunStarting.event.isInstanceOf[TestSucceeded]) { eventsJList.setSelectedIndex(indexOfSecondEventInRerun) } // If they have display only Runs and Failures selected, it won't show successful tests. In that case // just select the run starting event. else eventsJList.setSelectedIndex(indexOfRunStartingEventForLastRerun) } case None => } } } } // This must be called by the event handler thread private def selectFirstFailureIfExistsAndNothingElseAlreadySelected() { val holder: EventHolder = eventsJList.getSelectedValue.asInstanceOf[EventHolder] if (holder == null) { // Only do this if something isn't already selected // First get the model into a List val modelList = getModelAsList val firstFailureEvent = modelList.find(isFailureEvent(_)) firstFailureEvent match { case Some(eventHolder) => eventsJList.setSelectedValue(eventHolder, true) case None => // do nothing if no failure events in the run } } } def getSelectedRerunner(): Option[Rerunner] = { val holder: EventHolder = eventsJList.getSelectedValue().asInstanceOf[EventHolder] if (holder == null) None else { holder.rerunner match { case Some(rerunner) => holder.event match { case e: TestStarting => Some(new TestRerunner(rerunner, e.testName)) case e: TestSucceeded => Some(new TestRerunner(rerunner, e.testName)) case e: TestFailed => Some(new TestRerunner(rerunner, e.testName)) case e: SuiteStarting => Some(new SuiteRerunner(rerunner)) case e: SuiteCompleted => Some(new SuiteRerunner(rerunner)) case e: SuiteAborted => Some(new SuiteRerunner(rerunner)) case _ => None } case None => None } } } private class GraphicRerunReporter extends Reporter { // This is written by the event handler thread to avoid having the event handler thread spend time // determining if an error has previously occurred by looking through the events. This way if a // rerun has a lot of errors, you don't hang up the GUI giving the event handler thread too much // work to do. var anErrorHasOccurredAlready = false def apply(event: Event) { event match { case RunStarting(ordinal, testCount, configMap, formatter, location, payload, threadName, timeStamp) => // Create the Report outside of the event handler thread, because otherwise // the event handler thread shows up as the originating thread of this event, // and that looks bad and is actually wrong. usingEventDispatchThread { rerunTestsCompletedCount = 0 rerunColorBox.setMax(testCount) rerunColorBox.setValue(0) rerunColorBox.setGreen() registerRerunEvent(event) anErrorHasOccurredAlready = false; } case RunCompleted(ordinal, duration, summary, formatter, location, payload, threadName, timeStamp) => // Create the Report outside of the event handler thread, because otherwise // the event handler thread shows up as the originating thread of this event, // and that looks bad and is actually wrong. usingEventDispatchThread { registerRerunEvent(event) scrollTheRerunStartingEventToTheTopOfVisibleEvents() } case RunAborted(ordinal, message, throwable, duration, summary, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { rerunColorBox.setRed() val eventHolder = registerRerunEvent(event) if (!anErrorHasOccurredAlready) { selectFirstErrorInLastRerunIfThisIsThatError(eventHolder) anErrorHasOccurredAlready = true } } case RunStopped(ordinal, duration, summary, formatter, location, payload, threadName, timeStamp) => // Create the Report outside of the event handler thread, because otherwise // the event handler thread shows up as the originating thread of this event, // and that looks bad and is actually wrong. usingEventDispatchThread { registerRerunEvent(event) scrollTheRerunStartingEventToTheTopOfVisibleEvents() } case SuiteStarting(ordinal, suiteName, suiteId, suiteClassName, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { registerRerunEvent(event) } case SuiteCompleted(ordinal, suiteName, suiteId, suiteClassName, duration, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { registerRerunEvent(event) } case SuiteAborted(ordinal, message, suiteName, suiteId, suiteClassName, throwable, duration, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { rerunColorBox.setRed() val eventHolder = registerRerunEvent(event) if (!anErrorHasOccurredAlready) { selectFirstErrorInLastRerunIfThisIsThatError(eventHolder) anErrorHasOccurredAlready = true } } case TestStarting(ordinal, suiteName, suiteId, suiteClassName, testName, testText, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { registerRerunEvent(event) } case TestIgnored(ordinal, suiteName, suiteId, suiteClassName, testName, testText, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { rerunColorBox.setValue(rerunTestsCompletedCount) registerRerunEvent(event) } case TestPending(ordinal, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, duration, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { rerunColorBox.setValue(rerunTestsCompletedCount) registerRerunEvent(event) recordedEvents.foreach(registerRerunEvent(_)) } case TestCanceled(ordinal, message, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, throwable, duration, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { rerunColorBox.setValue(rerunTestsCompletedCount) registerRerunEvent(event) recordedEvents.foreach(registerRerunEvent(_)) } case TestSucceeded(ordinal, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, duration, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { rerunTestsCompletedCount += 1 rerunColorBox.setValue(rerunTestsCompletedCount) registerRerunEvent(event) recordedEvents.foreach(registerRerunEvent(_)) } case TestFailed(ordinal, message, suiteName, suiteId, suiteClassName, testName, testText, recordedEvents, throwable, duration, formatter, location, rerunner, payload, threadName, timeStamp) => usingEventDispatchThread { rerunTestsCompletedCount += 1 rerunColorBox.setValue(rerunTestsCompletedCount) rerunColorBox.setRed() val eventHolder = registerRerunEvent(event) recordedEvents.foreach(registerRerunEvent(_)) if (!anErrorHasOccurredAlready) { selectFirstErrorInLastRerunIfThisIsThatError(eventHolder) anErrorHasOccurredAlready = true } } case InfoProvided(ordinal, message, nameInfo, throwable, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { registerRerunEvent(event) } case ScopeOpened(ordinal, message, nameInfo, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { registerRerunEvent(event) } case ScopeClosed(ordinal, message, nameInfo, formatter, location, payload, threadName, timeStamp) => usingEventDispatchThread { registerRerunEvent(event) } case MarkupProvided(ordinal, message, nameInfo, formatter, location, payload, threadName, timeStamp) => } } } // Invoked by ReadyState if can't run when the Run or Rerun buttons // are pressed. May never happen. If so, delete this. Before it was // commented that the problem could occur when they change the prefs. def showErrorDialog(title: String, msg: String) { val jOptionPane: JOptionPane = new NarrowJOptionPane(msg, JOptionPane.ERROR_MESSAGE) val jd: JDialog = jOptionPane.createDialog(RunnerJFrame.this, title) jd.setVisible(true) } private class RunnerThread extends Thread { override def run() { withClassLoaderAndDispatchReporter(runpathList, reporterConfigurations, Some(graphicRunReporter), passFailReporter) { (loader, dispatchReporter) => { try { Runner.doRunRunRunDaDoRunRun( dispatchReporter, suitesList, junitsList, stopper, tagsToIncludeSet, tagsToExcludeSet, propertiesMap, concurrent, memberOfList, beginsWithList, testNGList, runpathList, loader, RunnerJFrame.this, nextRunStamp, concurrentConfig, suffixes, chosenStyleSet ) } finally { stopper.reset() nextRunStamp += 1 } } } } } private class RerunnerThread(rerun: Rerunner) extends Thread { if (rerun == null) throw new NullPointerException override def run() { val distributor: Option[Distributor] = None val tracker = new Tracker(new Ordinal(nextRunStamp)) withClassLoaderAndDispatchReporter(runpathList, reporterConfigurations, Some(graphicRerunReporter), None) { (loader, dispatchReporter) => { try { val filter = Filter(if (tagsToIncludeSet.isEmpty) None else Some(tagsToIncludeSet), tagsToExcludeSet) rerun(dispatchReporter, stopper, filter, propertiesMap, distributor, tracker, loader) } catch { case e: Throwable => { dispatchReporter.apply(RunAborted(tracker.nextOrdinal(), Resources.bigProblems(e), Some(e))) } } finally { stopper.reset() RunnerJFrame.this.done() nextRunStamp += 1 } } } } } } private[tools] object RunnerJFrame { def getUpperCaseName(event: Event): String = getUpperCaseName(eventToEventToPresent(event)) def getUpperCaseName(eventToPresent: EventToPresent) = eventToPresent match { case PresentRunStarting => "RUN_STARTING" case PresentTestStarting => "TEST_STARTING" case PresentTestFailed => "TEST_FAILED" case PresentTestSucceeded => "TEST_SUCCEEDED" case PresentTestIgnored => "TEST_IGNORED" case PresentTestPending => "TEST_PENDING" case PresentTestCanceled => "TEST_CANCELED" case PresentSuiteStarting => "SUITE_STARTING" case PresentSuiteAborted => "SUITE_ABORTED" case PresentSuiteCompleted => "SUITE_COMPLETED" case PresentInfoProvided => "INFO_PROVIDED" case PresentScopeOpened => "SCOPE_OPENED" case PresentScopeClosed => "SCOPE_CLOSED" case PresentMarkupProvided => "MARKUP_PROVIDED" // Shouldn't get here, because not registering these events in the GUI case PresentRunStopped => "RUN_STOPPED" case PresentRunAborted => "RUN_ABORTED" case PresentRunCompleted => "RUN_COMPLETED" } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy