-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Conversation
} | ||
|
||
// StartContainer starts a container with the given image and zero or more ContainerOptions. | ||
func (c *Containers) StartContainer(image string, opts ...ContainerOption) Container { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for fixing this. :-)
There was a problem hiding this 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.
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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"?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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().
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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 == "" { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
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() { |
There was a problem hiding this comment.
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
.
I was just thinking that any tests that run when you say |
Oh I see. Yeah when you run |
Would like to but internal doesn't have a go.mod. Not sure if it can/should be reorganized or not. |
Marking ready to review just so that coverage bot will run (assuming that's why it's not..) but it's still WIP. |
Codecov Report
@@ 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
Continue to review full report at Codecov.
|
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
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. |
There was a problem hiding this 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") |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
Yep not needed, removed. |
@tigrannajaryan ready for merge. coverage is lower due to error conditions being hard to test without mocking docker library |
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
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
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]>
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