add better generate

Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
This commit is contained in:
Jess Frazelle 2018-03-20 01:33:56 -04:00
parent 3fc6abf56b
commit cdd93563f5
5655 changed files with 1187011 additions and 392 deletions

View file

@ -0,0 +1,189 @@
package environment // import "github.com/docker/docker/internal/test/environment"
import (
"regexp"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/gotestyourself/gotestyourself/assert"
"golang.org/x/net/context"
)
type testingT interface {
assert.TestingT
logT
Fatalf(string, ...interface{})
}
type logT interface {
Logf(string, ...interface{})
}
// Clean the environment, preserving protected objects (images, containers, ...)
// and removing everything else. It's meant to run after any tests so that they don't
// depend on each others.
func (e *Execution) Clean(t testingT) {
client := e.APIClient()
platform := e.OSType
if (platform != "windows") || (platform == "windows" && e.DaemonInfo.Isolation == "hyperv") {
unpauseAllContainers(t, client)
}
deleteAllContainers(t, client, e.protectedElements.containers)
deleteAllImages(t, client, e.protectedElements.images)
deleteAllVolumes(t, client, e.protectedElements.volumes)
deleteAllNetworks(t, client, platform, e.protectedElements.networks)
if platform == "linux" {
deleteAllPlugins(t, client, e.protectedElements.plugins)
}
}
func unpauseAllContainers(t assert.TestingT, client client.ContainerAPIClient) {
ctx := context.Background()
containers := getPausedContainers(ctx, t, client)
if len(containers) > 0 {
for _, container := range containers {
err := client.ContainerUnpause(ctx, container.ID)
assert.Check(t, err, "failed to unpause container %s", container.ID)
}
}
}
func getPausedContainers(ctx context.Context, t assert.TestingT, client client.ContainerAPIClient) []types.Container {
filter := filters.NewArgs()
filter.Add("status", "paused")
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
Filters: filter,
Quiet: true,
All: true,
})
assert.Check(t, err, "failed to list containers")
return containers
}
var alreadyExists = regexp.MustCompile(`Error response from daemon: removal of container (\w+) is already in progress`)
func deleteAllContainers(t assert.TestingT, apiclient client.ContainerAPIClient, protectedContainers map[string]struct{}) {
ctx := context.Background()
containers := getAllContainers(ctx, t, apiclient)
if len(containers) == 0 {
return
}
for _, container := range containers {
if _, ok := protectedContainers[container.ID]; ok {
continue
}
err := apiclient.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{
Force: true,
RemoveVolumes: true,
})
if err == nil || client.IsErrNotFound(err) || alreadyExists.MatchString(err.Error()) || isErrNotFoundSwarmClassic(err) {
continue
}
assert.Check(t, err, "failed to remove %s", container.ID)
}
}
func getAllContainers(ctx context.Context, t assert.TestingT, client client.ContainerAPIClient) []types.Container {
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
Quiet: true,
All: true,
})
assert.Check(t, err, "failed to list containers")
return containers
}
func deleteAllImages(t testingT, apiclient client.ImageAPIClient, protectedImages map[string]struct{}) {
images, err := apiclient.ImageList(context.Background(), types.ImageListOptions{})
assert.Check(t, err, "failed to list images")
ctx := context.Background()
for _, image := range images {
tags := tagsFromImageSummary(image)
if len(tags) == 0 {
t.Logf("Removing image %s", image.ID)
removeImage(ctx, t, apiclient, image.ID)
continue
}
for _, tag := range tags {
if _, ok := protectedImages[tag]; !ok {
t.Logf("Removing image %s", tag)
removeImage(ctx, t, apiclient, tag)
continue
}
}
}
}
func removeImage(ctx context.Context, t assert.TestingT, apiclient client.ImageAPIClient, ref string) {
_, err := apiclient.ImageRemove(ctx, ref, types.ImageRemoveOptions{
Force: true,
})
if client.IsErrNotFound(err) {
return
}
assert.Check(t, err, "failed to remove image %s", ref)
}
func deleteAllVolumes(t assert.TestingT, c client.VolumeAPIClient, protectedVolumes map[string]struct{}) {
volumes, err := c.VolumeList(context.Background(), filters.Args{})
assert.Check(t, err, "failed to list volumes")
for _, v := range volumes.Volumes {
if _, ok := protectedVolumes[v.Name]; ok {
continue
}
err := c.VolumeRemove(context.Background(), v.Name, true)
// Docker EE may list volumes that no longer exist.
if isErrNotFoundSwarmClassic(err) {
continue
}
assert.Check(t, err, "failed to remove volume %s", v.Name)
}
}
func deleteAllNetworks(t assert.TestingT, c client.NetworkAPIClient, daemonPlatform string, protectedNetworks map[string]struct{}) {
networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
assert.Check(t, err, "failed to list networks")
for _, n := range networks {
if n.Name == "bridge" || n.Name == "none" || n.Name == "host" {
continue
}
if _, ok := protectedNetworks[n.ID]; ok {
continue
}
if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" {
// nat is a pre-defined network on Windows and cannot be removed
continue
}
err := c.NetworkRemove(context.Background(), n.ID)
assert.Check(t, err, "failed to remove network %s", n.ID)
}
}
func deleteAllPlugins(t assert.TestingT, c client.PluginAPIClient, protectedPlugins map[string]struct{}) {
plugins, err := c.PluginList(context.Background(), filters.Args{})
// Docker EE does not allow cluster-wide plugin management.
if client.IsErrNotImplemented(err) {
return
}
assert.Check(t, err, "failed to list plugins")
for _, p := range plugins {
if _, ok := protectedPlugins[p.Name]; ok {
continue
}
err := c.PluginRemove(context.Background(), p.Name, types.PluginRemoveOptions{Force: true})
assert.Check(t, err, "failed to remove plugin %s", p.ID)
}
}
// Swarm classic aggregates node errors and returns a 500 so we need to check
// the error string instead of just IsErrNotFound().
func isErrNotFoundSwarmClassic(err error) bool {
return err != nil && strings.Contains(strings.ToLower(err.Error()), "no such")
}

