Skip to content

Commit 79a09b7

Browse files
committed
metrics-testkit: use shared utilities
1 parent ad3a011 commit 79a09b7

File tree

2 files changed

+87
-235
lines changed

2 files changed

+87
-235
lines changed

oteljava/metrics-testkit/src/main/scala/org/typelevel/otel4s/oteljava/testkit/metrics/MetricExpectations.scala

Lines changed: 27 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package org.typelevel.otel4s.oteljava.testkit.metrics
1818

1919
import cats.data.NonEmptyList
2020
import io.opentelemetry.sdk.metrics.data.MetricData
21+
import org.typelevel.otel4s.oteljava.testkit.FlatExpectationMatching
2122

2223
/** Result of matching a [[MetricExpectation]] against a list of collected metrics. */
2324
sealed trait MetricMismatch {
@@ -138,22 +139,21 @@ object MetricExpectations {
138139
metrics: List[MetricData],
139140
expectation: MetricExpectation
140141
): Boolean =
141-
find(metrics, expectation).nonEmpty
142+
FlatExpectationMatching.exists(metrics, expectation)(_.matches(_))
142143

143144
/** Returns the first collected metric matching the expectation, if any. */
144145
def find(
145146
metrics: List[MetricData],
146147
expectation: MetricExpectation
147148
): Option[MetricData] =
148-
metrics.find(expectation.matches)
149+
FlatExpectationMatching.find(metrics, expectation)(_.matches(_))
149150

150151
/** Returns a mismatch if no collected metric matches the expectation. */
151152
def check(
152153
metrics: List[MetricData],
153154
expectation: MetricExpectation
154155
): Option[MetricMismatch] =
155-
if (exists(metrics, expectation)) None
156-
else Some(bestMismatch(metrics, expectation))
156+
FlatExpectationMatching.check(metrics, expectation)(_.matches(_), bestMismatch _)
157157

158158
/** Checks that every expectation matched at least one collected metric.
159159
*
@@ -175,7 +175,7 @@ object MetricExpectations {
175175
metrics: List[MetricData],
176176
expectations: List[MetricExpectation]
177177
): Either[NonEmptyList[MetricMismatch], Unit] =
178-
NonEmptyList.fromList(missing(metrics, expectations)).toLeft(())
178+
FlatExpectationMatching.checkAll(metrics, expectations)(_.matches(_), bestMismatch _)
179179

180180
/** Checks that every expectation matched a different collected metric.
181181
*
@@ -197,66 +197,56 @@ object MetricExpectations {
197197
metrics: List[MetricData],
198198
expectations: List[MetricExpectation]
199199
): Either[NonEmptyList[MetricMismatch], Unit] =
200-
NonEmptyList.fromList(missingDistinct(metrics, expectations)).toLeft(())
200+
FlatExpectationMatching.checkAllDistinct(metrics, expectations)(
201+
_.matches(_),
202+
bestMismatch _,
203+
MetricMismatch.distinctMatchUnavailable _,
204+
_.getName
205+
)
201206

202207
/** Returns mismatches for all expectations that did not match any collected metric. */
203208
def missing(
204209
metrics: List[MetricData],
205210
expectations: List[MetricExpectation]
206211
): List[MetricMismatch] =
207-
expectations.flatMap { expectation =>
208-
check(metrics, expectation)
209-
}
212+
FlatExpectationMatching.missing(metrics, expectations)(_.matches(_), bestMismatch _)
210213

211214
/** Returns mismatches for all expectations that could not be matched to distinct collected metrics. */
212215
def missingDistinct(
213216
metrics: List[MetricData],
214217
expectations: List[MetricExpectation]
215-
): List[MetricMismatch] = {
216-
val indexedMetrics = metrics.toVector
217-
val indexedExpectations = expectations.toVector
218-
val candidates = indexedExpectations.map { expectation =>
219-
indexedMetrics.indices.filter(index => expectation.matches(indexedMetrics(index))).toList
220-
}
221-
val matching = maximumMatching(candidates)
222-
223-
if (matching.isComplete) Nil
224-
else
225-
indexedExpectations.indices.collect {
226-
case index if !matching.matchedExpectationIndices(index) =>
227-
candidates(index) match {
228-
case Nil =>
229-
bestMismatch(metrics, indexedExpectations(index))
230-
case matches =>
231-
MetricMismatch.distinctMatchUnavailable(
232-
indexedExpectations(index),
233-
matches.map(indexedMetrics(_).getName).distinct
234-
)
235-
}
236-
}.toList
237-
}
218+
): List[MetricMismatch] =
219+
FlatExpectationMatching.missingDistinct(metrics, expectations)(
220+
_.matches(_),
221+
bestMismatch _,
222+
MetricMismatch.distinctMatchUnavailable _,
223+
_.getName
224+
)
238225

