diff --git a/cli/compose/convert/compose_test.go b/cli/compose/convert/compose_test.go index 0dfac14e15..900f1cc2d4 100644 --- a/cli/compose/convert/compose_test.go +++ b/cli/compose/convert/compose_test.go @@ -16,6 +16,16 @@ func TestNamespaceScope(t *testing.T) { assert.Check(t, is.Equal("foo_bar", scoped)) } +func TestNamespaceDescope(t *testing.T) { + descoped := Namespace{name: "foo"}.Descope("foo_bar") + assert.Check(t, is.Equal("bar", descoped)) +} + +func TestNamespaceName(t *testing.T) { + namespaceName := Namespace{name: "foo"}.Name() + assert.Check(t, is.Equal("foo", namespaceName)) +} + func TestAddStackLabel(t *testing.T) { labels := map[string]string{ "something": "labeled", diff --git a/cli/compose/convert/service_test.go b/cli/compose/convert/service_test.go index 3cf2ee9e03..c4a01d8bf2 100644 --- a/cli/compose/convert/service_test.go +++ b/cli/compose/convert/service_test.go @@ -61,6 +61,15 @@ func TestConvertEnvironment(t *testing.T) { assert.Check(t, is.DeepEqual([]string{"foo=bar", "key=value"}, env)) } +func TestConvertEnvironmentWhenNilValueExists(t *testing.T) { + source := map[string]*string{ + "key": strPtr("value"), + "keyWithNoValue": nil, + } + env := convertEnvironment(source) + assert.Check(t, is.DeepEqual([]string{"key=value", "keyWithNoValue"}, env)) +} + func TestConvertExtraHosts(t *testing.T) { source := composetypes.HostsList{ "zulu:127.0.0.2", diff --git a/cli/compose/convert/volume_test.go b/cli/compose/convert/volume_test.go index 7f9da874fb..cc2fdc851e 100644 --- a/cli/compose/convert/volume_test.go +++ b/cli/compose/convert/volume_test.go @@ -9,6 +9,55 @@ import ( is "gotest.tools/v3/assert/cmp" ) +func TestVolumesWithMultipleServiceVolumeConfigs(t *testing.T) { + namespace := NewNamespace("foo") + + serviceVolumes := []composetypes.ServiceVolumeConfig{ + { + Type: "volume", + Target: "/foo", + }, + { + Type: "volume", + Target: "/foo/bar", + }, + } + + expected := []mount.Mount{ + { + Type: "volume", + Target: "/foo", + }, + { + Type: "volume", + Target: "/foo/bar", + }, + } + + mnt, err := Volumes(serviceVolumes, volumes{}, namespace) + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(expected, mnt)) +} + +func TestVolumesWithMultipleServiceVolumeConfigsWithUndefinedVolumeConfig(t *testing.T) { + namespace := NewNamespace("foo") + + serviceVolumes := []composetypes.ServiceVolumeConfig{ + { + Type: "volume", + Source: "foo", + Target: "/foo", + }, + { + Type: "volume", + Target: "/foo/bar", + }, + } + + _, err := Volumes(serviceVolumes, volumes{}, namespace) + assert.Error(t, err, "undefined volume \"foo\"") +} + func TestConvertVolumeToMountAnonymousVolume(t *testing.T) { config := composetypes.ServiceVolumeConfig{ Type: "volume", @@ -104,6 +153,49 @@ func TestConvertVolumeToMountConflictingOptionsTmpfsInBind(t *testing.T) { assert.Error(t, err, "tmpfs options are incompatible with type bind") } +func TestConvertVolumeToMountConflictingOptionsClusterInVolume(t *testing.T) { + namespace := NewNamespace("foo") + + config := composetypes.ServiceVolumeConfig{ + Type: "volume", + Target: "/target", + Cluster: &composetypes.ServiceVolumeCluster{}, + } + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "cluster options are incompatible with type volume") +} + +func TestConvertVolumeToMountConflictingOptionsClusterInBind(t *testing.T) { + namespace := NewNamespace("foo") + + config := composetypes.ServiceVolumeConfig{ + Type: "bind", + Source: "/foo", + Target: "/target", + Bind: &composetypes.ServiceVolumeBind{ + Propagation: "slave", + }, + Cluster: &composetypes.ServiceVolumeCluster{}, + } + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "cluster options are incompatible with type bind") +} + +func TestConvertVolumeToMountConflictingOptionsClusterInTmpfs(t *testing.T) { + namespace := NewNamespace("foo") + + config := composetypes.ServiceVolumeConfig{ + Type: "tmpfs", + Target: "/target", + Tmpfs: &composetypes.ServiceVolumeTmpfs{ + Size: 1000, + }, + Cluster: &composetypes.ServiceVolumeCluster{}, + } + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "cluster options are incompatible with type tmpfs") +} + func TestConvertVolumeToMountConflictingOptionsBindInTmpfs(t *testing.T) { namespace := NewNamespace("foo") @@ -132,6 +224,50 @@ func TestConvertVolumeToMountConflictingOptionsVolumeInTmpfs(t *testing.T) { assert.Error(t, err, "volume options are incompatible with type tmpfs") } +func TestHandleNpipeToMountAnonymousNpipe(t *testing.T) { + namespace := NewNamespace("foo") + + config := composetypes.ServiceVolumeConfig{ + Type: "npipe", + Target: "/target", + Volume: &composetypes.ServiceVolumeVolume{ + NoCopy: true, + }, + } + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "invalid npipe source, source cannot be empty") +} + +func TestHandleNpipeToMountConflictingOptionsTmpfsInNpipe(t *testing.T) { + namespace := NewNamespace("foo") + + config := composetypes.ServiceVolumeConfig{ + Type: "npipe", + Source: "/foo", + Target: "/target", + Tmpfs: &composetypes.ServiceVolumeTmpfs{ + Size: 1000, + }, + } + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "tmpfs options are incompatible with type npipe") +} + +func TestHandleNpipeToMountConflictingOptionsVolumeInNpipe(t *testing.T) { + namespace := NewNamespace("foo") + + config := composetypes.ServiceVolumeConfig{ + Type: "npipe", + Source: "/foo", + Target: "/target", + Volume: &composetypes.ServiceVolumeVolume{ + NoCopy: true, + }, + } + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "volume options are incompatible with type npipe") +} + func TestConvertVolumeToMountNamedVolume(t *testing.T) { stackVolumes := volumes{ "normal": composetypes.VolumeConfig{ @@ -344,6 +480,27 @@ func TestConvertTmpfsToMountVolumeWithSource(t *testing.T) { assert.Error(t, err, "invalid tmpfs source, source must be empty") } +func TestHandleNpipeToMountBind(t *testing.T) { + namespace := NewNamespace("foo") + expected := mount.Mount{ + Type: mount.TypeNamedPipe, + Source: "/bar", + Target: "/foo", + ReadOnly: true, + BindOptions: &mount.BindOptions{Propagation: mount.PropagationShared}, + } + config := composetypes.ServiceVolumeConfig{ + Type: "npipe", + Source: "/bar", + Target: "/foo", + ReadOnly: true, + Bind: &composetypes.ServiceVolumeBind{Propagation: "shared"}, + } + mnt, err := convertVolumeToMount(config, volumes{}, namespace) + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(expected, mnt)) +} + func TestConvertVolumeToMountAnonymousNpipe(t *testing.T) { config := composetypes.ServiceVolumeConfig{ Type: "npipe", @@ -427,3 +584,98 @@ func TestConvertVolumeMountClusterGroup(t *testing.T) { assert.NilError(t, err) assert.Check(t, is.DeepEqual(expected, mnt)) } + +func TestHandleClusterToMountAnonymousCluster(t *testing.T) { + namespace := NewNamespace("foo") + + config := composetypes.ServiceVolumeConfig{ + Type: "cluster", + Target: "/target", + Volume: &composetypes.ServiceVolumeVolume{ + NoCopy: true, + }, + } + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "invalid cluster source, source cannot be empty") +} + +func TestHandleClusterToMountConflictingOptionsTmpfsInCluster(t *testing.T) { + namespace := NewNamespace("foo") + + config := composetypes.ServiceVolumeConfig{ + Type: "cluster", + Source: "/foo", + Target: "/target", + Tmpfs: &composetypes.ServiceVolumeTmpfs{ + Size: 1000, + }, + } + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "tmpfs options are incompatible with type cluster") +} + +func TestHandleClusterToMountConflictingOptionsVolumeInCluster(t *testing.T) { + namespace := NewNamespace("foo") + + config := composetypes.ServiceVolumeConfig{ + Type: "cluster", + Source: "/foo", + Target: "/target", + Volume: &composetypes.ServiceVolumeVolume{ + NoCopy: true, + }, + } + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "volume options are incompatible with type cluster") +} + +func TestHandleClusterToMountWithUndefinedVolumeConfig(t *testing.T) { + namespace := NewNamespace("foo") + + config := composetypes.ServiceVolumeConfig{ + Type: "cluster", + Source: "foo", + Target: "/srv", + } + + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "undefined volume \"foo\"") +} + +func TestHandleClusterToMountWithVolumeConfigName(t *testing.T) { + stackVolumes := volumes{ + "foo": composetypes.VolumeConfig{ + Name: "bar", + }, + } + + config := composetypes.ServiceVolumeConfig{ + Type: "cluster", + Source: "foo", + Target: "/srv", + } + + expected := mount.Mount{ + Type: mount.TypeCluster, + Source: "bar", + Target: "/srv", + ClusterOptions: &mount.ClusterOptions{}, + } + + mnt, err := convertVolumeToMount(config, stackVolumes, NewNamespace("foo")) + assert.NilError(t, err) + assert.Check(t, is.DeepEqual(expected, mnt)) +} + +func TestHandleClusterToMountBind(t *testing.T) { + namespace := NewNamespace("foo") + config := composetypes.ServiceVolumeConfig{ + Type: "cluster", + Source: "/bar", + Target: "/foo", + ReadOnly: true, + Bind: &composetypes.ServiceVolumeBind{Propagation: "shared"}, + } + _, err := convertVolumeToMount(config, volumes{}, namespace) + assert.Error(t, err, "bind options are incompatible with type cluster") +} diff --git a/opts/network_test.go b/opts/network_test.go index 8a5b66fb11..424c5ad045 100644 --- a/opts/network_test.go +++ b/opts/network_test.go @@ -135,3 +135,18 @@ func TestNetworkOptAdvancedSyntaxInvalid(t *testing.T) { }) } } + +func TestNetworkOptStringNetOptString(t *testing.T) { + networkOpt := &NetworkOpt{} + result := networkOpt.String() + assert.Check(t, is.Equal("", result)) + if result != "" { + t.Errorf("Expected an empty string, got %s", result) + } +} + +func TestNetworkOptTypeNetOptType(t *testing.T) { + networkOpt := &NetworkOpt{} + result := networkOpt.Type() + assert.Check(t, is.Equal("network", result)) +} diff --git a/opts/opts_test.go b/opts/opts_test.go index 2868b04f4f..9bf9dd12d2 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -465,3 +465,18 @@ func TestParseLink(t *testing.T) { t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err) } } + +func TestGetAllOrEmptyReturnsNilOrValue(t *testing.T) { + opts := NewListOpts(nil) + assert.Check(t, is.DeepEqual(opts.GetAllOrEmpty(), []string{})) + opts.Set("foo") + assert.Check(t, is.DeepEqual(opts.GetAllOrEmpty(), []string{"foo"})) +} + +func TestParseCPUsReturnZeroOnInvalidValues(t *testing.T) { + resValue, _ := ParseCPUs("foo") + var z1 int64 = 0 + assert.Equal(t, z1, resValue) + resValue, _ = ParseCPUs("1e-32") + assert.Equal(t, z1, resValue) +}