...

Source file src/go.opentelemetry.io/otel/sdk/resource/resource_test.go

Documentation: go.opentelemetry.io/otel/sdk/resource

     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  
    15  package resource_test
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"os"
    23  	"strings"
    24  	"sync"
    25  	"testing"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/require"
    30  
    31  	"go.opentelemetry.io/otel/attribute"
    32  	"go.opentelemetry.io/otel/sdk"
    33  	ottest "go.opentelemetry.io/otel/sdk/internal/internaltest"
    34  	"go.opentelemetry.io/otel/sdk/resource"
    35  	semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
    36  )
    37  
    38  var (
    39  	kv11 = attribute.String("k1", "v11")
    40  	kv12 = attribute.String("k1", "v12")
    41  	kv21 = attribute.String("k2", "v21")
    42  	kv31 = attribute.String("k3", "v31")
    43  	kv41 = attribute.String("k4", "v41")
    44  	kv42 = attribute.String("k4", "")
    45  )
    46  
    47  func TestNewWithAttributes(t *testing.T) {
    48  	cases := []struct {
    49  		name string
    50  		in   []attribute.KeyValue
    51  		want []attribute.KeyValue
    52  	}{
    53  		{
    54  			name: "Key with common key order1",
    55  			in:   []attribute.KeyValue{kv12, kv11, kv21},
    56  			want: []attribute.KeyValue{kv11, kv21},
    57  		},
    58  		{
    59  			name: "Key with common key order2",
    60  			in:   []attribute.KeyValue{kv11, kv12, kv21},
    61  			want: []attribute.KeyValue{kv12, kv21},
    62  		},
    63  		{
    64  			name: "Key with nil",
    65  			in:   nil,
    66  			want: nil,
    67  		},
    68  	}
    69  	for _, c := range cases {
    70  		t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) {
    71  			res := resource.NewSchemaless(c.in...)
    72  			if diff := cmp.Diff(
    73  				res.Attributes(),
    74  				c.want,
    75  				cmp.AllowUnexported(attribute.Value{})); diff != "" {
    76  				t.Fatalf("unwanted result: diff %+v,", diff)
    77  			}
    78  		})
    79  	}
    80  }
    81  
    82  func TestMerge(t *testing.T) {
    83  	cases := []struct {
    84  		name      string
    85  		a, b      *resource.Resource
    86  		want      []attribute.KeyValue
    87  		isErr     bool
    88  		schemaURL string
    89  	}{
    90  		{
    91  			name: "Merge 2 nils",
    92  			a:    nil,
    93  			b:    nil,
    94  			want: nil,
    95  		},
    96  		{
    97  			name: "Merge with no overlap, no nil",
    98  			a:    resource.NewSchemaless(kv11, kv31),
    99  			b:    resource.NewSchemaless(kv21, kv41),
   100  			want: []attribute.KeyValue{kv11, kv21, kv31, kv41},
   101  		},
   102  		{
   103  			name: "Merge with no overlap, no nil, not interleaved",
   104  			a:    resource.NewSchemaless(kv11, kv21),
   105  			b:    resource.NewSchemaless(kv31, kv41),
   106  			want: []attribute.KeyValue{kv11, kv21, kv31, kv41},
   107  		},
   108  		{
   109  			name: "Merge with common key order1",
   110  			a:    resource.NewSchemaless(kv11),
   111  			b:    resource.NewSchemaless(kv12, kv21),
   112  			want: []attribute.KeyValue{kv12, kv21},
   113  		},
   114  		{
   115  			name: "Merge with common key order2",
   116  			a:    resource.NewSchemaless(kv12, kv21),
   117  			b:    resource.NewSchemaless(kv11),
   118  			want: []attribute.KeyValue{kv11, kv21},
   119  		},
   120  		{
   121  			name: "Merge with common key order4",
   122  			a:    resource.NewSchemaless(kv11, kv21, kv41),
   123  			b:    resource.NewSchemaless(kv31, kv41),
   124  			want: []attribute.KeyValue{kv11, kv21, kv31, kv41},
   125  		},
   126  		{
   127  			name: "Merge with no keys",
   128  			a:    resource.NewSchemaless(),
   129  			b:    resource.NewSchemaless(),
   130  			want: nil,
   131  		},
   132  		{
   133  			name: "Merge with first resource no keys",
   134  			a:    resource.NewSchemaless(),
   135  			b:    resource.NewSchemaless(kv21),
   136  			want: []attribute.KeyValue{kv21},
   137  		},
   138  		{
   139  			name: "Merge with second resource no keys",
   140  			a:    resource.NewSchemaless(kv11),
   141  			b:    resource.NewSchemaless(),
   142  			want: []attribute.KeyValue{kv11},
   143  		},
   144  		{
   145  			name: "Merge with first resource nil",
   146  			a:    nil,
   147  			b:    resource.NewSchemaless(kv21),
   148  			want: []attribute.KeyValue{kv21},
   149  		},
   150  		{
   151  			name: "Merge with second resource nil",
   152  			a:    resource.NewSchemaless(kv11),
   153  			b:    nil,
   154  			want: []attribute.KeyValue{kv11},
   155  		},
   156  		{
   157  			name: "Merge with first resource value empty string",
   158  			a:    resource.NewSchemaless(kv42),
   159  			b:    resource.NewSchemaless(kv41),
   160  			want: []attribute.KeyValue{kv41},
   161  		},
   162  		{
   163  			name: "Merge with second resource value empty string",
   164  			a:    resource.NewSchemaless(kv41),
   165  			b:    resource.NewSchemaless(kv42),
   166  			want: []attribute.KeyValue{kv42},
   167  		},
   168  		{
   169  			name:      "Merge with first resource with schema",
   170  			a:         resource.NewWithAttributes("https://opentelemetry.io/schemas/1.4.0", kv41),
   171  			b:         resource.NewSchemaless(kv42),
   172  			want:      []attribute.KeyValue{kv42},
   173  			schemaURL: "https://opentelemetry.io/schemas/1.4.0",
   174  		},
   175  		{
   176  			name:      "Merge with second resource with schema",
   177  			a:         resource.NewSchemaless(kv41),
   178  			b:         resource.NewWithAttributes("https://opentelemetry.io/schemas/1.4.0", kv42),
   179  			want:      []attribute.KeyValue{kv42},
   180  			schemaURL: "https://opentelemetry.io/schemas/1.4.0",
   181  		},
   182  		{
   183  			name:  "Merge with different schemas",
   184  			a:     resource.NewWithAttributes("https://opentelemetry.io/schemas/1.4.0", kv41),
   185  			b:     resource.NewWithAttributes("https://opentelemetry.io/schemas/1.3.0", kv42),
   186  			want:  nil,
   187  			isErr: true,
   188  		},
   189  	}
   190  	for _, c := range cases {
   191  		t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) {
   192  			res, err := resource.Merge(c.a, c.b)
   193  			if c.isErr {
   194  				assert.Error(t, err)
   195  			} else {
   196  				assert.NoError(t, err)
   197  			}
   198  			assert.EqualValues(t, c.schemaURL, res.SchemaURL())
   199  			if diff := cmp.Diff(
   200  				res.Attributes(),
   201  				c.want,
   202  				cmp.AllowUnexported(attribute.Value{})); diff != "" {
   203  				t.Fatalf("unwanted result: diff %+v,", diff)
   204  			}
   205  		})
   206  	}
   207  }
   208  
   209  func TestEmpty(t *testing.T) {
   210  	var res *resource.Resource
   211  	assert.Equal(t, "", res.SchemaURL())
   212  	assert.Equal(t, "", res.String())
   213  	assert.Equal(t, []attribute.KeyValue(nil), res.Attributes())
   214  
   215  	it := res.Iter()
   216  	assert.Equal(t, 0, it.Len())
   217  	assert.True(t, res.Equal(res))
   218  }
   219  
   220  func TestDefault(t *testing.T) {
   221  	res := resource.Default()
   222  	require.False(t, res.Equal(resource.Empty()))
   223  	require.True(t, res.Set().HasValue(semconv.ServiceNameKey))
   224  
   225  	serviceName, _ := res.Set().Value(semconv.ServiceNameKey)
   226  	require.True(t, strings.HasPrefix(serviceName.AsString(), "unknown_service:"))
   227  	require.Greaterf(t, len(serviceName.AsString()), len("unknown_service:"),
   228  		"default service.name should include executable name")
   229  
   230  	require.Contains(t, res.Attributes(), semconv.TelemetrySDKLanguageGo)
   231  	require.Contains(t, res.Attributes(), semconv.TelemetrySDKVersion(sdk.Version()))
   232  	require.Contains(t, res.Attributes(), semconv.TelemetrySDKName("opentelemetry"))
   233  }
   234  
   235  func TestString(t *testing.T) {
   236  	for _, test := range []struct {
   237  		kvs  []attribute.KeyValue
   238  		want string
   239  	}{
   240  		{
   241  			kvs:  nil,
   242  			want: "",
   243  		},
   244  		{
   245  			kvs:  []attribute.KeyValue{},
   246  			want: "",
   247  		},
   248  		{
   249  			kvs:  []attribute.KeyValue{kv11},
   250  			want: "k1=v11",
   251  		},
   252  		{
   253  			kvs:  []attribute.KeyValue{kv11, kv12},
   254  			want: "k1=v12",
   255  		},
   256  		{
   257  			kvs:  []attribute.KeyValue{kv11, kv21},
   258  			want: "k1=v11,k2=v21",
   259  		},
   260  		{
   261  			kvs:  []attribute.KeyValue{kv21, kv11},
   262  			want: "k1=v11,k2=v21",
   263  		},
   264  		{
   265  			kvs:  []attribute.KeyValue{kv11, kv21, kv31},
   266  			want: "k1=v11,k2=v21,k3=v31",
   267  		},
   268  		{
   269  			kvs:  []attribute.KeyValue{kv31, kv11, kv21},
   270  			want: "k1=v11,k2=v21,k3=v31",
   271  		},
   272  		{
   273  			kvs:  []attribute.KeyValue{attribute.String("A", "a"), attribute.String("B", "b")},
   274  			want: "A=a,B=b",
   275  		},
   276  		{
   277  			kvs:  []attribute.KeyValue{attribute.String("A", "a,B=b")},
   278  			want: `A=a\,B\=b`,
   279  		},
   280  		{
   281  			kvs:  []attribute.KeyValue{attribute.String("A", `a,B\=b`)},
   282  			want: `A=a\,B\\\=b`,
   283  		},
   284  		{
   285  			kvs:  []attribute.KeyValue{attribute.String("A=a,B", `b`)},
   286  			want: `A\=a\,B=b`,
   287  		},
   288  		{
   289  			kvs:  []attribute.KeyValue{attribute.String(`A=a\,B`, `b`)},
   290  			want: `A\=a\\\,B=b`,
   291  		},
   292  		{
   293  			kvs:  []attribute.KeyValue{attribute.String("", "invalid")},
   294  			want: "",
   295  		},
   296  		{
   297  			kvs:  []attribute.KeyValue{attribute.String("", "invalid"), attribute.String("B", "b")},
   298  			want: "B=b",
   299  		},
   300  	} {
   301  		if got := resource.NewSchemaless(test.kvs...).String(); got != test.want {
   302  			t.Errorf("Resource(%v).String() = %q, want %q", test.kvs, got, test.want)
   303  		}
   304  	}
   305  }
   306  
   307  const envVar = "OTEL_RESOURCE_ATTRIBUTES"
   308  
   309  func TestMarshalJSON(t *testing.T) {
   310  	r := resource.NewSchemaless(attribute.Int64("A", 1), attribute.String("C", "D"))
   311  	data, err := json.Marshal(r)
   312  	require.NoError(t, err)
   313  	require.Equal(t,
   314  		`[{"Key":"A","Value":{"Type":"INT64","Value":1}},{"Key":"C","Value":{"Type":"STRING","Value":"D"}}]`,
   315  		string(data))
   316  }
   317  
   318  func TestNew(t *testing.T) {
   319  	tc := []struct {
   320  		name      string
   321  		envars    string
   322  		detectors []resource.Detector
   323  		options   []resource.Option
   324  
   325  		resourceValues map[string]string
   326  		schemaURL      string
   327  		isErr          bool
   328  	}{
   329  		{
   330  			name:           "No Options returns empty resource",
   331  			envars:         "key=value,other=attr",
   332  			options:        nil,
   333  			resourceValues: map[string]string{},
   334  		},
   335  		{
   336  			name:   "Nil Detectors works",
   337  			envars: "key=value,other=attr",
   338  			options: []resource.Option{
   339  				resource.WithDetectors(),
   340  			},
   341  			resourceValues: map[string]string{},
   342  		},
   343  		{
   344  			name:   "Only Host",
   345  			envars: "from=here",
   346  			options: []resource.Option{
   347  				resource.WithHost(),
   348  			},
   349  			resourceValues: map[string]string{
   350  				"host.name": hostname(),
   351  			},
   352  			schemaURL: semconv.SchemaURL,
   353  		},
   354  		{
   355  			name:   "Only Env",
   356  			envars: "key=value,other=attr",
   357  			options: []resource.Option{
   358  				resource.WithFromEnv(),
   359  			},
   360  			resourceValues: map[string]string{
   361  				"key":   "value",
   362  				"other": "attr",
   363  			},
   364  		},
   365  		{
   366  			name:   "Only TelemetrySDK",
   367  			envars: "",
   368  			options: []resource.Option{
   369  				resource.WithTelemetrySDK(),
   370  			},
   371  			resourceValues: map[string]string{
   372  				"telemetry.sdk.name":     "opentelemetry",
   373  				"telemetry.sdk.language": "go",
   374  				"telemetry.sdk.version":  sdk.Version(),
   375  			},
   376  			schemaURL: semconv.SchemaURL,
   377  		},
   378  		{
   379  			name:   "WithAttributes",
   380  			envars: "key=value,other=attr",
   381  			options: []resource.Option{
   382  				resource.WithAttributes(attribute.String("A", "B")),
   383  			},
   384  			resourceValues: map[string]string{
   385  				"A": "B",
   386  			},
   387  		},
   388  		{
   389  			name:   "With schema url",
   390  			envars: "",
   391  			options: []resource.Option{
   392  				resource.WithAttributes(attribute.String("A", "B")),
   393  				resource.WithSchemaURL("https://opentelemetry.io/schemas/1.0.0"),
   394  			},
   395  			resourceValues: map[string]string{
   396  				"A": "B",
   397  			},
   398  			schemaURL: "https://opentelemetry.io/schemas/1.0.0",
   399  		},
   400  		{
   401  			name:   "With conflicting schema urls",
   402  			envars: "",
   403  			options: []resource.Option{
   404  				resource.WithDetectors(
   405  					resource.StringDetector("https://opentelemetry.io/schemas/1.0.0", semconv.HostNameKey, os.Hostname),
   406  				),
   407  				resource.WithSchemaURL("https://opentelemetry.io/schemas/1.1.0"),
   408  			},
   409  			resourceValues: map[string]string{},
   410  			schemaURL:      "",
   411  			isErr:          true,
   412  		},
   413  		{
   414  			name:   "With conflicting detector schema urls",
   415  			envars: "",
   416  			options: []resource.Option{
   417  				resource.WithDetectors(
   418  					resource.StringDetector("https://opentelemetry.io/schemas/1.0.0", semconv.HostNameKey, os.Hostname),
   419  					resource.StringDetector("https://opentelemetry.io/schemas/1.1.0", semconv.HostNameKey, func() (string, error) { return "", errors.New("fail") }),
   420  				),
   421  				resource.WithSchemaURL("https://opentelemetry.io/schemas/1.2.0"),
   422  			},
   423  			resourceValues: map[string]string{},
   424  			schemaURL:      "",
   425  			isErr:          true,
   426  		},
   427  	}
   428  	for _, tt := range tc {
   429  		t.Run(tt.name, func(t *testing.T) {
   430  			store, err := ottest.SetEnvVariables(map[string]string{
   431  				envVar: tt.envars,
   432  			})
   433  			require.NoError(t, err)
   434  			defer func() { require.NoError(t, store.Restore()) }()
   435  
   436  			ctx := context.Background()
   437  			res, err := resource.New(ctx, tt.options...)
   438  
   439  			if tt.isErr {
   440  				require.Error(t, err)
   441  			} else {
   442  				require.NoError(t, err)
   443  			}
   444  
   445  			require.EqualValues(t, tt.resourceValues, toMap(res))
   446  
   447  			// TODO: do we need to ensure that resource is never nil and eliminate the
   448  			// following if?
   449  			if res != nil {
   450  				assert.EqualValues(t, tt.schemaURL, res.SchemaURL())
   451  			}
   452  		})
   453  	}
   454  }
   455  
   456  func TestNewWrapedError(t *testing.T) {
   457  	localErr := errors.New("local error")
   458  	_, err := resource.New(
   459  		context.Background(),
   460  		resource.WithDetectors(
   461  			resource.StringDetector("", "", func() (string, error) {
   462  				return "", localErr
   463  			}),
   464  			resource.StringDetector("", "", func() (string, error) {
   465  				return "", assert.AnError
   466  			}),
   467  		),
   468  	)
   469  
   470  	assert.ErrorIs(t, err, localErr)
   471  	assert.ErrorIs(t, err, assert.AnError)
   472  	assert.NotErrorIs(t, err, errors.New("false positive error"))
   473  }
   474  
   475  func TestWithHostID(t *testing.T) {
   476  	mockHostIDProvider()
   477  	t.Cleanup(restoreHostIDProvider)
   478  
   479  	ctx := context.Background()
   480  
   481  	res, err := resource.New(ctx,
   482  		resource.WithHostID(),
   483  	)
   484  
   485  	require.NoError(t, err)
   486  	require.EqualValues(t, map[string]string{
   487  		"host.id": "f2c668b579780554f70f72a063dc0864",
   488  	}, toMap(res))
   489  }
   490  
   491  func TestWithHostIDError(t *testing.T) {
   492  	mockHostIDProviderWithError()
   493  	t.Cleanup(restoreHostIDProvider)
   494  
   495  	ctx := context.Background()
   496  
   497  	res, err := resource.New(ctx,
   498  		resource.WithHostID(),
   499  	)
   500  
   501  	assert.ErrorIs(t, err, assert.AnError)
   502  	require.EqualValues(t, map[string]string{}, toMap(res))
   503  }
   504  
   505  func TestWithOSType(t *testing.T) {
   506  	mockRuntimeProviders()
   507  	t.Cleanup(restoreAttributesProviders)
   508  
   509  	ctx := context.Background()
   510  
   511  	res, err := resource.New(ctx,
   512  		resource.WithOSType(),
   513  	)
   514  
   515  	require.NoError(t, err)
   516  	require.EqualValues(t, map[string]string{
   517  		"os.type": "linux",
   518  	}, toMap(res))
   519  }
   520  
   521  func TestWithOSDescription(t *testing.T) {
   522  	mockRuntimeProviders()
   523  	t.Cleanup(restoreAttributesProviders)
   524  
   525  	ctx := context.Background()
   526  
   527  	res, err := resource.New(ctx,
   528  		resource.WithOSDescription(),
   529  	)
   530  
   531  	require.NoError(t, err)
   532  	require.EqualValues(t, map[string]string{
   533  		"os.description": "Test",
   534  	}, toMap(res))
   535  }
   536  
   537  func TestWithOS(t *testing.T) {
   538  	mockRuntimeProviders()
   539  	t.Cleanup(restoreAttributesProviders)
   540  
   541  	ctx := context.Background()
   542  
   543  	res, err := resource.New(ctx,
   544  		resource.WithOS(),
   545  	)
   546  
   547  	require.NoError(t, err)
   548  	require.EqualValues(t, map[string]string{
   549  		"os.type":        "linux",
   550  		"os.description": "Test",
   551  	}, toMap(res))
   552  }
   553  
   554  func TestWithProcessPID(t *testing.T) {
   555  	mockProcessAttributesProvidersWithErrors()
   556  	ctx := context.Background()
   557  
   558  	res, err := resource.New(ctx,
   559  		resource.WithProcessPID(),
   560  	)
   561  
   562  	require.NoError(t, err)
   563  	require.EqualValues(t, map[string]string{
   564  		"process.pid": fmt.Sprint(fakePID),
   565  	}, toMap(res))
   566  }
   567  
   568  func TestWithProcessExecutableName(t *testing.T) {
   569  	mockProcessAttributesProvidersWithErrors()
   570  	ctx := context.Background()
   571  
   572  	res, err := resource.New(ctx,
   573  		resource.WithProcessExecutableName(),
   574  	)
   575  
   576  	require.NoError(t, err)
   577  	require.EqualValues(t, map[string]string{
   578  		"process.executable.name": fakeExecutableName,
   579  	}, toMap(res))
   580  }
   581  
   582  func TestWithProcessExecutablePath(t *testing.T) {
   583  	mockProcessAttributesProviders()
   584  	ctx := context.Background()
   585  
   586  	res, err := resource.New(ctx,
   587  		resource.WithProcessExecutablePath(),
   588  	)
   589  
   590  	require.NoError(t, err)
   591  	require.EqualValues(t, map[string]string{
   592  		"process.executable.path": fakeExecutablePath,
   593  	}, toMap(res))
   594  }
   595  
   596  func TestWithProcessCommandArgs(t *testing.T) {
   597  	mockProcessAttributesProvidersWithErrors()
   598  	ctx := context.Background()
   599  
   600  	res, err := resource.New(ctx,
   601  		resource.WithProcessCommandArgs(),
   602  	)
   603  
   604  	require.NoError(t, err)
   605  	require.EqualValues(t, map[string]string{
   606  		"process.command_args": fmt.Sprint(fakeCommandArgs),
   607  	}, toMap(res))
   608  }
   609  
   610  func TestWithProcessOwner(t *testing.T) {
   611  	mockProcessAttributesProviders()
   612  	ctx := context.Background()
   613  
   614  	res, err := resource.New(ctx,
   615  		resource.WithProcessOwner(),
   616  	)
   617  
   618  	require.NoError(t, err)
   619  	require.EqualValues(t, map[string]string{
   620  		"process.owner": fakeOwner,
   621  	}, toMap(res))
   622  }
   623  
   624  func TestWithProcessRuntimeName(t *testing.T) {
   625  	mockProcessAttributesProvidersWithErrors()
   626  	ctx := context.Background()
   627  
   628  	res, err := resource.New(ctx,
   629  		resource.WithProcessRuntimeName(),
   630  	)
   631  
   632  	require.NoError(t, err)
   633  	require.EqualValues(t, map[string]string{
   634  		"process.runtime.name": fakeRuntimeName,
   635  	}, toMap(res))
   636  }
   637  
   638  func TestWithProcessRuntimeVersion(t *testing.T) {
   639  	mockProcessAttributesProvidersWithErrors()
   640  	ctx := context.Background()
   641  
   642  	res, err := resource.New(ctx,
   643  		resource.WithProcessRuntimeVersion(),
   644  	)
   645  
   646  	require.NoError(t, err)
   647  	require.EqualValues(t, map[string]string{
   648  		"process.runtime.version": fakeRuntimeVersion,
   649  	}, toMap(res))
   650  }
   651  
   652  func TestWithProcessRuntimeDescription(t *testing.T) {
   653  	mockProcessAttributesProvidersWithErrors()
   654  	ctx := context.Background()
   655  
   656  	res, err := resource.New(ctx,
   657  		resource.WithProcessRuntimeDescription(),
   658  	)
   659  
   660  	require.NoError(t, err)
   661  	require.EqualValues(t, map[string]string{
   662  		"process.runtime.description": fakeRuntimeDescription,
   663  	}, toMap(res))
   664  }
   665  
   666  func TestWithProcess(t *testing.T) {
   667  	mockProcessAttributesProviders()
   668  	ctx := context.Background()
   669  
   670  	res, err := resource.New(ctx,
   671  		resource.WithProcess(),
   672  	)
   673  
   674  	require.NoError(t, err)
   675  	require.EqualValues(t, map[string]string{
   676  		"process.pid":                 fmt.Sprint(fakePID),
   677  		"process.executable.name":     fakeExecutableName,
   678  		"process.executable.path":     fakeExecutablePath,
   679  		"process.command_args":        fmt.Sprint(fakeCommandArgs),
   680  		"process.owner":               fakeOwner,
   681  		"process.runtime.name":        fakeRuntimeName,
   682  		"process.runtime.version":     fakeRuntimeVersion,
   683  		"process.runtime.description": fakeRuntimeDescription,
   684  	}, toMap(res))
   685  }
   686  
   687  func toMap(res *resource.Resource) map[string]string {
   688  	m := map[string]string{}
   689  	for _, attr := range res.Attributes() {
   690  		m[string(attr.Key)] = attr.Value.Emit()
   691  	}
   692  	return m
   693  }
   694  
   695  func hostname() string {
   696  	hn, err := os.Hostname()
   697  	if err != nil {
   698  		return fmt.Sprintf("hostname(%s)", err)
   699  	}
   700  	return hn
   701  }
   702  
   703  func TestWithContainerID(t *testing.T) {
   704  	t.Cleanup(restoreAttributesProviders)
   705  
   706  	fakeContainerID := "fake-container-id"
   707  
   708  	testCases := []struct {
   709  		name                string
   710  		containerIDProvider func() (string, error)
   711  		expectedResource    map[string]string
   712  		expectedErr         bool
   713  	}{
   714  		{
   715  			name: "get container id",
   716  			containerIDProvider: func() (string, error) {
   717  				return fakeContainerID, nil
   718  			},
   719  			expectedResource: map[string]string{
   720  				string(semconv.ContainerIDKey): fakeContainerID,
   721  			},
   722  		},
   723  		{
   724  			name: "no container id found",
   725  			containerIDProvider: func() (string, error) {
   726  				return "", nil
   727  			},
   728  			expectedResource: map[string]string{},
   729  		},
   730  		{
   731  			name: "error",
   732  			containerIDProvider: func() (string, error) {
   733  				return "", fmt.Errorf("unable to get container id")
   734  			},
   735  			expectedResource: map[string]string{},
   736  			expectedErr:      true,
   737  		},
   738  	}
   739  
   740  	for _, tc := range testCases {
   741  		t.Run(tc.name, func(t *testing.T) {
   742  			resource.SetContainerProviders(tc.containerIDProvider)
   743  
   744  			res, err := resource.New(context.Background(),
   745  				resource.WithContainerID(),
   746  			)
   747  
   748  			if tc.expectedErr {
   749  				assert.Error(t, err)
   750  			}
   751  			assert.Equal(t, tc.expectedResource, toMap(res))
   752  		})
   753  	}
   754  }
   755  
   756  func TestWithContainer(t *testing.T) {
   757  	t.Cleanup(restoreAttributesProviders)
   758  
   759  	fakeContainerID := "fake-container-id"
   760  	resource.SetContainerProviders(func() (string, error) {
   761  		return fakeContainerID, nil
   762  	})
   763  
   764  	res, err := resource.New(context.Background(),
   765  		resource.WithContainer(),
   766  	)
   767  
   768  	assert.NoError(t, err)
   769  	assert.Equal(t, map[string]string{
   770  		string(semconv.ContainerIDKey): fakeContainerID,
   771  	}, toMap(res))
   772  }
   773  
   774  func TestResourceConcurrentSafe(t *testing.T) {
   775  	// Creating Resources should also be free of any data races,
   776  	// because Resources are immutable.
   777  	var wg sync.WaitGroup
   778  	for i := 0; i < 2; i++ {
   779  		wg.Add(1)
   780  		go func() {
   781  			defer wg.Done()
   782  			d := &fakeDetector{}
   783  			_, err := resource.Detect(context.Background(), d)
   784  			assert.NoError(t, err)
   785  		}()
   786  	}
   787  	wg.Wait()
   788  }
   789  
   790  type fakeDetector struct{}
   791  
   792  func (f fakeDetector) Detect(_ context.Context) (*resource.Resource, error) {
   793  	// A bit pedantic, but resource.NewWithAttributes returns an empty Resource when
   794  	// no attributes specified. We want to make sure that this is concurrent-safe.
   795  	return resource.NewWithAttributes("https://opentelemetry.io/schemas/1.3.0"), nil
   796  }
   797  
   798  var _ resource.Detector = &fakeDetector{}
   799  

View as plain text