Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Helper library for starting containers in e2e tests #306

Merged
merged 4 commits into from
Jun 24, 2020
Merged

Helper library for starting containers in e2e tests #306

merged 4 commits into from
Jun 24, 2020

Conversation

jrcamp
Copy link
Contributor

@jrcamp jrcamp commented Jun 9, 2020

This provides a helper around starting and cleaning up containers as part of
end to end testing. See the redis e2e test as an example of how it would be
used.

Contributes to open-telemetry/opentelemetry-collector#1027

@jrcamp jrcamp requested review from pmcollins, asuresh4 and a team June 9, 2020 21:15
@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Jun 9, 2020

CLA Check
The committers are authorized under a signed CLA.

  • ✅ Jay Camp (9e2e600b83efeafda1617547bb8b6114e6358350)

@jrcamp jrcamp marked this pull request as draft June 9, 2020 21:21
}

// StartContainer starts a container with the given image and zero or more ContainerOptions.
func (c *Containers) StartContainer(image string, opts ...ContainerOption) Container {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might also be useful to be able to pass in path to a Dockerfile from which the image can be built.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep we'll need that eventually.

@@ -45,7 +46,7 @@ func (f *Factory) Type() configmodels.Type {

// CreateDefaultConfig creates a default config.
func (f *Factory) CreateDefaultConfig() configmodels.Receiver {
return &config{}
return &config{CollectionInterval: 10 * time.Second}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this. :-)

Copy link
Contributor

@jchengsfx jchengsfx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add - setup_remote_docker to the test job in the circleci config.

Copy link
Contributor

@owais owais left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to place it under common? It could simple be internal/testing/container.

}

// StartContainer starts a container with the given image and zero or more ContainerOptions.
func (c *Containers) StartContainer(image string, opts ...ContainerOption) Container {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be just Start() so API reads container.Start()? Is there anything else in this package that can be "started"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm we may have other ways to start (like @asuresh4 mentioned building from a Dockerfile and starting it which is something we do in Smart Agent). So there may be more than one kind of start. Maybe this could be StartImage and StartDockerfile in future?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea that sounds good or just Start(WithImage()) Start(WithDockerfile()).


func TestIntegration(t *testing.T) {
d := container.New(t)
c := d.StartContainer("docker.io/library/redis:6.0.3", container.WithPortReady(6379))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are containers stopped? Should a test be responsible for stopping a container it starts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They get stopped automatically by test framework (calls Cleanup). See how it registers itself with t.Cleanup in New().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


func TestIntegration(t *testing.T) {
d := container.New(t)
c := d.StartContainer("docker.io/library/redis:6.0.3", container.WithPortReady(6379))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have a randomPort() int function that returns a randomly available port but not strictly required for this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have that function already somewhere. But yeah it's not needed here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could multiple tests run in parallel in same or different suites that both want to run redis?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They could but it'd still be fine. They'd get assigned different host ports at random.

ports nat.PortMap
}

func (c *Container) AddrForPort(port uint16) string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stay away from the host port bindings as much as possible with this, as long as you are using bridged networking with Docker, you can access the container IP directly from the test process and access any port you want without having to bind it with Docker. The host mappings just create more indirection and potential for port conflicts and have no real benefit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stay away from the host port bindings as much as possible with this, as long as you are using bridged networking with Docker, you can access the container IP directly from the test process and access any port you want without having to bind it with Docker.

Best I could tell after trying and reading the docs this doesn't work on Mac does it? I'm basing that on https://docs.docker.com/docker-for-mac/networking/#per-container-ip-addressing-is-not-possible.

The host mappings just create more indirection and potential for port conflicts and have no real benefit.

The host ports are assigned randomly which is why there's an AddrForPort function that looks up the container port to the mapped host port.

@pmcollins
Copy link
Member

Very cool functionality overall. Am I correct in assuming that the unit test defined here won't be how these kinds of tests (that require Docker to be running) will be run?

c.t.Fatalf("failed inspecting container %v: %v", created.ID, err)
}

if con.NetworkSettings.IPAddress == "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Access the container with this IP address instead of host port bindings. If you do that, you don't need to look at con.NetworkSettings.Ports at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I addressed this here: #306 (comment)

This approach seems to work on both Mac and CircleCI and haven't found any downsides to it (yet).

@jrcamp
Copy link
Contributor Author

jrcamp commented Jun 10, 2020

Very cool functionality overall. Am I correct in assuming that the unit test defined here won't be how these kinds of tests (that require Docker to be running) will be run?

Yeah I think for an end to end test it needs to be constructing things at a higher level to get more coverage so I'm going to experiment with that. (Maybe generating a yaml config file dynamically?) Or did you mean something else?

for _, port := range container.waitForPorts {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)

func() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty hacky way to make use of defer.

@pmcollins
Copy link
Member

Very cool functionality overall. Am I correct in assuming that the unit test defined here won't be how these kinds of tests (that require Docker to be running) will be run?

Yeah I think for an end to end test it needs to be constructing things at a higher level to get more coverage so I'm going to experiment with that. (Maybe generating a yaml config file dynamically?) Or did you mean something else?

I was just thinking that any tests that run when you say go test shouldn't require a service to be running in the background, such as Docker. So I was wondering if you were in agreement with that, and if so, what mechanism were you planning on for running these tests.

@jrcamp
Copy link
Contributor Author

jrcamp commented Jun 10, 2020

Very cool functionality overall. Am I correct in assuming that the unit test defined here won't be how these kinds of tests (that require Docker to be running) will be run?

Yeah I think for an end to end test it needs to be constructing things at a higher level to get more coverage so I'm going to experiment with that. (Maybe generating a yaml config file dynamically?) Or did you mean something else?

I was just thinking that any tests that run when you say go test shouldn't require a service to be running in the background, such as Docker. So I was wondering if you were in agreement with that, and if so, what mechanism were you planning on for running these tests.

Oh I see. Yeah when you run go test you will just get unit tests. To run these tests you will have to do something like go test -tags=integration ./...

@jrcamp
Copy link
Contributor Author

jrcamp commented Jun 10, 2020

Any reason to place it under common? It could simple be internal/testing/container.

Would like to but internal doesn't have a go.mod. Not sure if it can/should be reorganized or not.

@jrcamp jrcamp marked this pull request as ready for review June 11, 2020 03:51
@jrcamp jrcamp added the WIP label Jun 11, 2020
@jrcamp
Copy link
Contributor Author

jrcamp commented Jun 11, 2020

Marking ready to review just so that coverage bot will run (assuming that's why it's not..) but it's still WIP.

@codecov
Copy link

codecov bot commented Jun 11, 2020

Codecov Report

Merging #306 into master will increase coverage by 0.03%.
The diff coverage is 74.11%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #306      +/-   ##
==========================================
+ Coverage   82.78%   82.82%   +0.03%     
==========================================
  Files         170      171       +1     
  Lines        9070     9147      +77     
==========================================
+ Hits         7509     7576      +67     
+ Misses       1236     1235       -1     
- Partials      325      336      +11     
Flag Coverage Δ
#integration 63.40% <74.11%> (+63.40%) ⬆️
#unit 82.59% <0.00%> (-0.19%) ⬇️
Impacted Files Coverage Δ
internal/common/testing/container/container.go 73.80% <73.80%> (ø)
receiver/redisreceiver/factory.go 40.00% <100.00%> (+40.00%) ⬆️
receiver/kubeletstatsreceiver/factory.go 64.10% <0.00%> (-35.90%) ⬇️
receiver/kubeletstatsreceiver/kubelet/client.go 92.85% <0.00%> (-2.85%) ⬇️
receiver/redisreceiver/receiver.go 89.47% <0.00%> (+89.47%) ⬆️
receiver/redisreceiver/client.go 100.00% <0.00%> (+100.00%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a00790d...6597bc2. Read the comment docs.

@jrcamp jrcamp marked this pull request as draft June 12, 2020 22:58
This provides a helper around starting and cleaning up containers as part of
end to end testing. See the redis e2e test as an example of how it would be
used.

The test library itself Doesn't have tests yet, mainly proof of concept and to
get feedback.

open-telemetry/opentelemetry-collector#1027
@jrcamp jrcamp marked this pull request as ready for review June 18, 2020 15:59
@jrcamp jrcamp removed the WIP label Jun 18, 2020
@jrcamp
Copy link
Contributor Author

jrcamp commented Jun 18, 2020

This is ready for review now. The coverage is a bit low due to a lot of error paths that would require mocking a lot of Docker API calls which is probably not worth doing.

Copy link
Member

@asuresh4 asuresh4 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Just a couple of nits. Also, looks like e2e.yaml is empty and does not seem to be used anywhere. Is it intentional?


con.Cleanup()

assert.Nil(t, con.runningContainers, "started containers should be empty after cleanup")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you like to use the corresponding method from the require module instead? So we don't need to import both require and assert modules?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require and assert have different behaviors. Usually you use require if you want the test to fail immediately whereas asserts continue execution of the test so that additional assertions can run.

t: t,
}))