239226
/** Returns `true` if every expectation matched at least one collected metric. */
240227
def allMatch(
241228
metrics: List[MetricData],
242229
expectations: List[MetricExpectation]
243230
): Boolean =
244-
checkAll(metrics, expectations).isRight
231+
FlatExpectationMatching.allMatch(metrics, expectations)(_.matches(_), bestMismatch _)
245232

246233
/** Returns `true` if every expectation matched a different collected metric. */
247234
def allMatchDistinct(
248235
metrics: List[MetricData],
249236
expectations: List[MetricExpectation]
250237
): Boolean =
251-
checkAllDistinct(metrics, expectations).isRight
238+
FlatExpectationMatching.allMatchDistinct(metrics, expectations)(
239+
_.matches(_),
240+
bestMismatch _,
241+
MetricMismatch.distinctMatchUnavailable _,
242+
_.getName
243+
)
252244

253245
/** Formats mismatches into a multi-line human-readable failure message. */
254246
def format(
255247
mismatches: NonEmptyList[MetricMismatch]
256248
): String =
257-
mismatches.toList.zipWithIndex
258-
.map { case (mismatch, index) => s"${index + 1}. ${mismatch.message}" }
259-
.mkString("Metric expectations failed:\n", "\n", "")
249+
FlatExpectationMatching.format("Metric expectations", mismatches)(_.message)
260250

261251
private def bestMismatch(
262252
metrics: List[MetricData],
@@ -288,49 +278,4 @@ object MetricExpectations {
288278
}
289279
.getOrElse(MetricMismatch.notFound(expectation, metrics.map(_.getName)))
290280
}
291-
292-
private final case class MatchingResult(
293-
isComplete: Boolean,
294-
matchedExpectationIndices: Set[Int],
295-
size: Int
296-
)
297-
298-
private def maximumMatching(
299-
candidates: Vector[List[Int]]
300-
): MatchingResult = {
301-
type Matching = Map[Int, Int] // metricIndex -> expectationIndex
302-
303-
val orderedCandidates = candidates.zipWithIndex.sortBy(_._1.length)
304-
305-
def augment(
306-
expectationIndex: Int,
307-
seen: Set[Int],
308-
matching: Matching
309-
): Option[Matching] =
310-
orderedCandidates(expectationIndex)._1.foldLeft(Option.empty[Matching]) {
311-
case (result @ Some(_), _) =>
312-
result
313-
case (None, metricIndex) if seen(metricIndex) =>
314-
None
315-
case (None, metricIndex) =>
316-
matching.get(metricIndex) match {
317-
case None =>
318-
Some(matching.updated(metricIndex, expectationIndex))
319-
case Some(otherExpectationIndex) =>
320-
augment(otherExpectationIndex, seen + metricIndex, matching)
321-
.map(_.updated(metricIndex, expectationIndex))
322-
}
323-
}
324-
325-
val finalMatching =
326-
orderedCandidates.indices.foldLeft(Map.empty[Int, Int]) { case (matching, expectationIndex) =>
327-
augment(expectationIndex, Set.empty, matching).getOrElse(matching)
328-
}
329-
330-
MatchingResult(
331-
isComplete = finalMatching.size == candidates.length,
332-
matchedExpectationIndices = finalMatching.values.toSet,
333-
size = finalMatching.size
334-
)
335-
}
336281
}

0 commit comments

Comments
 (0)