@@ -18,6 +18,7 @@ package org.typelevel.otel4s.oteljava.testkit.metrics
1818
1919import cats .data .NonEmptyList
2020import 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. */
2324sealed 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