summaryrefslogtreecommitdiff
path: root/contrib/bsp/src/mill/contrib/bsp/BspTestReporter.scala
blob: 05e58ec7254497d4e4ba1e1b4eb4c2166cce7caa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package mill.contrib.bsp

import java.io.{PrintWriter, StringWriter}

import ch.epfl.scala.bsp4j._
import mill.api.BspContext
import sbt.testing._


/**
  * Context class for BSP, specialized for sending `task-start` and
  * `task-finish` notifications for every test being ran.
  * @param client The client to send notifications to
  * @param targetId The targetId of the BSP target for which
  *                 the test request is being processed
  * @param taskId   The unique taskId associated with the
  *                 test task that will trigger this reporter
  *                 to log testing events.
  * @param arguments compilation arguments as part of the BSP context,
  *                  in case special arguments need to be passed to
  *                  the compiler before running the test task.
  */
class BspTestReporter(
                       client: BuildClient,
                       targetId: BuildTargetIdentifier,
                       taskId: TaskId,
                       arguments: Seq[String]) extends BspContext {

  var passed = 0
  var failed = 0
  var cancelled = 0
  var ignored = 0
  var skipped = 0
  var totalTime: Long = 0

  override def args: Seq[String] = arguments

  override def logStart(event: Event): Unit = {
    val taskStartParams = new TaskStartParams(taskId)
    taskStartParams.setEventTime(System.currentTimeMillis())
    taskStartParams.setDataKind(TaskDataKind.TEST_START)
    taskStartParams.setData(new TestStart(getDisplayName(event)))
    taskStartParams.setMessage("Starting running: " + getDisplayName(event))
    client.onBuildTaskStart(taskStartParams)
  }

  override def logFinish(event: Event): Unit = {
    totalTime += event.duration()
    val taskFinishParams = new TaskFinishParams(taskId,
      event.status()  match {
        case sbt.testing.Status.Canceled => StatusCode.CANCELLED
        case sbt.testing.Status.Error => StatusCode.ERROR
        case default => StatusCode.OK
      })
    val status = event.status match {
      case sbt.testing.Status.Success =>
        passed += 1
        TestStatus.PASSED
      case sbt.testing.Status.Canceled =>
        cancelled += 1
        TestStatus.CANCELLED
      case sbt.testing.Status.Error =>
        failed += 1
        TestStatus.FAILED
      case sbt.testing.Status.Failure =>
        failed += 1
        TestStatus.FAILED
      case sbt.testing.Status.Ignored =>
        ignored += 1
        TestStatus.IGNORED
      case sbt.testing.Status.Skipped =>
        skipped += 1
        TestStatus.SKIPPED
      case sbt.testing.Status.Pending =>
        skipped += 1
        TestStatus.SKIPPED //TODO: what to do here
    }

    taskFinishParams.setDataKind(TaskDataKind.TEST_FINISH)
    taskFinishParams.setData({
      val testFinish = new TestFinish(getDisplayName(event), status)
      if (event.throwable.isDefined)
        testFinish.setMessage(throwableToString(event.throwable().get()))
      testFinish
    })
    taskFinishParams.setEventTime(System.currentTimeMillis())
    client.onBuildTaskFinish(taskFinishParams)
  }

  private def throwableToString(t: Throwable): String = {
    val sw = new StringWriter
    val pw = new PrintWriter(sw)
    t.printStackTrace(pw)
    sw.toString
  }

  // Compute the display name of the test / test suite
  // to which the given event relates
  private[this] def getDisplayName(e: Event): String = {
    e.selector() match{
      case s: NestedSuiteSelector => s.suiteId()
      case s: NestedTestSelector => s.suiteId() + "." + s.testName()
      case s: SuiteSelector => s.toString
      case s: TestSelector => s.testName()
      case s: TestWildcardSelector => s.testWildcard()
    }
  }

  // Compute the test report data structure that will go into
  // the task finish notification after all tests are ran.
  def getTestReport: TestReport = {
    val report = new TestReport(targetId, passed, failed, ignored, cancelled, skipped)
    report.setTime(totalTime)
    report
  }

}

case class TestException(stackTrace: String, message: String, exClass: String)