Skip to content

Commit

Permalink
otelmongo: Use a mock deployment for testing against a MongoDB server (
Browse files Browse the repository at this point in the history
…#5749)

Resolves #39 

Use mtest to mock a mongodb deployment.
  • Loading branch information
prestonvasquez authored Sep 20, 2024
1 parent 2a72871 commit 4122c4b
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 147 deletions.
44 changes: 0 additions & 44 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -139,47 +139,3 @@ jobs:
echo ${{ needs.compatibility-test.result }}
test ${{ needs.compatibility-test.result }} == "success"
integration:
strategy:
matrix:
target: [test-mongo-driver]
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ env.DEFAULT_GO_VERSION }}
check-latest: true
cache-dependency-path: "**/go.sum"
- name: Run coverage tests ${{ matrix.target }}
env:
INTEGRATION: ${{ matrix.target }}
run: |
make ${{ matrix.target }}
mkdir -p $TEST_RESULTS
find . -name 'coverage.html' > "${TEST_RESULTS}/coverage.lst"
tar -n -cf - -T "${TEST_RESULTS}/coverage.lst" | tar -C "${TEST_RESULTS}" -xvf -
- name: Upload coverage report
uses: codecov/[email protected]
if: hashFiles('coverage.out') != ''
with:
file: ./coverage.out
fail_ci_if_error: true
verbose: true
token: ${{ secrets.CODECOV_TOKEN }}
- name: Store coverage test output
uses: actions/upload-artifact@v4
with:
name: opentelemetry-go-contrib-integration-test-output
path: ${{ env.TEST_RESULTS }}

test-integration:
runs-on: ubuntu-latest
needs: [integration]
steps:
- name: Test if integration workflow passed
run: |
echo ${{ needs.integration.result }}
test ${{ needs.integration.result }} == "success"
19 changes: 1 addition & 18 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -263,23 +263,6 @@ test-coverage/%:
&& $$CMD ./... \
&& $(GO) tool cover -html=coverage.out -o coverage.html;

.PHONY: test-mongo-driver
test-mongo-driver:
@if ./tools/should_build.sh mongo-driver; then \
set -e; \
docker run --name mongo-integ --rm -p 27017:27017 -d mongo; \
CMD=mongo IMG_NAME=mongo-integ ./tools/wait.sh; \
(cd instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test && \
$(GO) test \
-covermode=$(COVERAGE_MODE) \
-coverprofile=$(COVERAGE_PROFILE) \
-coverpkg=go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/... \
./... \
&& $(GO) tool cover -html=$(COVERAGE_PROFILE) -o coverage.html); \
cp ./instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo/test/coverage.out ./; \
docker stop mongo-integ; \
fi

# Releasing

.PHONY: gorelease
Expand Down Expand Up @@ -351,4 +334,4 @@ genjsonschema: genjsonschema-cleanup $(GOJSONSCHEMA)

