Skip to content

Commit 9c52cde

Browse files
author
Tom Verran
committed
Move to MUnit
1 parent 0c0b19b commit 9c52cde

File tree

13 files changed

+547
-661
lines changed

13 files changed

+547
-661
lines changed

build.sbt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ val common = Seq(
4646
compilerPlugin("org.typelevel" % "kind-projector" % "0.13.2" cross CrossVersion.full),
4747
"org.typelevel" %% "cats-core" % "2.6.1",
4848
"org.typelevel" %% "cats-effect" % "3.2.9",
49-
"org.scalatest" %% "scalatest" % "3.2.10" % Test,
49+
"org.scalameta" %% "munit" % "0.7.29" % Test,
5050
"org.scalacheck" %% "scalacheck" % "1.15.4" % Test,
51-
"org.scalatestplus" %% "scalacheck-1-14" % "3.2.2.0" % Test
51+
"org.typelevel" %% "munit-cats-effect-3" % "1.0.7" % Test,
52+
"org.typelevel" %% "scalacheck-effect-munit" % "1.0.3" % Test
5253
)
5354
)
5455

@@ -209,7 +210,6 @@ lazy val datadogMetrics = project
209210
.dependsOn(metricsCommon)
210211
.settings(
211212
libraryDependencies ++= Seq(
212-
"org.typelevel" %% "claimant" % "0.2.0" % Test,
213213
"co.fs2" %% "fs2-core" % fs2Version,
214214
"co.fs2" %% "fs2-io" % fs2Version,
215215
)
Lines changed: 105 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,146 @@
11
package com.ovoenergy.natchez.extras.datadog
22

33
import cats.effect._
4-
import cats.effect.unsafe.implicits._
54
import cats.instances.list._
65
import cats.syntax.traverse._
76
import com.ovoenergy.natchez.extras.datadog.Datadog.entryPoint
87
import com.ovoenergy.natchez.extras.datadog.DatadogTags.SpanType.{Cache, Db, Web}
98
import com.ovoenergy.natchez.extras.datadog.DatadogTags.spanType
9+
import munit.CatsEffectSuite
1010
import natchez.EntryPoint
1111
import org.http4s.Request
1212
import org.http4s.circe.CirceEntityDecoder._
1313
import org.http4s.syntax.literals._
14-
import org.scalatest.{Inspectors, OptionValues}
15-
import org.scalatest.matchers.should.Matchers
16-
import org.scalatest.wordspec.AnyWordSpec
1714

1815
import scala.concurrent.duration._
1916

2017
/**
2118
* This tests both the datadog span code itself and the submission of metrics over HTTP
2219
* Could be expanded but even just these tests exposed concurrency issues with my original code.
2320
*/
24-
class DatadogTest extends AnyWordSpec with Matchers with OptionValues {
21+
class DatadogTest extends CatsEffectSuite {
2522

2623
def run(f: EntryPoint[IO] => IO[Unit]): IO[List[Request[IO]]] =
2724
TestClient[IO].flatMap(c => entryPoint(c.client, "test", "blah").use(f) >> c.requests)
2825

29-
"Datadog span" should {
30-
31-
"Obtain the agent host from the parameter" in {
32-
(
33-
for {
34-
client <- TestClient[IO]
35-
ep = entryPoint(client.client, "a", "b", agentHost = uri"http://example.com")
36-
_ <- ep.use(_.root("foo").use(_ => IO.unit))
37-
requests <- client.requests
38-
} yield requests.map(_.uri) shouldBe List(
39-
uri"http://example.com/v0.3/traces"
40-
)
41-
).unsafeRunSync()
42-
}
43-
44-
"Allow you to modify trace tokens" in {
45-
(
46-
for {
47-
client <- TestClient[IO]
48-
ep = entryPoint(client.client, "a", "b", agentHost = uri"http://example.com")
49-
kernel <- ep.use(_.root("foo").use(s => s.put("traceToken" -> "foo") >> s.kernel))
50-
} yield kernel.toHeaders.get("X-Trace-Token") shouldBe Some("foo")
51-
).unsafeRunSync()
52-
}
53-
54-
"Continue to send HTTP calls even if one of them fails" in {
55-
56-
val test: EntryPoint[IO] => IO[Unit] =
57-
ep =>
58-
ep.root("first").use(_ => IO.unit) >>
59-
IO.sleep(1.second) >>
60-
ep.root("second").use(_ => IO.unit)
61-
(
62-
for {
63-
client <- TestClient[IO]
64-
_ <- client.respondWith(IO.raiseError(new Exception))
65-
ep = entryPoint(client.client, "a", "b", agentHost = uri"http://example.com")
66-
_ <- ep.use(test)
67-
requests <- client.requests
68-
} yield requests.length shouldBe 2
69-
).unsafeRunSync()
70-
}
26+
test("Obtain the agent host from the parameter") {
27+
assertIO(
28+
returns = List(uri"http://example.com/v0.3/traces"),
29+
obtained = for {
30+
client <- TestClient[IO]
31+
ep = entryPoint(client.client, "a", "b", agentHost = uri"http://example.com")
32+
_ <- ep.use(_.root("foo").use(_ => IO.unit))
33+
requests <- client.requests
34+
} yield requests.map(_.uri)
35+
)
36+
}
7137

72-
"Submit the right info to Datadog when closed" in {
38+
test("Allow you to modify trace tokens") {
39+
assertIO(
40+
returns = Some("foo"),
41+
obtained = for {
42+
client <- TestClient[IO]
43+
ep = entryPoint(client.client, "a", "b", agentHost = uri"http://example.com")
44+
kernel <- ep.use(_.root("foo").use(s => s.put("traceToken" -> "foo") >> s.kernel))
45+
} yield kernel.toHeaders.get("X-Trace-Token")
46+
)
47+
}
7348

74-
val res = run(_.root("bar:res").use(_.put("k" -> "v") >> IO.sleep(1.milli))).unsafeRunSync()
75-
val span = res.flatTraverse(_.as[List[List[SubmittableSpan]]]).unsafeRunSync().flatten.head
49+
test("Continue to send HTTP calls even if one of them fails") {
50+
51+
val test: EntryPoint[IO] => IO[Unit] =
52+
ep =>
53+
ep.root("first").use(_ => IO.unit) >>
54+
IO.sleep(1.second) >>
55+
ep.root("second").use(_ => IO.unit)
56+
57+
assertIO(
58+
returns = 2,
59+
obtained = for {
60+
client <- TestClient[IO]
61+
_ <- client.respondWith(IO.raiseError(new Exception))
62+
ep = entryPoint(client.client, "a", "b", agentHost = uri"http://example.com")
63+
_ <- ep.use(test)
64+
requests <- client.requests
65+
} yield requests.length
66+
)
67+
}
7668

77-
span.name shouldBe "bar"
78-
span.service shouldBe "test"
79-
span.resource shouldBe "res"
80-
span.`type` shouldBe None
81-
span.duration > 0 shouldBe true
82-
span.meta.get("k") shouldBe Some("v")
83-
span.meta.contains("traceToken") shouldBe true
69+
test("Submit the right info to Datadog when closed") {
70+
for {
71+
res <- run(_.root("bar:res").use(_.put("k" -> "v") >> IO.sleep(1.milli)))
72+
spans <- res.flatTraverse(_.as[List[List[SubmittableSpan]]])
73+
} yield {
74+
val span = spans.flatten.head
75+
assertEquals(span.name, "bar")
76+
assertEquals(span.service, "test")
77+
assertEquals(span.resource, "res")
78+
assertEquals(span.`type`, None)
79+
assertEquals(span.duration > 0, true)
80+
assertEquals(span.meta.get("k"), Some("v"))
81+
assertEquals(span.meta.contains("traceToken"), true)
8482
}
83+
}
8584

86-
"Only include the sampling priority metric on the root span" in {
87-
val res = run(_.root("root").use(_.span("span").use(_ => IO.sleep(1.milli)))).unsafeRunSync()
88-
val spans = res.flatTraverse(_.as[List[List[SubmittableSpan]]]).unsafeRunSync().flatten
89-
spans.find(_.parentId.isEmpty).value.metrics shouldBe Map("_sampling_priority_v1" -> 2.0)
90-
spans.find(_.parentId.isDefined).value.metrics shouldBe Map.empty
85+
test("Only include the sampling priority metric on the root span") {
86+
for {
87+
res <- run(_.root("root").use(_.span("span").use(_ => IO.sleep(1.milli))))
88+
spans <- res.flatTraverse(_.as[List[List[SubmittableSpan]]]).map(_.flatten)
89+
} yield {
90+
assertEquals(spans.find(_.parentId.isEmpty).map(_.metrics), Some(Map("_sampling_priority_v1" -> 2.0)))
91+
assertEquals(spans.find(_.parentId.isDefined).map(_.metrics), Some(Map.empty[String, Double]))
9192
}
93+
}
9294

93-
"Infer the right span.type from any tags set" in {
94-
Inspectors.forAll(List(Web, Cache, Db)) { typ =>
95-
val res = run(_.root("bar:res").use(_.put(spanType(typ)))).unsafeRunSync()
96-
val span = res.flatTraverse(_.as[List[List[SubmittableSpan]]]).unsafeRunSync().flatten.head
97-
span.`type` shouldBe Some(typ)
98-
}
95+
test("Infer the right span.type from any tags set") {
96+
List(Web, Cache, Db).traverse { typ =>
97+
assertIO(
98+
returns = Some(typ),
99+
obtained = for {
100+
res <- run(_.root("bar:res").use(_.put(spanType(typ))))
101+
span <- res.flatTraverse(_.as[List[List[SubmittableSpan]]])
102+
} yield span.flatten.head.`type`
103+
)
99104
}
105+
}
100106

101-
"Submit multiple spans across multiple calls when span() is called" in {
102-
val res = run(_.root("bar").use(_.span("subspan").use(_ => IO.sleep(1.second)))).unsafeRunSync()
103-
val spans = res.flatTraverse(_.as[List[List[SubmittableSpan]]]).unsafeRunSync().flatten
104-
spans.map(_.traceId).distinct.length shouldBe 1
105-
spans.map(_.spanId).distinct.length shouldBe 2
107+
test("Submit multiple spans across multiple calls when span() is called") {
108+
for {
109+
res <- run(_.root("bar").use(_.span("subspan").use(_ => IO.sleep(1.second))))
110+
spans <- res.flatTraverse(_.as[List[List[SubmittableSpan]]]).map(_.flatten)
111+
} yield {
112+
assertEquals(spans.map(_.traceId).distinct.length, 1)
113+
assertEquals(spans.map(_.spanId).distinct.length, 2)
106114
}
115+
}
107116

108-
"Allow you to override the service name and resource with colons" in {
109-
val res = run(_.root("svc:name:res").use(_ => IO.unit)).unsafeRunSync()
110-
val spans = res.flatTraverse(_.as[List[List[SubmittableSpan]]]).unsafeRunSync().flatten
111-
spans.head.resource shouldBe "res"
112-
spans.head.service shouldBe "svc"
113-
spans.head.name shouldBe "name"
117+
test("Allow you to override the service name and resource with colons") {
118+
for {
119+
res <- run(_.root("svc:name:res").use(_ => IO.unit))
120+
spans <- res.flatTraverse(_.as[List[List[SubmittableSpan]]]).map(_.flatten)
121+
} yield {
122+
assertEquals(spans.head.resource, "res")
123+
assertEquals(spans.head.service, "svc")
124+
assertEquals(spans.head.name, "name")
114125
}
126+
}
115127

116-
"Inherit metadata into subspans but only at the time of creation" in {
117-
val res = run(
118-
_.root("bar:res").use { root =>
119-
root.put("foo" -> "bar") >> root.span("sub").use(_.put("baz" -> "qux"))
120-
}
121-
).unsafeRunSync()
122-
123-
val spans = res.flatTraverse(_.as[List[List[SubmittableSpan]]]).unsafeRunSync().flatten
128+
test("Inherit metadata into subspans but only at the time of creation") {
129+
run(
130+
_.root("bar:res").use { root =>
131+
root.put("foo" -> "bar") >> root.span("sub").use(_.put("baz" -> "qux"))
132+
}
133+
).flatMap(_.flatTraverse(_.as[List[List[SubmittableSpan]]]).map(_.flatten)).map { spans =>
124134
val rootSpan = spans.find(_.name == "bar").get
125135
val subSpan = spans.find(_.name == "sub").get
126-
127-
subSpan.meta.view.filterKeys(_ != "traceToken").toMap shouldBe Map("foo" -> "bar", "baz" -> "qux")
128-
rootSpan.meta.view.filterKeys(_ != "traceToken").toMap shouldBe Map("foo" -> "bar")
136+
assertEquals(
137+
subSpan.meta.view.filterKeys(_ != "traceToken").toMap,
138+
Map("foo" -> "bar", "baz" -> "qux")
139+
)
140+
assertEquals(
141+
rootSpan.meta.view.filterKeys(_ != "traceToken").toMap,
142+
Map("foo" -> "bar")
143+
)
129144
}
130145
}
131146
}
Lines changed: 45 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,66 @@
11
package com.ovoenergy.natchez.extras.datadog
22

33
import cats.effect.IO
4-
import cats.effect.unsafe.implicits.global
54
import com.ovoenergy.natchez.extras.datadog.SpanIdentifiers._
65
import com.ovoenergy.natchez.extras.datadog.data.UnsignedLong
6+
import munit.CatsEffectSuite
77
import natchez.Kernel
8-
import org.scalatest.matchers.should.Matchers
9-
import org.scalatest.wordspec.AnyWordSpec
10-
import org.scalatest.{EitherValues, OptionValues}
11-
import org.scalatestplus.scalacheck.Checkers
128

13-
class SpanIdentifiersTest
14-
extends AnyWordSpec
15-
with Matchers
16-
with Checkers
17-
with OptionValues
18-
with EitherValues {
9+
class SpanIdentifiersTest extends CatsEffectSuite {
1910

20-
"Span identifiers" should {
21-
22-
"Set IDs correctly when creating child IDs" in {
23-
24-
val (parent, child) = (
25-
for {
26-
parent <- SpanIdentifiers.create[IO]
27-
child <- SpanIdentifiers.child[IO](parent)
28-
} yield parent -> child
29-
).unsafeRunSync()
30-
31-
parent.parentId shouldBe None
32-
child.traceId shouldBe parent.traceId
33-
child.parentId shouldBe Some(parent.spanId)
34-
child.spanId should not be parent.spanId
11+
test("Span identifiers should set IDs correctly when creating child IDs") {
12+
for {
13+
parent <- SpanIdentifiers.create[IO]
14+
child <- SpanIdentifiers.child[IO](parent)
15+
} yield {
16+
assertEquals(parent.parentId, None)
17+
assertEquals(child.traceId, parent.traceId)
18+
assertEquals(child.parentId, Some(parent.spanId))
19+
assertNotEquals(child.spanId, parent.spanId)
3520
}
21+
}
3622

37-
"Convert to and from a kernel losslessly" in {
38-
val (original, kernel) = (
39-
for {
40-
ids <- create[IO]
41-
kernel <- fromKernel[IO](SpanIdentifiers.toKernel(ids))
42-
} yield ids -> kernel
43-
).unsafeRunSync()
44-
45-
kernel.traceId shouldBe original.traceId
46-
kernel.traceToken shouldBe original.traceToken
47-
kernel.parentId shouldBe Some(original.spanId)
23+
test("Span identifiers should convert to and from a kernel losslessly") {
24+
for {
25+
original <- create[IO]
26+
kernel <- fromKernel[IO](SpanIdentifiers.toKernel(original))
27+
} yield {
28+
assertEquals(kernel.traceId, original.traceId)
29+
assertEquals(kernel.traceToken, original.traceToken)
30+
assertEquals(kernel.parentId, Some(original.spanId))
4831
}
4932
}
5033

51-
"Succeed in converting from a kernel even if info is missing" in {
52-
fromKernel[IO](Kernel(Map.empty)).attempt.unsafeRunSync() should matchPattern { case Right(_) => }
53-
fromKernel[IO](Kernel(Map("X-Trace-Token" -> "foo"))).unsafeRunSync().traceToken shouldBe "foo"
34+
test("fromKernel should succeed in converting from a kernel even if info is missing") {
35+
assertIOBoolean(fromKernel[IO](Kernel(Map.empty)).attempt.map(_.isRight))
36+
assertIO(fromKernel[IO](Kernel(Map("X-Trace-Token" -> "foo"))).map(_.traceToken), "foo")
5437
}
5538

56-
"Ignore header case when extracting info" in {
57-
fromKernel[IO](Kernel(Map("x-TRACe-tokeN" -> "foo"))).unsafeRunSync().traceToken shouldBe "foo"
39+
test("fromKernel should ignore header case when extracting info") {
40+
assertIO(fromKernel[IO](Kernel(Map("x-TRACe-tokeN" -> "foo"))).map(_.traceToken), "foo")
5841
}
5942

60-
"Output hex-encoded B3 Trace IDs alongside decimal encoded Datadog IDs" in {
61-
val result = SpanIdentifiers.create[IO].map(SpanIdentifiers.toKernel).unsafeRunSync()
62-
val ddSpanId: String = result.toHeaders.get("X-Trace-Id").value
63-
val b3SpanId: String = result.toHeaders.get("X-B3-Trace-Id").value
64-
val ddULong = UnsignedLong.fromString(ddSpanId, 10)
65-
val b3ULong = UnsignedLong.fromString(b3SpanId, 16)
66-
ddULong shouldBe b3ULong
43+
test("toKernel should output hex-encoded B3 Trace IDs alongside decimal encoded Datadog IDs") {
44+
for {
45+
ids <- SpanIdentifiers.create[IO].map(SpanIdentifiers.toKernel)
46+
ddSpanId <- IO.fromOption(ids.toHeaders.get("X-Trace-Id"))(new Exception("Missing X-Trace-Id"))
47+
b3SpanId <- IO.fromOption(ids.toHeaders.get("X-B3-Trace-Id"))(new Exception("Missing X-B3-Trace-Id"))
48+
} yield {
49+
val ddULong = UnsignedLong.fromString(ddSpanId, 10)
50+
val b3ULong = UnsignedLong.fromString(b3SpanId, 16)
51+
assertEquals(ddULong, b3ULong)
52+
}
6753
}
6854

69-
"Output hex-encoded B3 Span IDs alongside decimal encoded Datadog Parent IDs" in {
70-
val result = SpanIdentifiers.create[IO].map(SpanIdentifiers.toKernel).unsafeRunSync()
71-
val ddSpanId: String = result.toHeaders.get("X-Parent-Id").value
72-
val b3SpanId: String = result.toHeaders.get("X-B3-Span-Id").value
73-
val ddULong = UnsignedLong.fromString(ddSpanId, 10)
74-
val b3ULong = UnsignedLong.fromString(b3SpanId, 16)
75-
ddULong shouldBe b3ULong
55+
test("toKernel should output hex-encoded B3 Span IDs alongside decimal encoded Datadog Parent IDs") {
56+
for {
57+
ids <- SpanIdentifiers.create[IO].map(SpanIdentifiers.toKernel)
58+
ddSpanId <- IO.fromOption(ids.toHeaders.get("X-Parent-Id"))(new Exception("Missing X-Parent-Id"))
59+
b3SpanId <- IO.fromOption(ids.toHeaders.get("X-B3-Span-Id"))(new Exception("Missing X-B3-Span-Id"))
60+
} yield {
61+
val ddULong = UnsignedLong.fromString(ddSpanId, 10)
62+
val b3ULong = UnsignedLong.fromString(b3SpanId, 16)
63+
assertEquals(ddULong, b3ULong)
64+
}
7665
}
7766
}

0 commit comments

Comments
 (0)