trace_test.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. // Copyright The OpenTelemetry Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package trace
  15. import (
  16. "bytes"
  17. "context"
  18. "testing"
  19. "github.com/google/go-cmp/cmp"
  20. "github.com/stretchr/testify/assert"
  21. "go.opentelemetry.io/otel/attribute"
  22. )
  23. func TestSpanContextIsValid(t *testing.T) {
  24. for _, testcase := range []struct {
  25. name string
  26. tid TraceID
  27. sid SpanID
  28. want bool
  29. }{
  30. {
  31. name: "SpanContext.IsValid() returns true if sc has both an Trace ID and Span ID",
  32. tid: [16]byte{1},
  33. sid: [8]byte{42},
  34. want: true,
  35. }, {
  36. name: "SpanContext.IsValid() returns false if sc has neither an Trace ID nor Span ID",
  37. tid: TraceID([16]byte{}),
  38. sid: [8]byte{},
  39. want: false,
  40. }, {
  41. name: "SpanContext.IsValid() returns false if sc has a Span ID but not a Trace ID",
  42. tid: TraceID([16]byte{}),
  43. sid: [8]byte{42},
  44. want: false,
  45. }, {
  46. name: "SpanContext.IsValid() returns false if sc has a Trace ID but not a Span ID",
  47. tid: TraceID([16]byte{1}),
  48. sid: [8]byte{},
  49. want: false,
  50. },
  51. } {
  52. t.Run(testcase.name, func(t *testing.T) {
  53. sc := SpanContext{
  54. traceID: testcase.tid,
  55. spanID: testcase.sid,
  56. }
  57. have := sc.IsValid()
  58. if have != testcase.want {
  59. t.Errorf("Want: %v, but have: %v", testcase.want, have)
  60. }
  61. })
  62. }
  63. }
  64. func TestSpanContextEqual(t *testing.T) {
  65. a := SpanContext{
  66. traceID: [16]byte{1},
  67. spanID: [8]byte{42},
  68. }
  69. b := SpanContext{
  70. traceID: [16]byte{1},
  71. spanID: [8]byte{42},
  72. }
  73. c := SpanContext{
  74. traceID: [16]byte{2},
  75. spanID: [8]byte{42},
  76. }
  77. if !a.Equal(b) {
  78. t.Error("Want: true, but have: false")
  79. }
  80. if a.Equal(c) {
  81. t.Error("Want: false, but have: true")
  82. }
  83. }
  84. func TestSpanContextIsSampled(t *testing.T) {
  85. for _, testcase := range []struct {
  86. name string
  87. tf TraceFlags
  88. want bool
  89. }{
  90. {
  91. name: "SpanContext.IsSampled() returns false if sc is not sampled",
  92. want: false,
  93. }, {
  94. name: "SpanContext.IsSampled() returns true if sc is sampled",
  95. tf: FlagsSampled,
  96. want: true,
  97. },
  98. } {
  99. t.Run(testcase.name, func(t *testing.T) {
  100. sc := SpanContext{
  101. traceFlags: testcase.tf,
  102. }
  103. have := sc.IsSampled()
  104. if have != testcase.want {
  105. t.Errorf("Want: %v, but have: %v", testcase.want, have)
  106. }
  107. })
  108. }
  109. }
  110. func TestSpanContextIsRemote(t *testing.T) {
  111. for _, testcase := range []struct {
  112. name string
  113. remote bool
  114. want bool
  115. }{
  116. {
  117. name: "SpanContext.IsRemote() returns false if sc is not remote",
  118. want: false,
  119. }, {
  120. name: "SpanContext.IsRemote() returns true if sc is remote",
  121. remote: true,
  122. want: true,
  123. },
  124. } {
  125. t.Run(testcase.name, func(t *testing.T) {
  126. sc := SpanContext{
  127. remote: testcase.remote,
  128. }
  129. have := sc.IsRemote()
  130. if have != testcase.want {
  131. t.Errorf("Want: %v, but have: %v", testcase.want, have)
  132. }
  133. })
  134. }
  135. }
  136. func TestSpanContextMarshalJSON(t *testing.T) {
  137. for _, testcase := range []struct {
  138. name string
  139. tid TraceID
  140. sid SpanID
  141. tstate TraceState
  142. tflags TraceFlags
  143. isRemote bool
  144. want []byte
  145. }{
  146. {
  147. name: "SpanContext.MarshalJSON() returns json with partial data",
  148. tid: [16]byte{1},
  149. sid: [8]byte{42},
  150. want: []byte(`{"TraceID":"01000000000000000000000000000000","SpanID":"2a00000000000000","TraceFlags":"00","TraceState":"","Remote":false}`),
  151. },
  152. {
  153. name: "SpanContext.MarshalJSON() returns json with full data",
  154. tid: [16]byte{1},
  155. sid: [8]byte{42},
  156. tflags: FlagsSampled,
  157. isRemote: true,
  158. tstate: TraceState{list: []member{
  159. {Key: "foo", Value: "1"},
  160. }},
  161. want: []byte(`{"TraceID":"01000000000000000000000000000000","SpanID":"2a00000000000000","TraceFlags":"01","TraceState":"foo=1","Remote":true}`),
  162. },
  163. } {
  164. t.Run(testcase.name, func(t *testing.T) {
  165. sc := SpanContext{
  166. traceID: testcase.tid,
  167. spanID: testcase.sid,
  168. traceFlags: testcase.tflags,
  169. traceState: testcase.tstate,
  170. remote: testcase.isRemote,
  171. }
  172. have, err := sc.MarshalJSON()
  173. if err != nil {
  174. t.Errorf("Marshaling failed: %v", err)
  175. }
  176. if !bytes.Equal(have, testcase.want) {
  177. t.Errorf("Want: %v, but have: %v", string(testcase.want), string(have))
  178. }
  179. })
  180. }
  181. }
  182. func TestSpanIDFromHex(t *testing.T) {
  183. for _, testcase := range []struct {
  184. name string
  185. hex string
  186. sid SpanID
  187. valid bool
  188. }{
  189. {
  190. name: "Valid SpanID",
  191. sid: SpanID([8]byte{42}),
  192. hex: "2a00000000000000",
  193. valid: true,
  194. }, {
  195. name: "Invalid SpanID with invalid length",
  196. hex: "80f198ee56343ba",
  197. valid: false,
  198. }, {
  199. name: "Invalid SpanID with invalid char",
  200. hex: "80f198ee563433g7",
  201. valid: false,
  202. }, {
  203. name: "Invalid SpanID with uppercase",
  204. hex: "80f198ee53ba86F7",
  205. valid: false,
  206. }, {
  207. name: "Invalid SpanID with zero value",
  208. hex: "0000000000000000",
  209. valid: false,
  210. },
  211. } {
  212. t.Run(testcase.name, func(t *testing.T) {
  213. sid, err := SpanIDFromHex(testcase.hex)
  214. if testcase.valid && err != nil {
  215. t.Errorf("Expected SpanID %s to be valid but end with error %s", testcase.hex, err.Error())
  216. } else if !testcase.valid && err == nil {
  217. t.Errorf("Expected SpanID %s to be invalid but end no error", testcase.hex)
  218. }
  219. if sid != testcase.sid {
  220. t.Errorf("Want: %v, but have: %v", testcase.sid, sid)
  221. }
  222. })
  223. }
  224. }
  225. func TestIsValidFromHex(t *testing.T) {
  226. for _, testcase := range []struct {
  227. name string
  228. hex string
  229. tid TraceID
  230. valid bool
  231. }{
  232. {
  233. name: "Valid TraceID",
  234. tid: TraceID([16]byte{128, 241, 152, 238, 86, 52, 59, 168, 100, 254, 139, 42, 87, 211, 239, 247}),
  235. hex: "80f198ee56343ba864fe8b2a57d3eff7",
  236. valid: true,
  237. }, {
  238. name: "Invalid TraceID with invalid length",
  239. hex: "80f198ee56343ba864fe8b2a57d3eff",
  240. valid: false,
  241. }, {
  242. name: "Invalid TraceID with invalid char",
  243. hex: "80f198ee56343ba864fe8b2a57d3efg7",
  244. valid: false,
  245. }, {
  246. name: "Invalid TraceID with uppercase",
  247. hex: "80f198ee56343ba864fe8b2a57d3efF7",
  248. valid: false,
  249. }, {
  250. name: "Invalid TraceID with zero value",
  251. hex: "00000000000000000000000000000000",
  252. valid: false,
  253. },
  254. } {
  255. t.Run(testcase.name, func(t *testing.T) {
  256. tid, err := TraceIDFromHex(testcase.hex)
  257. if testcase.valid && err != nil {
  258. t.Errorf("Expected TraceID %s to be valid but end with error %s", testcase.hex, err.Error())
  259. }
  260. if !testcase.valid && err == nil {
  261. t.Errorf("Expected TraceID %s to be invalid but end no error", testcase.hex)
  262. }
  263. if tid != testcase.tid {
  264. t.Errorf("Want: %v, but have: %v", testcase.tid, tid)
  265. }
  266. })
  267. }
  268. }
  269. func TestSpanContextHasTraceID(t *testing.T) {
  270. for _, testcase := range []struct {
  271. name string
  272. tid TraceID
  273. want bool
  274. }{
  275. {
  276. name: "SpanContext.HasTraceID() returns true if both Low and High are nonzero",
  277. tid: TraceID([16]byte{1}),
  278. want: true,
  279. }, {
  280. name: "SpanContext.HasTraceID() returns false if neither Low nor High are nonzero",
  281. tid: TraceID{},
  282. want: false,
  283. },
  284. } {
  285. t.Run(testcase.name, func(t *testing.T) {
  286. //proto: func (sc SpanContext) HasTraceID() bool{}
  287. sc := SpanContext{traceID: testcase.tid}
  288. have := sc.HasTraceID()
  289. if have != testcase.want {
  290. t.Errorf("Want: %v, but have: %v", testcase.want, have)
  291. }
  292. })
  293. }
  294. }
  295. func TestSpanContextHasSpanID(t *testing.T) {
  296. for _, testcase := range []struct {
  297. name string
  298. sc SpanContext
  299. want bool
  300. }{
  301. {
  302. name: "SpanContext.HasSpanID() returns true if self.SpanID != 0",
  303. sc: SpanContext{spanID: [8]byte{42}},
  304. want: true,
  305. }, {
  306. name: "SpanContext.HasSpanID() returns false if self.SpanID == 0",
  307. sc: SpanContext{},
  308. want: false,
  309. },
  310. } {
  311. t.Run(testcase.name, func(t *testing.T) {
  312. //proto: func (sc SpanContext) HasSpanID() bool {}
  313. have := testcase.sc.HasSpanID()
  314. if have != testcase.want {
  315. t.Errorf("Want: %v, but have: %v", testcase.want, have)
  316. }
  317. })
  318. }
  319. }
  320. func TestTraceFlagsIsSampled(t *testing.T) {
  321. for _, testcase := range []struct {
  322. name string
  323. tf TraceFlags
  324. want bool
  325. }{
  326. {
  327. name: "sampled",
  328. tf: FlagsSampled,
  329. want: true,
  330. }, {
  331. name: "unused bits are ignored, still not sampled",
  332. tf: ^FlagsSampled,
  333. want: false,
  334. }, {
  335. name: "unused bits are ignored, still sampled",
  336. tf: FlagsSampled | ^FlagsSampled,
  337. want: true,
  338. }, {
  339. name: "not sampled/default",
  340. want: false,
  341. },
  342. } {
  343. t.Run(testcase.name, func(t *testing.T) {
  344. have := testcase.tf.IsSampled()
  345. if have != testcase.want {
  346. t.Errorf("Want: %v, but have: %v", testcase.want, have)
  347. }
  348. })
  349. }
  350. }
  351. func TestTraceFlagsWithSampled(t *testing.T) {
  352. for _, testcase := range []struct {
  353. name string
  354. start TraceFlags
  355. sample bool
  356. want TraceFlags
  357. }{
  358. {
  359. name: "sampled unchanged",
  360. start: FlagsSampled,
  361. want: FlagsSampled,
  362. sample: true,
  363. }, {
  364. name: "become sampled",
  365. want: FlagsSampled,
  366. sample: true,
  367. }, {
  368. name: "unused bits are ignored, still not sampled",
  369. start: ^FlagsSampled,
  370. want: ^FlagsSampled,
  371. sample: false,
  372. }, {
  373. name: "unused bits are ignored, becomes sampled",
  374. start: ^FlagsSampled,
  375. want: FlagsSampled | ^FlagsSampled,
  376. sample: true,
  377. }, {
  378. name: "not sampled/default",
  379. sample: false,
  380. },
  381. } {
  382. t.Run(testcase.name, func(t *testing.T) {
  383. have := testcase.start.WithSampled(testcase.sample)
  384. if have != testcase.want {
  385. t.Errorf("Want: %v, but have: %v", testcase.want, have)
  386. }
  387. })
  388. }
  389. }
  390. func TestStringTraceID(t *testing.T) {
  391. for _, testcase := range []struct {
  392. name string
  393. tid TraceID
  394. want string
  395. }{
  396. {
  397. name: "TraceID.String returns string representation of self.TraceID values > 0",
  398. tid: TraceID([16]byte{255}),
  399. want: "ff000000000000000000000000000000",
  400. },
  401. {
  402. name: "TraceID.String returns string representation of self.TraceID values == 0",
  403. tid: TraceID([16]byte{}),
  404. want: "00000000000000000000000000000000",
  405. },
  406. } {
  407. t.Run(testcase.name, func(t *testing.T) {
  408. //proto: func (t TraceID) String() string {}
  409. have := testcase.tid.String()
  410. if have != testcase.want {
  411. t.Errorf("Want: %s, but have: %s", testcase.want, have)
  412. }
  413. })
  414. }
  415. }
  416. func TestStringSpanID(t *testing.T) {
  417. for _, testcase := range []struct {
  418. name string
  419. sid SpanID
  420. want string
  421. }{
  422. {
  423. name: "SpanID.String returns string representation of self.SpanID values > 0",
  424. sid: SpanID([8]byte{255}),
  425. want: "ff00000000000000",
  426. },
  427. {
  428. name: "SpanID.String returns string representation of self.SpanID values == 0",
  429. sid: SpanID([8]byte{}),
  430. want: "0000000000000000",
  431. },
  432. } {
  433. t.Run(testcase.name, func(t *testing.T) {
  434. //proto: func (t TraceID) String() string {}
  435. have := testcase.sid.String()
  436. if have != testcase.want {
  437. t.Errorf("Want: %s, but have: %s", testcase.want, have)
  438. }
  439. })
  440. }
  441. }
  442. func TestValidateSpanKind(t *testing.T) {
  443. tests := []struct {
  444. in SpanKind
  445. want SpanKind
  446. }{
  447. {
  448. SpanKindUnspecified,
  449. SpanKindInternal,
  450. },
  451. {
  452. SpanKindInternal,
  453. SpanKindInternal,
  454. },
  455. {
  456. SpanKindServer,
  457. SpanKindServer,
  458. },
  459. {
  460. SpanKindClient,
  461. SpanKindClient,
  462. },
  463. {
  464. SpanKindProducer,
  465. SpanKindProducer,
  466. },
  467. {
  468. SpanKindConsumer,
  469. SpanKindConsumer,
  470. },
  471. }
  472. for _, test := range tests {
  473. if got := ValidateSpanKind(test.in); got != test.want {
  474. t.Errorf("ValidateSpanKind(%#v) = %#v, want %#v", test.in, got, test.want)
  475. }
  476. }
  477. }
  478. func TestSpanKindString(t *testing.T) {
  479. tests := []struct {
  480. in SpanKind
  481. want string
  482. }{
  483. {
  484. SpanKindUnspecified,
  485. "unspecified",
  486. },
  487. {
  488. SpanKindInternal,
  489. "internal",
  490. },
  491. {
  492. SpanKindServer,
  493. "server",
  494. },
  495. {
  496. SpanKindClient,
  497. "client",
  498. },
  499. {
  500. SpanKindProducer,
  501. "producer",
  502. },
  503. {
  504. SpanKindConsumer,
  505. "consumer",
  506. },
  507. }
  508. for _, test := range tests {
  509. if got := test.in.String(); got != test.want {
  510. t.Errorf("%#v.String() = %#v, want %#v", test.in, got, test.want)
  511. }
  512. }
  513. }
  514. func assertSpanContextEqual(got SpanContext, want SpanContext) bool {
  515. return got.spanID == want.spanID &&
  516. got.traceID == want.traceID &&
  517. got.traceFlags == want.traceFlags &&
  518. got.remote == want.remote &&
  519. got.traceState.String() == want.traceState.String()
  520. }
  521. func TestNewSpanContext(t *testing.T) {
  522. testCases := []struct {
  523. name string
  524. config SpanContextConfig
  525. expectedSpanContext SpanContext
  526. }{
  527. {
  528. name: "Complete SpanContext",
  529. config: SpanContextConfig{
  530. TraceID: TraceID([16]byte{1}),
  531. SpanID: SpanID([8]byte{42}),
  532. TraceFlags: 0x1,
  533. TraceState: TraceState{list: []member{
  534. {"foo", "bar"},
  535. }},
  536. },
  537. expectedSpanContext: SpanContext{
  538. traceID: TraceID([16]byte{1}),
  539. spanID: SpanID([8]byte{42}),
  540. traceFlags: 0x1,
  541. traceState: TraceState{list: []member{
  542. {"foo", "bar"},
  543. }},
  544. },
  545. },
  546. {
  547. name: "Empty SpanContext",
  548. config: SpanContextConfig{},
  549. expectedSpanContext: SpanContext{},
  550. },
  551. {
  552. name: "Partial SpanContext",
  553. config: SpanContextConfig{
  554. TraceID: TraceID([16]byte{1}),
  555. SpanID: SpanID([8]byte{42}),
  556. },
  557. expectedSpanContext: SpanContext{
  558. traceID: TraceID([16]byte{1}),
  559. spanID: SpanID([8]byte{42}),
  560. traceFlags: 0x0,
  561. traceState: TraceState{},
  562. },
  563. },
  564. }
  565. for _, tc := range testCases {
  566. t.Run(tc.name, func(t *testing.T) {
  567. sctx := NewSpanContext(tc.config)
  568. if !assertSpanContextEqual(sctx, tc.expectedSpanContext) {
  569. t.Fatalf("%s: Unexpected context created: %s", tc.name, cmp.Diff(sctx, tc.expectedSpanContext))
  570. }
  571. })
  572. }
  573. }
  574. func TestSpanContextDerivation(t *testing.T) {
  575. from := SpanContext{}
  576. to := SpanContext{traceID: TraceID([16]byte{1})}
  577. modified := from.WithTraceID(to.TraceID())
  578. if !assertSpanContextEqual(modified, to) {
  579. t.Fatalf("WithTraceID: Unexpected context created: %s", cmp.Diff(modified, to))
  580. }
  581. from = to
  582. to.spanID = SpanID([8]byte{42})
  583. modified = from.WithSpanID(to.SpanID())
  584. if !assertSpanContextEqual(modified, to) {
  585. t.Fatalf("WithSpanID: Unexpected context created: %s", cmp.Diff(modified, to))
  586. }
  587. from = to
  588. to.traceFlags = 0x13
  589. modified = from.WithTraceFlags(to.TraceFlags())
  590. if !assertSpanContextEqual(modified, to) {
  591. t.Fatalf("WithTraceFlags: Unexpected context created: %s", cmp.Diff(modified, to))
  592. }
  593. from = to
  594. to.traceState = TraceState{list: []member{{"foo", "bar"}}}
  595. modified = from.WithTraceState(to.TraceState())
  596. if !assertSpanContextEqual(modified, to) {
  597. t.Fatalf("WithTraceState: Unexpected context created: %s", cmp.Diff(modified, to))
  598. }
  599. }
  600. func TestLinkFromContext(t *testing.T) {
  601. k1v1 := attribute.String("key1", "value1")
  602. spanCtx := SpanContext{traceID: TraceID([16]byte{1}), remote: true}
  603. receiverCtx := ContextWithRemoteSpanContext(context.Background(), spanCtx)
  604. link := LinkFromContext(receiverCtx, k1v1)
  605. if !assertSpanContextEqual(link.SpanContext, spanCtx) {
  606. t.Fatalf("LinkFromContext: Unexpected context created: %s", cmp.Diff(link.SpanContext, spanCtx))
  607. }
  608. assert.Equal(t, link.Attributes[0], k1v1)
  609. }