.PHONY: codespell
codespell: $(CODESPELL)
@$(DOCKERPY) $(CODESPELL)
@$(DOCKERPY) $(CODESPELL)
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ go 1.22
require (
github.com/stretchr/testify v1.9.0
go.mongodb.org/mongo-driver v1.17.0
go.opentelemetry.io/contrib v1.30.0
go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo v0.55.0
go.opentelemetry.io/otel v1.30.0
go.opentelemetry.io/otel/sdk v1.30.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,23 @@ package test

import (
"context"
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/integration/mtest"
"go.mongodb.org/mongo-driver/mongo/options"

"go.opentelemetry.io/contrib/instrumentation/go.mongodb.org/mongo-driver/mongo/otelmongo"
"go.opentelemetry.io/contrib/internal/util"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/sdk/trace/tracetest"
"go.opentelemetry.io/otel/trace"
)

func TestMain(m *testing.M) {
util.IntegrationShouldRun("test-mongo-driver")
os.Exit(m.Run())
}

type validator func(sdktrace.ReadOnlySpan) bool

func TestDBCrudOperation(t *testing.T) {
Expand All @@ -49,6 +43,7 @@ func TestDBCrudOperation(t *testing.T) {
tt := []struct {
title string
operation func(context.Context, *mongo.Database) (interface{}, error)
mockResponses []bson.D
excludeCommand bool
validators []validator
}{
Expand All @@ -57,6 +52,7 @@ func TestDBCrudOperation(t *testing.T) {
operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) {
return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}})
},
mockResponses: []bson.D{{{Key: "ok", Value: 1}}},
excludeCommand: false,
validators: append(commonValidators, func(s sdktrace.ReadOnlySpan) bool {
for _, attr := range s.Attributes() {
Expand All @@ -72,6 +68,7 @@ func TestDBCrudOperation(t *testing.T) {
operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) {
return db.Collection("test-collection").InsertOne(ctx, bson.D{{Key: "test-item", Value: "test-value"}})
},
mockResponses: []bson.D{{{Key: "ok", Value: 1}}},
excludeCommand: true,
validators: append(commonValidators, func(s sdktrace.ReadOnlySpan) bool {
for _, attr := range s.Attributes() {
Expand All @@ -84,13 +81,17 @@ func TestDBCrudOperation(t *testing.T) {
},
}
for _, tc := range tt {
tc := tc

title := tc.title
if tc.excludeCommand {
title = title + "/excludeCommand"
} else {
title = title + "/includeCommand"
}
t.Run(title, func(t *testing.T) {

mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
mt.Run(title, func(mt *mtest.T) {
sr := tracetest.NewSpanRecorder()
provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))

Expand All @@ -106,53 +107,54 @@ func TestDBCrudOperation(t *testing.T) {
otelmongo.WithCommandAttributeDisabled(tc.excludeCommand),
)
opts.ApplyURI(addr)
client, err := mongo.Connect(ctx, opts)
if err != nil {
t.Fatal(err)
}

_, err = tc.operation(ctx, client.Database("test-database"))
mt.ResetClient(opts)
mt.AddMockResponses(tc.mockResponses...)

_, err := tc.operation(ctx, mt.Client.Database("test-database"))
if err != nil {
t.Error(err)
mt.Error(err)
}

span.End()

spans := sr.Ended()
if !assert.Len(t, spans, 2, "expected 2 spans, received %d", len(spans)) {
t.FailNow()
if !assert.Len(mt, spans, 2, "expected 2 spans, received %d", len(spans)) {
mt.FailNow()
}
assert.Len(t, spans, 2)
assert.Equal(t, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID())
assert.Equal(t, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID())
assert.Equal(t, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID())
assert.Len(mt, spans, 2)
assert.Equal(mt, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID())
assert.Equal(mt, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID())
assert.Equal(mt, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID())

s := spans[0]
assert.Equal(t, trace.SpanKindClient, s.SpanKind())
assert.Equal(mt, trace.SpanKindClient, s.SpanKind())
attrs := s.Attributes()
assert.Contains(t, attrs, attribute.String("db.system", "mongodb"))
assert.Contains(t, attrs, attribute.String("net.peer.name", "localhost"))
assert.Contains(t, attrs, attribute.Int64("net.peer.port", int64(27017)))
assert.Contains(t, attrs, attribute.String("net.transport", "ip_tcp"))
assert.Contains(t, attrs, attribute.String("db.name", "test-database"))
assert.Contains(mt, attrs, attribute.String("db.system", "mongodb"))
assert.Contains(mt, attrs, attribute.String("net.peer.name", "<mock_connection>"))
assert.Contains(mt, attrs, attribute.Int64("net.peer.port", int64(27017)))
assert.Contains(mt, attrs, attribute.String("net.transport", "ip_tcp"))
assert.Contains(mt, attrs, attribute.String("db.name", "test-database"))
for _, v := range tc.validators {
assert.True(t, v(s))
assert.True(mt, v(s))
}
})
}
}

func TestDBCollectionAttribute(t *testing.T) {
tt := []struct {
title string
operation func(context.Context, *mongo.Database) (interface{}, error)
validators []validator
title string
operation func(context.Context, *mongo.Database) (interface{}, error)
mockResponses []bson.D
validators []validator
}{
{
title: "delete",
operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) {
return db.Collection("test-collection").DeleteOne(ctx, bson.D{{Key: "test-item"}})
},
mockResponses: []bson.D{{{Key: "ok", Value: 1}}},
validators: []validator{
func(s sdktrace.ReadOnlySpan) bool {
return assert.Equal(t, "test-collection.delete", s.Name())
Expand All @@ -173,6 +175,12 @@ func TestDBCollectionAttribute(t *testing.T) {
operation: func(ctx context.Context, db *mongo.Database) (interface{}, error) {
return db.ListCollectionNames(ctx, bson.D{})
},
mockResponses: []bson.D{
{
{Key: "ok", Value: 1},
{Key: "cursor", Value: bson.D{{Key: "firstBatch", Value: bson.A{}}}},
},
},
validators: []validator{
func(s sdktrace.ReadOnlySpan) bool {
return assert.Equal(t, "listCollections", s.Name())
Expand All @@ -187,7 +195,10 @@ func TestDBCollectionAttribute(t *testing.T) {
},
}
for _, tc := range tt {
t.Run(tc.title, func(t *testing.T) {
tc := tc

mt := mtest.New(t, mtest.NewOptions().ClientType(mtest.Mock))
mt.Run(tc.title, func(mt *mtest.T) {
sr := tracetest.NewSpanRecorder()
provider := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(sr))

Expand All @@ -203,37 +214,36 @@ func TestDBCollectionAttribute(t *testing.T) {
otelmongo.WithCommandAttributeDisabled(true),
)
opts.ApplyURI(addr)
client, err := mongo.Connect(ctx, opts)
if err != nil {
t.Fatal(err)
}

_, err = tc.operation(ctx, client.Database("test-database"))
mt.ResetClient(opts)
mt.AddMockResponses(tc.mockResponses...)

_, err := tc.operation(ctx, mt.Client.Database("test-database"))
if err != nil {
t.Error(err)
mt.Error(err)
}

span.End()

spans := sr.Ended()
if !assert.Len(t, spans, 2, "expected 2 spans, received %d", len(spans)) {
t.FailNow()
if !assert.Len(mt, spans, 2, "expected 2 spans, received %d", len(spans)) {
mt.FailNow()
}
assert.Len(t, spans, 2)
assert.Equal(t, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID())
assert.Equal(t, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID())
assert.Equal(t, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID())
assert.Len(mt, spans, 2)
assert.Equal(mt, spans[0].SpanContext().TraceID(), spans[1].SpanContext().TraceID())
assert.Equal(mt, spans[0].Parent().SpanID(), spans[1].SpanContext().SpanID())
assert.Equal(mt, span.SpanContext().SpanID(), spans[1].SpanContext().SpanID())

s := spans[0]
assert.Equal(t, trace.SpanKindClient, s.SpanKind())
assert.Equal(mt, trace.SpanKindClient, s.SpanKind())
attrs := s.Attributes()
assert.Contains(t, attrs, attribute.String("db.system", "mongodb"))
assert.Contains(t, attrs, attribute.String("net.peer.name", "localhost"))
assert.Contains(t, attrs, attribute.Int64("net.peer.port", int64(27017)))
assert.Contains(t, attrs, attribute.String("net.transport", "ip_tcp"))
assert.Contains(t, attrs, attribute.String("db.name", "test-database"))
assert.Contains(mt, attrs, attribute.String("db.system", "mongodb"))
assert.Contains(mt, attrs, attribute.String("net.peer.name", "<mock_connection>"))
assert.Contains(mt, attrs, attribute.Int64("net.peer.port", int64(27017)))
assert.Contains(mt, attrs, attribute.String("net.transport", "ip_tcp"))
assert.Contains(mt, attrs, attribute.String("db.name", "test-database"))
for _, v := range tc.validators {
assert.True(t, v(s))
assert.True(mt, v(s))
}
})
}
Expand Down
20 changes: 0 additions & 20 deletions internal/util/testutil.go

This file was deleted.

14 changes: 0 additions & 14 deletions tools/wait.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,6 @@ wait_for_cassandra () {
exit 1
}

wait_for_mongo () {
for ((i = 0; i < 5; ++i)); do
if docker exec "$1" mongosh; then
exit 0
fi
echo "Mongo not yet available..."
sleep 10
done
echo "Timeout waiting for mongo to initialize"
exit 1
}

wait_for_gomemcache () {
for ((i = 0; i < 5; ++i)); do
if nc -z localhost 11211; then
Expand All @@ -49,8 +37,6 @@ fi

if [ "$CMD" == "cassandra" ]; then
wait_for_cassandra "$IMG_NAME"
elif [ "$CMD" == "mongo" ]; then
wait_for_mongo "$IMG_NAME"
elif [ "$CMD" == "gomemcache" ]; then
wait_for_gomemcache
else
Expand Down

0 comments on commit 4122c4b

Please sign in to comment.