aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/scala/org/perl8/test/tap/TestBuilder.scala
blob: 432f902b5d79953310bca1b6b8beba4c4163b310 (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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package org.perl8.test.tap

import org.perl8.test._

class TestBuilder private (
  plan:          Plan,
  indent:        String,
  terminalInUse: Boolean
) {
  plan match {
    case NoPlan => ()
    case p      => outLine(Producer.plan(p))
  }

  def this (plan: Plan = NoPlan, terminalInUse: Boolean = false) =
    this(plan, "", terminalInUse)

  def cloneForSubtest (newPlan: Plan): TestBuilder =
    new TestBuilder(newPlan, indent + "    ", terminalInUse)

  def ok (
    test:        Boolean,
    description: Message = NoMessage,
    todo:        Message = NoMessage
  ) {
    val line = Producer.result(test, state.currentTest, description, todo)
    state.ok(test || todo.isDefined)
    outLine(line)
  }

  def skip (reason: Message = NoMessage) {
    val line = Producer.skip(state.currentTest, reason)
    state.ok(true)
    outLine(line)
  }

  def diag (message: Message) {
    message.foreach { m =>
      errLine(Producer.comment(m))
    }
  }

  def note (message: Message) {
    message.foreach(m => outLine(Producer.comment(m)))
  }

  def bailOut (message: Message = NoMessage) {
    outLine(Producer.bailOut(message))
    throw new BailOutException(message.getOrElse(""))
  }

  def doneTesting: Boolean = {
    plan match {
      case NoPlan => outLine(Producer.plan(state.currentTest - 1))
      case _      => ()
    }

    if (!state.isPassing) {
      if (!state.matchesPlan) {
        val planCount = (plan match {
          case NoPlan  => state.currentTest - 1
          case p       => p.plan
        })
        val planned = planCount + " test" + (if (planCount > 1) "s" else "")
        val ran = state.currentTest - 1
        diag("Looks like you planned " + planned + " but ran " + ran + ".")
      }

      if (state.currentTest == 1) {
        diag("No tests run!")
      }

      if (state.failCount > 0) {
        val count = state.failCount
        val fails = count + " test" + (if (count > 1) "s" else "")
        val total =
          state.currentTest - 1 + (if (state.matchesPlan) "" else " run")
        diag("Looks like you failed " + fails + " of " + total + ".")
      }
    }

    state.isPassing
  }

  def failedTests: Int =
    state.failCount

  def exitCode: Int =
    if (state.isPassing) {
      0
    }
    else if (!state.matchesPlan) {
      255
    }
    else {
      state.failCount
    }

  private val state = new TestState

  private def outLine (str: String) {
    Console.out.println(withIndent(str))
  }

  private def errLine (str: String) {
    if (terminalInUse) {
      Console.err.print("\n")
    }
    Console.err.println(withIndent(str))
  }

  private def withIndent (str: String): String =
    str.split("\n").map(s => indent + s).mkString("\n")

  private class TestState {
    var passCount = 0
    var failCount = 0

    def ok (cond: Boolean) {
      if (cond) {
        passCount += 1
      }
      else {
        failCount += 1
      }
    }

    def currentTest: Int =
      failCount + passCount + 1

    def matchesPlan: Boolean = plan match {
      case NumericPlan(p) => p.plan == failCount + passCount
      case _              => true
    }

    def isPassing: Boolean =
      currentTest > 1 && failCount == 0 && matchesPlan
  }
}