Skip to content

Commit

Permalink
perf: pre-fetch all images before running tests (runfinch#131)
Browse files Browse the repository at this point in the history
Issue #, if available:

*Description of changes:*
- Pre-fetches all images before running tests
- The bulk of the change is in
[`tests/tests.go`](https://github.com/runfinch/common-tests/compare/main...pendo324:common-tests:pre-fetch-images?expand=1#diff-a61afa8338f0442d1308258f7320c1a4a4698b432ac42d23bca256980f16f75a)
- This drastically reduces the possibility of tests timing out due to
external network download speed

There might be a better way to implement this, this was just the first
way I thought of. Please post suggestions if you have any

*Testing done:*
- Tested locally against latest Finch main branch


- [x] I've reviewed the guidance in CONTRIBUTING.md


#### License Acceptance

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.

---------

Signed-off-by: Justin Alvarez <[email protected]>
  • Loading branch information
pendo324 authored and Shubhranshu153 committed Mar 26, 2024
1 parent 038e5ef commit 9c6adbf
Show file tree
Hide file tree
Showing 42 changed files with 332 additions and 219 deletions.
8 changes: 4 additions & 4 deletions command/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ func RemoveAll(o *option.Option) {

// RemoveContainers removes all containers in the testing environment specified by o.
func RemoveContainers(o *option.Option) {
allIds := GetAllContainerIDs(o)
allIDs := GetAllContainerIDs(o)
var ids []string
for _, id := range allIds {
for _, id := range allIDs {
if id != localRegistryContainerID {
ids = append(ids, id)
}
Expand All @@ -46,9 +46,9 @@ func RemoveVolumes(o *option.Option) {

// RemoveImages removes all container images in the testing environment specified by o.
func RemoveImages(o *option.Option) {
allIds := GetAllImageIDs(o)
allIDs := GetAllImageIDs(o)
var ids []string
for _, id := range allIds {
for _, id := range allIDs {
if id != localRegistryImageID {
ids = append(ids, id)
}
Expand Down
2 changes: 1 addition & 1 deletion run/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestRun(t *testing.T) {
ginkgo.SynchronizedBeforeSuite(func() []byte {
tests.SetupLocalRegistry(o)
return nil
}, func(bytes []byte) {})
}, func(_ []byte) {})

ginkgo.SynchronizedAfterSuite(func() {
tests.CleanupLocalRegistry(o)
Expand Down
18 changes: 9 additions & 9 deletions tests/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func Build(o *option.Option) {
ginkgo.BeforeEach(func() {
buildContext = ffs.CreateBuildContext(fmt.Sprintf(`FROM %s
CMD ["echo", "finch-test-dummy-output"]
`, defaultImage))
`, localImages[defaultImage]))
ginkgo.DeferCleanup(os.RemoveAll, buildContext)
command.RemoveAll(o)
})
Expand All @@ -53,7 +53,7 @@ func Build(o *option.Option) {
dockerFilePath = filepath.Join(buildContext, "AnotherDockerfile")
ffs.WriteFile(dockerFilePath, fmt.Sprintf(`FROM %s
RUN ["echo", "built from AnotherDockerfile"]
`, defaultImage))
`, localImages[defaultImage]))
})

for _, file := range []string{"-f", "--file"} {
Expand All @@ -68,7 +68,7 @@ func Build(o *option.Option) {
ginkgo.It("build image with --secret option", func() {
containerWithSecret := fmt.Sprintf(`FROM %s
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
`, defaultImage)
`, localImages[defaultImage])
dockerFilePath := filepath.Join(buildContext, "Dockerfile.with-secret")
ffs.WriteFile(dockerFilePath, containerWithSecret)
secretFile := filepath.Join(buildContext, "secret.txt")
Expand All @@ -83,7 +83,7 @@ func Build(o *option.Option) {
RUN echo output from build_env
FROM %s AS prod_env
RUN echo "output from prod_env
`, defaultImage, defaultImage)
`, localImages[defaultImage], localImages[defaultImage])
dockerFilePath := filepath.Join(buildContext, "Dockerfile.with-target")
ffs.WriteFile(dockerFilePath, containerWithTarget)
stdEr := command.Stderr(o, "build", "--progress=plain", "--no-cache",
Expand All @@ -110,7 +110,7 @@ func Build(o *option.Option) {
ginkgo.It("build image with --progress=plain", func() {
dockerFile := fmt.Sprintf(`FROM %s
RUN echo "progress flag set:$((1 + 1))"
`, defaultImage)
`, localImages[defaultImage])
dockerFilePath := filepath.Join(buildContext, "Dockerfile.progress")
ffs.WriteFile(dockerFilePath, dockerFile)
stdErr := command.Stderr(o, "build", "-f", dockerFilePath, "--no-cache", "--progress=plain", buildContext)
Expand All @@ -133,7 +133,7 @@ func Build(o *option.Option) {
}
containerWithSSH := fmt.Sprintf(`FROM %s
RUN ["echo", "built from Dockerfile.with-ssh"]
`, defaultImage)
`, localImages[defaultImage])
dockerFilePath := filepath.Join(buildContext, "Dockerfile.with-ssh")
ffs.WriteFile(dockerFilePath, containerWithSSH)
stdErr := command.Stderr(o, "build", "--ssh", "default", "-f", dockerFilePath, buildContext)
Expand All @@ -158,7 +158,7 @@ func Build(o *option.Option) {
fileName: "Dockerfile.NoEnv",
instructions: fmt.Sprintf(`FROM %s
ENV PATH
`, defaultImage),
`, localImages[defaultImage]),
errorMessage: "ENV must have two arguments",
},
{
Expand Down Expand Up @@ -195,10 +195,10 @@ func Build(o *option.Option) {
command.RemoveAll(o)
})
// If SetupLocalRegistry is invoked before this test case,
// then defaultImage will point to the image in the local registry,
// then localImages[defaultImage] will point to the image in the local registry,
// and there will be only one platform (i.e., the platform of the running machine) available for that image in the local registry.
// As a result, to make this test case not flaky even when SetupLocalRegistry is used,
// we need to pull alpineImage instead of defaultImage
// we need to pull alpineImage instead of localImages[defaultImage]
// because we can be sure that the registry associated with the former provides the image with the platform specified below.
ginkgo.It("build basic alpine image with --platform option", func() {
command.Run(o, "build", "-t", testImageName, "--platform=amd64", buildContext)
Expand Down
2 changes: 1 addition & 1 deletion tests/builder_prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func BuilderPrune(o *option.Option) {
ginkgo.BeforeEach(func() {
buildContext = ffs.CreateBuildContext(fmt.Sprintf(`FROM %s
CMD ["echo", "finch-test-dummy-output"]
`, defaultImage))
`, localImages[defaultImage]))
ginkgo.DeferCleanup(os.RemoveAll, buildContext)
command.RemoveAll(o)
})
Expand Down
6 changes: 3 additions & 3 deletions tests/compose_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func ComposeBuild(o *option.Option) {
gomega.Expect(imageList).Should(gomega.ContainElement(gomega.HaveSuffix(imageSuffix[0])))
gomega.Expect(imageList).Should(gomega.ContainElement(gomega.HaveSuffix(imageSuffix[1])))
// The built image should print 'Compose build test' when run.
output := command.StdoutStr(o, "run", defaultImage)
output := command.StdoutStr(o, "run", localImages[defaultImage])
gomega.Expect(output).Should(gomega.Equal("Compose build test"))
})

Expand Down Expand Up @@ -93,7 +93,7 @@ ARG CMD_MSG="Compose build test"
RUN printf "should only see the final answer when '--progress' is set to be 'plain': %%d\n" $(expr 1 + 1)
ENV ENV_CMD_MSG=${CMD_MSG}
CMD echo ${ENV_CMD_MSG}
`, defaultImage)
`, localImages[defaultImage])

composeYmlContent := fmt.Sprintf(
`
Expand All @@ -107,7 +107,7 @@ services:
build:
context: .
dockerfile: Dockerfile
`, serviceNames[0], serviceNames[1], defaultImage)
`, serviceNames[0], serviceNames[1], localImages[defaultImage])

composeDir, composeFilePath := ffs.CreateComposeYmlContext(composeYmlContent)
ffs.WriteFile(filepath.Join(composeDir, "Dockerfile"), dockerFileContent)
Expand Down
2 changes: 1 addition & 1 deletion tests/compose_down.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,6 @@ services:
- compose_data_volume:/usr/local/data
volumes:
compose_data_volume:
`, serviceNames[0], serviceNames[1], defaultImage, containerNames[0], containerNames[1])
`, serviceNames[0], serviceNames[1], localImages[defaultImage], containerNames[0], containerNames[1])
return ffs.CreateComposeYmlContext(composeYmlContent)
}
2 changes: 1 addition & 1 deletion tests/compose_kill.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,6 @@ services:
image: "%[3]s"
container_name: "%[5]s"
command: sleep infinity
`, serviceNames[0], serviceNames[1], defaultImage, containerNames[0], containerNames[1])
`, serviceNames[0], serviceNames[1], localImages[defaultImage], containerNames[0], containerNames[1])
return ffs.CreateComposeYmlContext(composeYmlContent)
}
2 changes: 1 addition & 1 deletion tests/compose_logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
func ComposeLogs(o *option.Option) {
services := []string{"svc1_compose_logs", "svc2_compose_logs"}
containerNames := []string{"container1_compose_logs", "container2_compose_logs"}
imageNames := []string{defaultImage, defaultImage}
imageNames := []string{localImages[defaultImage], localImages[defaultImage]}

ginkgo.Describe("Compose logs command", func() {
var buildContext string
Expand Down
2 changes: 1 addition & 1 deletion tests/compose_ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
func ComposePs(o *option.Option) {
services := []string{"svc1_compose_ps", "svc2_compose_ps"}
containerNames := []string{"container1_compose_ps", "container2_compose_ps"}
imageNames := []string{defaultImage, defaultImage}
imageNames := []string{localImages[defaultImage], localImages[defaultImage]}

ginkgo.Describe("Compose ps command", func() {
var composeContext string
Expand Down
2 changes: 1 addition & 1 deletion tests/compose_pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
// ComposePull tests functionality of `compose pull` command.
func ComposePull(o *option.Option) {
services := []string{"svc1_compose_pull", "svc2_compose_pull"}
imageNames := []string{defaultImage, olderAlpineImage}
imageNames := []string{localImages[defaultImage], localImages[olderAlpineImage]}
ginkgo.Describe("Compose pull command", func() {
var composeContext string
var composeFilePath string
Expand Down
6 changes: 3 additions & 3 deletions tests/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func Cp(o *option.Option) {

ginkgo.When("the container is running", func() {
ginkgo.BeforeEach(func() {
command.Run(o, "run", "-d", "--name", testContainerName, defaultImage, "sleep", "infinity")
command.Run(o, "run", "-d", "--name", testContainerName, localImages[defaultImage], "sleep", "infinity")
})

ginkgo.It("should be able to copy file from host to container", func() {
Expand Down Expand Up @@ -105,7 +105,7 @@ func Cp(o *option.Option) {

ginkgo.When("the container is not running", func() {
ginkgo.It("should be able to copy file from host to container", func() {
command.Run(o, "run", "--name", testContainerName, defaultImage, "sleep", "5")
command.Run(o, "run", "--name", testContainerName, localImages[defaultImage], "sleep", "5")
command.Run(o, "stop", testContainerName)
path := ffs.CreateTempFile(filename, content)
ginkgo.DeferCleanup(os.RemoveAll, filepath.Dir(path))
Expand All @@ -119,7 +119,7 @@ func Cp(o *option.Option) {

ginkgo.It("should be able to copy file from container to host", func() {
cmd := fmt.Sprintf("echo -n %s > %s", content, containerFilepath)
command.Run(o, "run", "--name", testContainerName, defaultImage, "sh", "-c", cmd)
command.Run(o, "run", "--name", testContainerName, localImages[defaultImage], "sh", "-c", cmd)
fileDir := ffs.CreateTempDir("finch-test")
path := filepath.Join(fileDir, filename)
ginkgo.DeferCleanup(os.RemoveAll, fileDir)
Expand Down
2 changes: 1 addition & 1 deletion tests/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func Create(o *option.Option) {
})

ginkgo.It("should create a container and able to start the container", func() {
command.Run(o, "create", "--name", testContainerName, defaultImage, "sleep", "infinity")
command.Run(o, "create", "--name", testContainerName, localImages[defaultImage], "sleep", "infinity")
status := command.StdoutStr(o, "ps", "-a", "--filter", fmt.Sprintf("name=%s", testContainerName), "--format", "{{.Status}}")
gomega.Expect(status).Should(gomega.Equal("Created"))

Expand Down
4 changes: 2 additions & 2 deletions tests/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ func Events(o *option.Option) {
session := command.RunWithoutWait(o, "system", "events")
defer session.Kill()
gomega.Expect(session.Out.Contents()).Should(gomega.BeEmpty())
command.Run(o, "pull", defaultImage)
command.Run(o, "pull", localImages[defaultImage])
// allow propagation time
gomega.Eventually(func(session *gexec.Session) string {
return strings.TrimSpace(string(session.Out.Contents()))
}).WithArguments(session).
WithTimeout(15 * time.Second).
WithPolling(1 * time.Second).
Should(gomega.ContainSubstring(defaultImage))
Should(gomega.ContainSubstring(localImages[defaultImage]))
})
})
}
4 changes: 2 additions & 2 deletions tests/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func Exec(o *option.Option) {
// TODO: specifying -t flag will have error in test -> panic: provided file is not a console
ginkgo.When("then container is running", func() {
ginkgo.BeforeEach(func() {
command.Run(o, "run", "-d", "--name", testContainerName, defaultImage, "sleep", "infinity")
command.Run(o, "run", "-d", "--name", testContainerName, localImages[defaultImage], "sleep", "infinity")
})

ginkgo.It("should execute a command in a running container", func() {
Expand Down Expand Up @@ -109,7 +109,7 @@ func Exec(o *option.Option) {
})

ginkgo.It("should not execute a command when the container is not running", func() {
command.Run(o, "run", "--name", testContainerName, defaultImage)
command.Run(o, "run", "--name", testContainerName, localImages[defaultImage])
command.RunWithoutSuccessfulExit(o, "exec", testContainerName)
})
})
Expand Down
10 changes: 5 additions & 5 deletions tests/image_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,35 @@ func ImageHistory(o *option.Option) {
ginkgo.Describe("show the history of an image", func() {
ginkgo.BeforeEach(func() {
command.RemoveAll(o)
pullImage(o, defaultImage)
pullImage(o, localImages[defaultImage])
})

ginkgo.AfterEach(func() {
command.RemoveAll(o)
})

ginkgo.It("should display image history", func() {
gomega.Expect(command.StdoutStr(o, "image", "history", defaultImage)).ShouldNot(gomega.BeEmpty())
gomega.Expect(command.StdoutStr(o, "image", "history", localImages[defaultImage])).ShouldNot(gomega.BeEmpty())
})

for _, quiet := range []string{"-q", "--quiet"} {
quiet := quiet
ginkgo.It(fmt.Sprintf("should only display snapshot ID with %s flag", quiet), func() {
ids := removeMissingID(command.StdoutAsLines(o, "image", "history", quiet, defaultImage))
ids := removeMissingID(command.StdoutAsLines(o, "image", "history", quiet, localImages[defaultImage]))
gomega.Expect(ids).Should(gomega.HaveEach(gomega.MatchRegexp(sha256RegexFull)))
})
}

ginkgo.It("should only display snapshot ID with --format flag", func() {
ids := removeMissingID(command.StdoutAsLines(o, "image", "history", defaultImage, "--format", "{{.Snapshot}}"))
ids := removeMissingID(command.StdoutAsLines(o, "image", "history", localImages[defaultImage], "--format", "{{.Snapshot}}"))
gomega.Expect(ids).Should(gomega.HaveEach(gomega.MatchRegexp(sha256RegexFull)))
})

ginkgo.It("should display image history with --no-trunc flag", func() {
const text = "a very very very very long test phrase that only serves for testing purpose"
buildContext := ffs.CreateBuildContext(fmt.Sprintf(`FROM %s
CMD ["echo", %s]
`, defaultImage, text))
`, localImages[defaultImage], text))
ginkgo.DeferCleanup(os.RemoveAll, buildContext)

command.Run(o, "build", "-t", testImageName, buildContext)
Expand Down
22 changes: 15 additions & 7 deletions tests/image_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,33 @@ func ImageInspect(o *option.Option) {
ginkgo.Describe("display detailed information on one or more images", func() {
ginkgo.BeforeEach(func() {
command.RemoveAll(o)
pullImage(o, defaultImage)
pullImage(o, localImages[defaultImage])
})
ginkgo.AfterEach(func() {
command.RemoveAll(o)
})

ginkgo.It("should display detailed information on an image", func() {
gomega.Expect(command.StdoutStr(o, "image", "inspect", defaultImage)).ShouldNot(gomega.BeEmpty())
gomega.Expect(command.StdoutStr(o, "image", "inspect", localImages[defaultImage])).ShouldNot(gomega.BeEmpty())
})

ginkgo.It("should display image RepoTags with --format flag", func() {
image := command.StdoutStr(o, "image", "inspect", defaultImage, "--format", "{{(index .RepoTags 0)}}")
gomega.Expect(image).Should(gomega.Equal(defaultImage))
image := command.StdoutStr(o, "image", "inspect", localImages[defaultImage], "--format", "{{(index .RepoTags 0)}}")
gomega.Expect(image).Should(gomega.Equal(localImages[defaultImage]))
})

ginkgo.It("should display multiple image RepoTags with --format flag", func() {
pullImage(o, olderAlpineImage)
lines := command.StdoutAsLines(o, "image", "inspect", defaultImage, olderAlpineImage, "--format", "{{(index .RepoTags 0)}}")
gomega.Expect(lines).Should(gomega.ConsistOf(defaultImage, olderAlpineImage))
pullImage(o, localImages[olderAlpineImage])
lines := command.StdoutAsLines(
o,
"image",
"inspect",
localImages[defaultImage],
localImages[olderAlpineImage],
"--format",
"{{(index .RepoTags 0)}}",
)
gomega.Expect(lines).Should(gomega.ConsistOf(localImages[defaultImage], localImages[olderAlpineImage]))
})

ginkgo.It("should not display information if image doesn't exist", func() {
Expand Down
20 changes: 10 additions & 10 deletions tests/image_prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,38 +21,38 @@ func ImagePrune(o *option.Option) {
ginkgo.Describe("Remove unused images", func() {
ginkgo.BeforeEach(func() {
command.RemoveAll(o)
pullImage(o, defaultImage)
pullImage(o, localImages[defaultImage])
})
ginkgo.AfterEach(func() {
command.RemoveAll(o)
})

ginkgo.It("should remove all unused images with inputting y in prompt confirmation", func() {
imageShouldExist(o, defaultImage)
imageShouldExist(o, localImages[defaultImage])
command.New(o, "image", "prune", "-a").WithStdin(gbytes.BufferWithBytes([]byte("y"))).Run()
imageShouldNotExist(o, defaultImage)
imageShouldNotExist(o, localImages[defaultImage])
})

ginkgo.It("should not remove any unused image with inputting n in prompt confirmation", func() {
imageShouldExist(o, defaultImage)
imageShouldExist(o, localImages[defaultImage])
command.New(o, "image", "prune", "-a").WithStdin(gbytes.BufferWithBytes([]byte("n"))).Run()
imageShouldExist(o, defaultImage)
imageShouldExist(o, localImages[defaultImage])
})

for _, force := range []string{"-f", "--force"} {
force := force
ginkgo.It(fmt.Sprintf("with %s flag, should remove unused images without prompting a confirmation", force), func() {
imageShouldExist(o, defaultImage)
imageShouldExist(o, localImages[defaultImage])
command.Run(o, "image", "prune", "-a", "-f")
imageShouldNotExist(o, defaultImage)
imageShouldNotExist(o, localImages[defaultImage])
})
}

ginkgo.It("should not remove an image if it's used by a dead container", func() {
command.Run(o, "run", defaultImage)
imageShouldExist(o, defaultImage)
command.Run(o, "run", localImages[defaultImage])
imageShouldExist(o, localImages[defaultImage])
command.Run(o, "image", "prune", "-a", "-f")
imageShouldExist(o, defaultImage)
imageShouldExist(o, localImages[defaultImage])
})
})
}
Loading

0 comments on commit 9c6adbf

Please sign in to comment.