/
HasTimeline.scala
133 lines (113 loc) · 4.11 KB
/
HasTimeline.scala
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
package riff.raft.integration.simulator
import scala.reflect.ClassTag
/**
* Adds some functionality for anything which has a 'currentTimeline'
*
* @tparam A
*/
trait HasTimeline[A] {
def currentTimeline(): Timeline[A]
def diff(other: Timeline[A]): List[(Long, A)] = currentTimeline.sortedEventsAscending.diff(other.sortedEventsAscending)
def size() = events.size
def events: List[(Long, A)] = currentTimeline.sortedEventsAscending
def timelineValues(): List[A] = events.map(_._2)
def lastTimeMatching(predicate: PartialFunction[A, Boolean]): Option[Long] = {
events.reverse.collectFirst {
case (time, a) if predicate.isDefinedAt(a) && predicate(a) => time
}
}
/** A convenience method for dumping the limeline as an expectation.
* Useful for stepping through test scenarios and adding the results as an assertion
*
* @param ev
* @tparam T
* @return the current timeline as a String which can be pasted
*/
def timelineAsExpectation[T](prefix: String = "simulator.timelineAssertions shouldBe ")(implicit ev: A =:= TimelineType) = {
val quote = "\""
val code = timelineAssertions(ev).mkString(s"$prefix List(\n $quote", "\",\n \"", "\"\n)")
s"""
|withClue(simulator.timelineAsExpectation()) {
| $code
|}
""".stripMargin
}
def timelineAssertions[T](implicit ev: A =:= TimelineType) = {
timelineValues().map { x => ev(x).asAssertion()
}
}
def findOnly[T <: A: ClassTag]: (Long, T) = {
val List(only) = findAll[T]
only
}
def findAll[T <: A: ClassTag]: List[(Long, T)] = events.collect {
case (time, tea: T) => (time, tea)
}
def pretty(indent: String = "", previousTimeline : Option[Timeline[A]] = None, includeHistory : Boolean = false): String = {
val currentTime: Long = currentTimeline.currentTime
val eventStrings = {
previousTimeline match {
case Some(before) =>
val beforeStrings = before.formatEventsAsStrings(includeHistory, currentTime)
// this is naive (and wrong), but easy ... to work out what changed,
// just see if the string was in the previous timeline
val beforeEvents: Set[String] = beforeStrings.map(_._2).toSet
val strings = formatEventsAsStrings(includeHistory, currentTime)
strings.map {
case (time, str) if !beforeEvents(str) => (time , s"* $str")
case unchanged => unchanged
}
case None =>
formatEventsAsStrings(includeHistory, currentTime)
}
}
eventStrings.map {
case (time, str) => s"$time : $str"
}.mkString(indent, s"\n$indent", "\n")
}
/**
*
* @param currentTime
* @return
*/
def formatEventsAsStrings(includeHistory : Boolean, currentTime: Long): List[(String, String)] = {
val timeline = currentTimeline
val (pastDeleted, futureDeleted) = {
val deleted = timeline.removed.sortBy(_._1).map {
case (time, event) => (time, s"(removed) $event")
}
deleted.partition(_._1 < currentTime)
}
implicit val ord: Ordering[(Long, Any)] = Ordering.by[(Long, Any), Long](_._1)
val hist = if (includeHistory) {
MergeSorted(currentTimeline.historyDescending.reverse, pastDeleted)
} else {
Nil
}
val future = MergeSorted(timeline.sortedEventsAscending, futureDeleted)
val all = (hist ::: future).map {
case (time, event) =>
val sign = if (time < currentTime) "-" else if (time > currentTime) "+" else " "
val diff = (currentTime - time).abs
s"$sign${diff}ms" -> event.toString
}
if (all.isEmpty) {
(s"empty timeline @ $currentTime", "") :: Nil
} else {
val padded = {
val padSize = all.map(_._1.length).max
all.map {
case (time, event) => (s"${time.padTo(padSize, ' ')}", event.toString)
}
}
padded
}
}
def wasRemoved(predicate: PartialFunction[A, Boolean]): Boolean = {
// format: off
currentTimeline().removed.exists {
case (_, a) => predicate.isDefinedAt(a) && predicate(a)
}
// format: on
}
}