assert.Eventuallyf(t, func() bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing about using methods from require.

}))

assert.Eventuallyf(t, func() bool {
return len(consumer.AllMetrics()) > 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to actually look for metrics by name (like in the Smart Agent tests). But I guess we can get to it once we have metric metadata.

@jrcamp
Copy link
Contributor Author

jrcamp commented Jun 22, 2020

LGTM. Just a couple of nits. Also, looks like e2e.yaml is empty and does not seem to be used anywhere. Is it intentional?

Yep not needed, removed.

@jrcamp
Copy link
Contributor Author

jrcamp commented Jun 23, 2020

@tigrannajaryan ready for merge. coverage is lower due to error conditions being hard to test without mocking docker library

@tigrannajaryan tigrannajaryan merged commit 9bc1ce9 into open-telemetry:master Jun 24, 2020
wyTrivail referenced this pull request in mxiamxia/opentelemetry-collector-contrib Jul 13, 2020
This provides a helper around starting and cleaning up containers as part of
end to end testing. See the redis e2e test as an example of how it would be
used.

The test library itself Doesn't have tests yet, mainly proof of concept and to
get feedback.

Contributes to open-telemetry/opentelemetry-collector#1027
mxiamxia referenced this pull request in mxiamxia/opentelemetry-collector-contrib Jul 22, 2020
ljmsc referenced this pull request in ljmsc/opentelemetry-collector-contrib Feb 21, 2022
Change all occurrences of value to pointer receivers
Add meta sys files to .gitignore
Code cleanup e.g.
- Don't capitalize error statements
- Fix ignored errors
- Fix ambiguous variable naming
- Remove unnecessary type casting
- Use named params

Fix #306
codeboten added a commit that referenced this pull request Nov 23, 2022
The InMemorySpanExporter provides a friendly interface to retrieving span information, reducing the need for mocking in unit tests.

Signed-off-by: Alex Boten <[email protected]>
codeboten pushed a commit that referenced this pull request Nov 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants