1
2
3
4
5
6
7 package operation
8
9 import (
10 "fmt"
11 "os"
12 "runtime"
13 "testing"
14
15 "go.mongodb.org/mongo-driver/bson"
16 "go.mongodb.org/mongo-driver/internal/assert"
17 "go.mongodb.org/mongo-driver/internal/driverutil"
18 "go.mongodb.org/mongo-driver/internal/require"
19 "go.mongodb.org/mongo-driver/version"
20 "go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
21 )
22
23 func assertDocsEqual(t *testing.T, got bsoncore.Document, want []byte) {
24 t.Helper()
25
26 var gotD bson.D
27 err := bson.Unmarshal(got, &gotD)
28 require.NoError(t, err, "error unmarshaling got document: %v", err)
29
30 var wantD bson.D
31 err = bson.UnmarshalExtJSON(want, true, &wantD)
32 require.NoError(t, err, "error unmarshaling want byte slice: %v", err)
33
34 assert.Equal(t, wantD, gotD, "got %v, want %v", gotD, wantD)
35 }
36
37 func encodeWithCallback(t *testing.T, cb func(int, []byte) ([]byte, error)) bsoncore.Document {
38 t.Helper()
39
40 var err error
41 idx, dst := bsoncore.AppendDocumentStart(nil)
42
43 dst, err = cb(len(dst), dst)
44 require.NoError(t, err, "error appending client metadata: %v", err)
45
46 dst, err = bsoncore.AppendDocumentEnd(dst, idx)
47 require.NoError(t, err, "error appending document end: %v", err)
48
49 got, _, ok := bsoncore.ReadDocument(dst)
50 require.True(t, ok, "error reading document: %v", got)
51
52 return got
53 }
54
55
56
57
58 func clearTestEnv(t *testing.T) {
59 t.Setenv("AWS_EXECUTION_ENV", "")
60 t.Setenv("AWS_LAMBDA_RUNTIME_API", "")
61 t.Setenv("FUNCTIONS_WORKER_RUNTIME", "")
62 t.Setenv("K_SERVICE", "")
63 t.Setenv("FUNCTION_NAME", "")
64 t.Setenv("VERCEL", "")
65 t.Setenv("AWS_REGION", "")
66 t.Setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "")
67 t.Setenv("FUNCTION_MEMORY_MB", "")
68 t.Setenv("FUNCTION_TIMEOUT_SEC", "")
69 t.Setenv("FUNCTION_REGION", "")
70 t.Setenv("VERCEL_REGION", "")
71 }
72
73 func TestAppendClientName(t *testing.T) {
74 t.Parallel()
75
76 tests := []struct {
77 name string
78 appname string
79 want []byte
80 }{
81 {
82 name: "empty",
83 want: []byte(`{}`),
84 },
85 {
86 name: "non-empty",
87 appname: "foo",
88 want: []byte(`{"application":{"name":"foo"}}`),
89 },
90 }
91
92 for _, test := range tests {
93 test := test
94
95 t.Run(test.name, func(t *testing.T) {
96 t.Parallel()
97
98 cb := func(_ int, dst []byte) ([]byte, error) {
99 var err error
100 dst, err = appendClientAppName(dst, test.appname)
101
102 return dst, err
103 }
104
105 got := encodeWithCallback(t, cb)
106 assertDocsEqual(t, got, test.want)
107 })
108 }
109 }
110
111 func TestAppendClientDriver(t *testing.T) {
112 t.Parallel()
113
114 tests := []struct {
115 name string
116 want []byte
117 }{
118 {
119 name: "full",
120 want: []byte(fmt.Sprintf(`{"driver":{"name": %q, "version": %q}}`, driverName, version.Driver)),
121 },
122 }
123
124 for _, test := range tests {
125 test := test
126
127 t.Run(test.name, func(t *testing.T) {
128 t.Parallel()
129
130 cb := func(_ int, dst []byte) ([]byte, error) {
131 var err error
132 dst, err = appendClientDriver(dst)
133
134 return dst, err
135 }
136
137 got := encodeWithCallback(t, cb)
138 assertDocsEqual(t, got, test.want)
139 })
140 }
141 }
142
143 func TestAppendClientEnv(t *testing.T) {
144 clearTestEnv(t)
145
146 if os.Getenv("DOCKER_RUNNING") != "" {
147 t.Skip("These tests gives different results when run in Docker due to extra environment data.")
148 }
149
150 tests := []struct {
151 name string
152 omitEnvFields bool
153 env map[string]string
154 want []byte
155 }{
156 {
157 name: "empty",
158 want: []byte(`{}`),
159 },
160 {
161 name: "empty with omit",
162 omitEnvFields: true,
163 want: []byte(`{}`),
164 },
165 {
166 name: "aws only",
167 env: map[string]string{
168 "AWS_EXECUTION_ENV": "AWS_Lambda_foo",
169 },
170 want: []byte(`{"env":{"name":"aws.lambda"}}`),
171 },
172 {
173 name: "aws mem only",
174 env: map[string]string{
175 "AWS_EXECUTION_ENV": "AWS_Lambda_foo",
176 "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024",
177 },
178 want: []byte(`{"env":{"name":"aws.lambda","memory_mb":1024}}`),
179 },
180 {
181 name: "aws region only",
182 env: map[string]string{
183 "AWS_EXECUTION_ENV": "AWS_Lambda_foo",
184 "AWS_REGION": "us-east-2",
185 },
186 want: []byte(`{"env":{"name":"aws.lambda","region":"us-east-2"}}`),
187 },
188 {
189 name: "aws mem and region",
190 env: map[string]string{
191 "AWS_EXECUTION_ENV": "AWS_Lambda_foo",
192 "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024",
193 "AWS_REGION": "us-east-2",
194 },
195 want: []byte(`{"env":{"name":"aws.lambda","memory_mb":1024,"region":"us-east-2"}}`),
196 },
197 {
198 name: "aws mem and region with omit fields",
199 omitEnvFields: true,
200 env: map[string]string{
201 "AWS_EXECUTION_ENV": "AWS_Lambda_foo",
202 "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": "1024",
203 "AWS_REGION": "us-east-2",
204 },
205 want: []byte(`{"env":{"name":"aws.lambda"}}`),
206 },
207 {
208 name: "gcp only",
209 env: map[string]string{
210 "K_SERVICE": "servicename",
211 },
212 want: []byte(`{"env":{"name":"gcp.func"}}`),
213 },
214 {
215 name: "gcp mem",
216 env: map[string]string{
217 "K_SERVICE": "servicename",
218 "FUNCTION_MEMORY_MB": "1024",
219 },
220 want: []byte(`{"env":{"name":"gcp.func","memory_mb":1024}}`),
221 },
222 {
223 name: "gcp region",
224 env: map[string]string{
225 "K_SERVICE": "servicename",
226 "FUNCTION_REGION": "us-east-2",
227 },
228 want: []byte(`{"env":{"name":"gcp.func","region":"us-east-2"}}`),
229 },
230 {
231 name: "gcp timeout",
232 env: map[string]string{
233 "K_SERVICE": "servicename",
234 "FUNCTION_TIMEOUT_SEC": "1",
235 },
236 want: []byte(`{"env":{"name":"gcp.func","timeout_sec":1}}`),
237 },
238 {
239 name: "gcp mem, region, and timeout",
240 env: map[string]string{
241 "K_SERVICE": "servicename",
242 "FUNCTION_TIMEOUT_SEC": "1",
243 "FUNCTION_REGION": "us-east-2",
244 "FUNCTION_MEMORY_MB": "1024",
245 },
246 want: []byte(`{"env":{"name":"gcp.func","memory_mb":1024,"region":"us-east-2","timeout_sec":1}}`),
247 },
248 {
249 name: "gcp mem, region, and timeout with omit fields",
250 omitEnvFields: true,
251 env: map[string]string{
252 "K_SERVICE": "servicename",
253 "FUNCTION_TIMEOUT_SEC": "1",
254 "FUNCTION_REGION": "us-east-2",
255 "FUNCTION_MEMORY_MB": "1024",
256 },
257 want: []byte(`{"env":{"name":"gcp.func"}}`),
258 },
259 {
260 name: "vercel only",
261 env: map[string]string{
262 "VERCEL": "1",
263 },
264 want: []byte(`{"env":{"name":"vercel"}}`),
265 },
266 {
267 name: "vercel region",
268 env: map[string]string{
269 "VERCEL": "1",
270 "VERCEL_REGION": "us-east-2",
271 },
272 want: []byte(`{"env":{"name":"vercel","region":"us-east-2"}}`),
273 },
274 {
275 name: "azure only",
276 env: map[string]string{
277 "FUNCTIONS_WORKER_RUNTIME": "go1.x",
278 },
279 want: []byte(`{"env":{"name":"azure.func"}}`),
280 },
281 {
282 name: "k8s",
283 env: map[string]string{
284 "KUBERNETES_SERVICE_HOST": "0.0.0.0",
285 },
286 want: []byte(`{"env":{"container":{"orchestrator":"kubernetes"}}}`),
287 },
288
289 }
290
291 for _, test := range tests {
292 test := test
293
294 t.Run(test.name, func(t *testing.T) {
295 for key, val := range test.env {
296 t.Setenv(key, val)
297 }
298
299 cb := func(_ int, dst []byte) ([]byte, error) {
300 var err error
301 dst, err = appendClientEnv(dst, test.omitEnvFields, false)
302
303 return dst, err
304 }
305
306 got := encodeWithCallback(t, cb)
307 assertDocsEqual(t, got, test.want)
308 })
309 }
310 }
311
312 func TestAppendClientOS(t *testing.T) {
313 t.Parallel()
314
315 tests := []struct {
316 name string
317 omitNonType bool
318 want []byte
319 }{
320 {
321 name: "full",
322 want: []byte(fmt.Sprintf(`{"os":{"type":%q,"architecture":%q}}`, runtime.GOOS, runtime.GOARCH)),
323 },
324 {
325 name: "partial",
326 omitNonType: true,
327 want: []byte(fmt.Sprintf(`{"os":{"type":%q}}`, runtime.GOOS)),
328 },
329 }
330
331 for _, test := range tests {
332 test := test
333
334 t.Run(test.name, func(t *testing.T) {
335 t.Parallel()
336
337 cb := func(_ int, dst []byte) ([]byte, error) {
338 var err error
339 dst, err = appendClientOS(dst, test.omitNonType)
340
341 return dst, err
342 }
343
344 got := encodeWithCallback(t, cb)
345 assertDocsEqual(t, got, test.want)
346 })
347 }
348 }
349
350 func TestAppendClientPlatform(t *testing.T) {
351 t.Parallel()
352
353 tests := []struct {
354 name string
355 want []byte
356 }{
357 {
358 name: "full",
359 want: []byte(fmt.Sprintf(`{"platform":%q}`, runtime.Version())),
360 },
361 }
362
363 for _, test := range tests {
364 test := test
365
366 t.Run(test.name, func(t *testing.T) {
367 t.Parallel()
368
369 cb := func(_ int, dst []byte) ([]byte, error) {
370 var err error
371 dst = appendClientPlatform(dst)
372
373 return dst, err
374 }
375
376 got := encodeWithCallback(t, cb)
377 assertDocsEqual(t, got, test.want)
378 })
379 }
380 }
381
382 func TestEncodeClientMetadata(t *testing.T) {
383 clearTestEnv(t)
384
385 if os.Getenv("DOCKER_RUNNING") != "" {
386 t.Skip("These tests gives different results when run in Docker due to extra environment data.")
387 }
388
389 type application struct {
390 Name string `bson:"name"`
391 }
392
393 type driver struct {
394 Name string `bson:"name"`
395 Version string `bson:"version"`
396 }
397
398 type dist struct {
399 Type string `bson:"type,omitempty"`
400 Architecture string `bson:"architecture,omitempty"`
401 }
402
403 type container struct {
404 Runtime string `bson:"runtime,omitempty"`
405 Orchestrator string `bson:"orchestrator,omitempty"`
406 }
407
408 type env struct {
409 Name string `bson:"name,omitempty"`
410 TimeoutSec int64 `bson:"timeout_sec,omitempty"`
411 MemoryMB int32 `bson:"memory_mb,omitempty"`
412 Region string `bson:"region,omitempty"`
413 Container *container `bson:"container,omitempty"`
414 }
415
416 type clientMetadata struct {
417 Application *application `bson:"application"`
418 Driver *driver `bson:"driver"`
419 OS *dist `bson:"os"`
420 Platform string `bson:"platform,omitempty"`
421 Env *env `bson:"env,omitempty"`
422 }
423
424 formatJSON := func(client *clientMetadata) []byte {
425 bytes, err := bson.MarshalExtJSON(client, true, false)
426 require.NoError(t, err, "error encoding client metadata for test: %v", err)
427
428 return bytes
429 }
430
431
432 t.Setenv("AWS_LAMBDA_RUNTIME_API", "lambda")
433 t.Setenv("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "123")
434 t.Setenv("AWS_REGION", "us-east-2")
435 t.Setenv("KUBERNETES_SERVICE_HOST", "0.0.0.0")
436
437 t.Run("nothing is omitted", func(t *testing.T) {
438 got, err := encodeClientMetadata("foo", maxClientMetadataSize)
439 assert.Nil(t, err, "error in encodeClientMetadata: %v", err)
440
441 want := formatJSON(&clientMetadata{
442 Application: &application{Name: "foo"},
443 Driver: &driver{Name: driverName, Version: version.Driver},
444 OS: &dist{Type: runtime.GOOS, Architecture: runtime.GOARCH},
445 Platform: runtime.Version(),
446 Env: &env{
447 Name: "aws.lambda",
448 MemoryMB: 123,
449 Region: "us-east-2",
450 Container: &container{
451 Orchestrator: "kubernetes",
452 },
453 },
454 })
455
456 assertDocsEqual(t, got, want)
457 })
458
459 t.Run("env is omitted sub env.name", func(t *testing.T) {
460
461 temp, err := encodeClientMetadata("foo", maxClientMetadataSize)
462 require.NoError(t, err, "error constructing template: %v", err)
463
464 got, err := encodeClientMetadata("foo", len(temp)-1)
465 assert.Nil(t, err, "error in encodeClientMetadata: %v", err)
466
467 want := formatJSON(&clientMetadata{
468 Application: &application{Name: "foo"},
469 Driver: &driver{Name: driverName, Version: version.Driver},
470 OS: &dist{Type: runtime.GOOS, Architecture: runtime.GOARCH},
471 Platform: runtime.Version(),
472 Env: &env{
473 Name: "aws.lambda",
474 Container: &container{
475 Orchestrator: "kubernetes",
476 },
477 },
478 })
479
480 assertDocsEqual(t, got, want)
481 })
482
483 t.Run("os is omitted sub os.type", func(t *testing.T) {
484
485 temp, err := encodeClientMetadata("foo", maxClientMetadataSize)
486 require.NoError(t, err, "error constructing template: %v", err)
487
488
489 edst, err := appendClientEnv(nil, false, false)
490 require.NoError(t, err, "error constructing env template: %v", err)
491
492
493 ndst := bsoncore.AppendStringElement(nil, "name", "aws.lambda")
494 idx, ndst := bsoncore.AppendDocumentElementStart(ndst, "container")
495 ndst = bsoncore.AppendStringElement(ndst, "orchestrator", "kubernetes")
496 ndst, err = bsoncore.AppendDocumentEnd(ndst, idx)
497 require.NoError(t, err)
498
499
500 envSubName := len(edst) - len(ndst)
501
502 got, err := encodeClientMetadata("foo", len(temp)-envSubName-1)
503 assert.Nil(t, err, "error in encodeClientMetadata: %v", err)
504
505 want := formatJSON(&clientMetadata{
506 Application: &application{Name: "foo"},
507 Driver: &driver{Name: driverName, Version: version.Driver},
508 OS: &dist{Type: runtime.GOOS},
509 Platform: runtime.Version(),
510 Env: &env{
511 Name: "aws.lambda",
512 Container: &container{
513 Orchestrator: "kubernetes",
514 },
515 },
516 })
517
518 assertDocsEqual(t, got, want)
519 })
520
521 t.Run("omit the env doc entirely", func(t *testing.T) {
522
523 temp, err := encodeClientMetadata("foo", maxClientMetadataSize)
524 require.NoError(t, err, "error constructing template: %v", err)
525
526
527 edst, err := appendClientEnv(nil, false, false)
528 require.NoError(t, err, "error constructing env template: %v", err)
529
530
531 odst := bsoncore.AppendStringElement(nil, "type", runtime.GOOS)
532
533
534 envAndOSType := len(edst) + len(odst)
535
536 got, err := encodeClientMetadata("foo", len(temp)-envAndOSType-1)
537 assert.Nil(t, err, "error in encodeClientMetadata: %v", err)
538
539 want := formatJSON(&clientMetadata{
540 Application: &application{Name: "foo"},
541 Driver: &driver{Name: driverName, Version: version.Driver},
542 OS: &dist{Type: runtime.GOOS},
543 Platform: runtime.Version(),
544 })
545
546 assertDocsEqual(t, got, want)
547 })
548
549 t.Run("omit the platform", func(t *testing.T) {
550
551 temp, err := encodeClientMetadata("foo", maxClientMetadataSize)
552 require.NoError(t, err, "error constructing template: %v", err)
553
554
555 edst, err := appendClientEnv(nil, false, false)
556 require.NoError(t, err, "error constructing env template: %v", err)
557
558
559 odst := bsoncore.AppendStringElement(nil, "type", runtime.GOOS)
560
561
562 pdst := appendClientPlatform(nil)
563
564
565 envAndOSTypeAndPlatform := len(edst) + len(odst) + len(pdst)
566
567 got, err := encodeClientMetadata("foo", len(temp)-envAndOSTypeAndPlatform)
568 assert.Nil(t, err, "error in encodeClientMetadata: %v", err)
569
570 want := formatJSON(&clientMetadata{
571 Application: &application{Name: "foo"},
572 Driver: &driver{Name: driverName, Version: version.Driver},
573 OS: &dist{Type: runtime.GOOS},
574 })
575
576 assertDocsEqual(t, got, want)
577 })
578
579 t.Run("0 max len", func(t *testing.T) {
580 got, err := encodeClientMetadata("foo", 0)
581 assert.Nil(t, err, "error in encodeClientMetadata: %v", err)
582 assert.Len(t, got, 0)
583 })
584 }
585
586 func TestParseFaasEnvName(t *testing.T) {
587 clearTestEnv(t)
588
589 tests := []struct {
590 name string
591 env map[string]string
592 want string
593 }{
594 {
595 name: "no env",
596 want: "",
597 },
598 {
599 name: "one aws",
600 env: map[string]string{
601 "AWS_EXECUTION_ENV": "AWS_Lambda_foo",
602 },
603 want: "aws.lambda",
604 },
605 {
606 name: "both aws options",
607 env: map[string]string{
608 "AWS_EXECUTION_ENV": "AWS_Lambda_foo",
609 "AWS_LAMBDA_RUNTIME_API": "hello",
610 },
611 want: "aws.lambda",
612 },
613 {
614 name: "multiple variables",
615 env: map[string]string{
616 "AWS_EXECUTION_ENV": "AWS_Lambda_foo",
617 "FUNCTIONS_WORKER_RUNTIME": "hello",
618 },
619 want: "",
620 },
621 {
622 name: "vercel and aws lambda",
623 env: map[string]string{
624 "AWS_EXECUTION_ENV": "AWS_Lambda_foo",
625 "VERCEL": "hello",
626 },
627 want: "vercel",
628 },
629 {
630 name: "invalid aws prefix",
631 env: map[string]string{
632 "AWS_EXECUTION_ENV": "foo",
633 },
634 want: "",
635 },
636 }
637
638 for _, test := range tests {
639 test := test
640
641 t.Run(test.name, func(t *testing.T) {
642 for key, value := range test.env {
643 t.Setenv(key, value)
644 }
645
646 got := driverutil.GetFaasEnvName()
647 if got != test.want {
648 t.Errorf("parseFaasEnvName(%s) = %s, want %s", test.name, got, test.want)
649 }
650 })
651 }
652 }
653
654 func BenchmarkClientMetadata(b *testing.B) {
655 b.ReportAllocs()
656 b.ResetTimer()
657
658 b.RunParallel(func(pb *testing.PB) {
659 for pb.Next() {
660 _, err := encodeClientMetadata("foo", maxClientMetadataSize)
661 if err != nil {
662 b.Fatal(err)
663 }
664 }
665 })
666 }
667
668 func BenchmarkClientMetadtaLargeEnv(b *testing.B) {
669 b.ReportAllocs()
670 b.ResetTimer()
671
672 b.Setenv("aws.lambda", "foo")
673
674 str := ""
675 for i := 0; i < 512; i++ {
676 str += "a"
677 }
678
679 b.Setenv("AWS_LAMBDA_RUNTIME_API", str)
680
681 b.RunParallel(func(pb *testing.PB) {
682 for pb.Next() {
683 _, err := encodeClientMetadata("foo", maxClientMetadataSize)
684 if err != nil {
685 b.Fatal(err)
686 }
687 }
688 })
689 }
690
691 func FuzzEncodeClientMetadata(f *testing.F) {
692 f.Fuzz(func(t *testing.T, b []byte, appname string) {
693 if len(b) > maxClientMetadataSize {
694 return
695 }
696
697 _, err := encodeClientMetadata(appname, maxClientMetadataSize)
698 if err != nil {
699 t.Fatalf("error appending client: %v", err)
700 }
701
702 _, err = appendClientAppName(b, appname)
703 if err != nil {
704 t.Fatalf("error appending client app name: %v", err)
705 }
706
707 _, err = appendClientDriver(b)
708 if err != nil {
709 t.Fatalf("error appending client driver: %v", err)
710 }
711
712 _, err = appendClientEnv(b, false, false)
713 if err != nil {
714 t.Fatalf("error appending client env ff: %v", err)
715 }
716
717 _, err = appendClientEnv(b, false, true)
718 if err != nil {
719 t.Fatalf("error appending client env ft: %v", err)
720 }
721
722 _, err = appendClientEnv(b, true, false)
723 if err != nil {
724 t.Fatalf("error appending client env tf: %v", err)
725 }
726
727 _, err = appendClientEnv(b, true, true)
728 if err != nil {
729 t.Fatalf("error appending client env tt: %v", err)
730 }
731
732 _, err = appendClientOS(b, false)
733 if err != nil {
734 t.Fatalf("error appending client os f: %v", err)
735 }
736
737 _, err = appendClientOS(b, true)
738 if err != nil {
739 t.Fatalf("error appending client os t: %v", err)
740 }
741
742 appendClientPlatform(b)
743 })
744 }
745
View as plain text