View file

@ -0,0 +1,149 @@
package environment // import "github.com/docker/docker/internal/test/environment"
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/integration-cli/fixtures/load"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
// Execution contains information about the current test execution and daemon
// under test
type Execution struct {
client client.APIClient
DaemonInfo types.Info
OSType string
PlatformDefaults PlatformDefaults
protectedElements protectedElements
}
// PlatformDefaults are defaults values for the platform of the daemon under test
type PlatformDefaults struct {
BaseImage string
VolumesConfigPath string
ContainerStoragePath string
}
// New creates a new Execution struct
func New() (*Execution, error) {
client, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return nil, errors.Wrapf(err, "failed to create client")
}
info, err := client.Info(context.Background())
if err != nil {
return nil, errors.Wrapf(err, "failed to get info from daemon")
}
osType := getOSType(info)
return &Execution{
client: client,
DaemonInfo: info,
OSType: osType,
PlatformDefaults: getPlatformDefaults(info, osType),
protectedElements: newProtectedElements(),
}, nil
}
func getOSType(info types.Info) string {
// Docker EE does not set the OSType so allow the user to override this value.
userOsType := os.Getenv("TEST_OSTYPE")
if userOsType != "" {
return userOsType
}
return info.OSType
}
func getPlatformDefaults(info types.Info, osType string) PlatformDefaults {
volumesPath := filepath.Join(info.DockerRootDir, "volumes")
containersPath := filepath.Join(info.DockerRootDir, "containers")
switch osType {
case "linux":
return PlatformDefaults{
BaseImage: "scratch",
VolumesConfigPath: toSlash(volumesPath),
ContainerStoragePath: toSlash(containersPath),
}
case "windows":
baseImage := "microsoft/windowsservercore"
if override := os.Getenv("WINDOWS_BASE_IMAGE"); override != "" {
baseImage = override
fmt.Println("INFO: Windows Base image is ", baseImage)
}
return PlatformDefaults{
BaseImage: baseImage,
VolumesConfigPath: filepath.FromSlash(volumesPath),
ContainerStoragePath: filepath.FromSlash(containersPath),
}
default:
panic(fmt.Sprintf("unknown OSType for daemon: %s", osType))
}
}
// Make sure in context of daemon, not the local platform. Note we can't
// use filepath.FromSlash or ToSlash here as they are a no-op on Unix.
func toSlash(path string) string {
return strings.Replace(path, `\`, `/`, -1)
}
// IsLocalDaemon is true if the daemon under test is on the same
// host as the test process.
//
// Deterministically working out the environment in which CI is running
// to evaluate whether the daemon is local or remote is not possible through
// a build tag.
//
// For example Windows to Linux CI under Jenkins tests the 64-bit
// Windows binary build with the daemon build tag, but calls a remote
// Linux daemon.
//
// We can't just say if Windows then assume the daemon is local as at
// some point, we will be testing the Windows CLI against a Windows daemon.
//
// Similarly, it will be perfectly valid to also run CLI tests from
// a Linux CLI (built with the daemon tag) against a Windows daemon.
func (e *Execution) IsLocalDaemon() bool {
return os.Getenv("DOCKER_REMOTE_DAEMON") == ""
}
// IsRemoteDaemon is true if the daemon under test is on different host
// as the test process.
func (e *Execution) IsRemoteDaemon() bool {
return !e.IsLocalDaemon()
}
// Print the execution details to stdout
// TODO: print everything
func (e *Execution) Print() {
if e.IsLocalDaemon() {
fmt.Println("INFO: Testing against a local daemon")
} else {
fmt.Println("INFO: Testing against a remote daemon")
}
}
// APIClient returns an APIClient connected to the daemon under test
func (e *Execution) APIClient() client.APIClient {
return e.client
}
// EnsureFrozenImagesLinux loads frozen test images into the daemon
// if they aren't already loaded
func EnsureFrozenImagesLinux(testEnv *Execution) error {
if testEnv.OSType == "linux" {
err := load.FrozenImagesLinux(testEnv.APIClient(), frozenImages...)
if err != nil {
return errors.Wrap(err, "error loading frozen images")
}
}
return nil
}

View file

@ -0,0 +1,205 @@
package environment // import "github.com/docker/docker/internal/test/environment"
import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
dclient "github.com/docker/docker/client"
"github.com/gotestyourself/gotestyourself/assert"
)
var frozenImages = []string{"busybox:latest", "busybox:glibc", "hello-world:frozen", "debian:jessie"}
type protectedElements struct {
containers map[string]struct{}
images map[string]struct{}
networks map[string]struct{}
plugins map[string]struct{}
volumes map[string]struct{}
}
func newProtectedElements() protectedElements {
return protectedElements{
containers: map[string]struct{}{},
images: map[string]struct{}{},
networks: map[string]struct{}{},
plugins: map[string]struct{}{},
volumes: map[string]struct{}{},
}
}
// ProtectAll protects the existing environment (containers, images, networks,
// volumes, and, on Linux, plugins) from being cleaned up at the end of test
// runs
func ProtectAll(t testingT, testEnv *Execution) {
ProtectContainers(t, testEnv)
ProtectImages(t, testEnv)
ProtectNetworks(t, testEnv)
ProtectVolumes(t, testEnv)
if testEnv.OSType == "linux" {
ProtectPlugins(t, testEnv)
}
}
// ProtectContainer adds the specified container(s) to be protected in case of
// clean
func (e *Execution) ProtectContainer(t testingT, containers ...string) {
for _, container := range containers {
e.protectedElements.containers[container] = struct{}{}
}
}
// ProtectContainers protects existing containers from being cleaned up at the
// end of test runs
func ProtectContainers(t testingT, testEnv *Execution) {
containers := getExistingContainers(t, testEnv)
testEnv.ProtectContainer(t, containers...)
}
func getExistingContainers(t assert.TestingT, testEnv *Execution) []string {
client := testEnv.APIClient()
containerList, err := client.ContainerList(context.Background(), types.ContainerListOptions{
All: true,
})
assert.NilError(t, err, "failed to list containers")
containers := []string{}
for _, container := range containerList {
containers = append(containers, container.ID)
}
return containers
}
// ProtectImage adds the specified image(s) to be protected in case of clean
func (e *Execution) ProtectImage(t testingT, images ...string) {
for _, image := range images {
e.protectedElements.images[image] = struct{}{}
}
}
// ProtectImages protects existing images and on linux frozen images from being
// cleaned up at the end of test runs
func ProtectImages(t testingT, testEnv *Execution) {
images := getExistingImages(t, testEnv)
if testEnv.OSType == "linux" {
images = append(images, frozenImages...)
}
testEnv.ProtectImage(t, images...)
}
func getExistingImages(t assert.TestingT, testEnv *Execution) []string {
client := testEnv.APIClient()
filter := filters.NewArgs()
filter.Add("dangling", "false")
imageList, err := client.ImageList(context.Background(), types.ImageListOptions{
All: true,
Filters: filter,
})
assert.NilError(t, err, "failed to list images")
images := []string{}
for _, image := range imageList {
images = append(images, tagsFromImageSummary(image)...)
}
return images
}
func tagsFromImageSummary(image types.ImageSummary) []string {
result := []string{}
for _, tag := range image.RepoTags {
if tag != "<none>:<none>" {
result = append(result, tag)
}
}
for _, digest := range image.RepoDigests {
if digest != "<none>@<none>" {
result = append(result, digest)
}
}
return result
}
// ProtectNetwork adds the specified network(s) to be protected in case of
// clean
func (e *Execution) ProtectNetwork(t testingT, networks ...string) {
for _, network := range networks {
e.protectedElements.networks[network] = struct{}{}
}
}
// ProtectNetworks protects existing networks from being cleaned up at the end
// of test runs
func ProtectNetworks(t testingT, testEnv *Execution) {
networks := getExistingNetworks(t, testEnv)
testEnv.ProtectNetwork(t, networks...)
}
func getExistingNetworks(t assert.TestingT, testEnv *Execution) []string {
client := testEnv.APIClient()
networkList, err := client.NetworkList(context.Background(), types.NetworkListOptions{})
assert.NilError(t, err, "failed to list networks")
networks := []string{}
for _, network := range networkList {
networks = append(networks, network.ID)
}
return networks
}
// ProtectPlugin adds the specified plugin(s) to be protected in case of clean
func (e *Execution) ProtectPlugin(t testingT, plugins ...string) {
for _, plugin := range plugins {
e.protectedElements.plugins[plugin] = struct{}{}
}
}
// ProtectPlugins protects existing plugins from being cleaned up at the end of
// test runs
func ProtectPlugins(t testingT, testEnv *Execution) {
plugins := getExistingPlugins(t, testEnv)
testEnv.ProtectPlugin(t, plugins...)
}
func getExistingPlugins(t assert.TestingT, testEnv *Execution) []string {
client := testEnv.APIClient()
pluginList, err := client.PluginList(context.Background(), filters.Args{})
// Docker EE does not allow cluster-wide plugin management.
if dclient.IsErrNotImplemented(err) {
return []string{}
}
assert.NilError(t, err, "failed to list plugins")
plugins := []string{}
for _, plugin := range pluginList {
plugins = append(plugins, plugin.Name)
}
return plugins
}
// ProtectVolume adds the specified volume(s) to be protected in case of clean
func (e *Execution) ProtectVolume(t testingT, volumes ...string) {
for _, volume := range volumes {
e.protectedElements.volumes[volume] = struct{}{}
}
}
// ProtectVolumes protects existing volumes from being cleaned up at the end of
// test runs
func ProtectVolumes(t testingT, testEnv *Execution) {
volumes := getExistingVolumes(t, testEnv)
testEnv.ProtectVolume(t, volumes...)
}
func getExistingVolumes(t assert.TestingT, testEnv *Execution) []string {
client := testEnv.APIClient()
volumeList, err := client.VolumeList(context.Background(), filters.Args{})
assert.NilError(t, err, "failed to list volumes")
volumes := []string{}
for _, volume := range volumeList.Volumes {
volumes = append(volumes, volume.Name)
}
return volumes
}

View file

@ -0,0 +1,33 @@
package testutil // import "github.com/docker/docker/internal/testutil"
import (
"io"
"github.com/gotestyourself/gotestyourself/assert"
)
type helperT interface {
Helper()
}
// ErrorContains checks that the error is not nil, and contains the expected
// substring.
// Deprecated: use assert.Assert(t, cmp.ErrorContains(err, expected))
func ErrorContains(t assert.TestingT, err error, expectedError string, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert.ErrorContains(t, err, expectedError, msgAndArgs...)
}
// DevZero acts like /dev/zero but in an OS-independent fashion.
var DevZero io.Reader = devZero{}
type devZero struct{}
func (d devZero) Read(p []byte) (n int, err error) {
for i := range p {
p[i] = 0
}
return len(p), nil
}

View file

@ -0,0 +1,14 @@
package testutil // import "github.com/docker/docker/internal/testutil"
import "math/rand"
// GenerateRandomAlphaOnlyString generates an alphabetical random string with length n.
func GenerateRandomAlphaOnlyString(n int) string {
// make a really long string
letters := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}

View file

@ -0,0 +1,34 @@
package testutil // import "github.com/docker/docker/internal/testutil"
import (
"testing"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
)
func testLengthHelper(generator func(int) string, t *testing.T) {
expectedLength := 20
s := generator(expectedLength)
assert.Check(t, is.Equal(expectedLength, len(s)))
}
func testUniquenessHelper(generator func(int) string, t *testing.T) {
repeats := 25
set := make(map[string]struct{}, repeats)
for i := 0; i < repeats; i = i + 1 {
str := generator(64)
assert.Check(t, is.Equal(64, len(str)))
_, ok := set[str]
assert.Check(t, !ok, "Random number is repeated")
set[str] = struct{}{}
}
}
func TestGenerateRandomAlphaOnlyStringLength(t *testing.T) {
testLengthHelper(GenerateRandomAlphaOnlyString, t)
}
func TestGenerateRandomAlphaOnlyStringUniqueness(t *testing.T) {
testUniquenessHelper(GenerateRandomAlphaOnlyString, t)
}