api/types/plugin: replace rapid with standard Go fuzzing
Replace the rapid-based property test for plugin capability IDs with a table-driven test and Go's built-in fuzzing support. Preserve the comparison against the historical JSON parser to catch compatibility regressions, and add explicit test cases for the supported serialization formats and invalid inputs. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
7 files changed
+145
-90
No files matched your search
@@ -7,5 +7,4 @@ require (
|
||||
github.com/moby/docker-image-spec v1.3.1
|
||||
github.com/opencontainers/go-digest v1.0.0
|
||||
github.com/opencontainers/image-spec v1.1.1
|
||||
pgregory.net/rapid v1.2.0
|
||||
)
|
||||
@@ -6,5 +6,3 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
@@ -1 +0,0 @@
|
||||
testdata/rapid/**
|
||||
@@ -0,0 +1,34 @@
|
||||
package plugin_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/moby/moby/api/types/plugin"
|
||||
)
|
||||
|
||||
func FuzzCapabilityID(f *testing.F) {
|
||||
f.Add("docker", "networkdriver", "1.0")
|
||||
f.Add("foo.bar", "baz", "1.0")
|
||||
f.Add("", "bar", "1.0")
|
||||
|
||||
f.Fuzz(func(t *testing.T, prefix, capability, version string) {
|
||||
original := plugin.CapabilityID{
|
||||
Prefix: prefix,
|
||||
Capability: capability,
|
||||
Version: version,
|
||||
}
|
||||
|
||||
b, err := original.MarshalText()
|
||||
if err != nil {
|
||||
return // Invalid combination.
|
||||
}
|
||||
|
||||
var got plugin.CapabilityID
|
||||
if err := got.UnmarshalText(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != original {
|
||||
t.Fatalf("roundtrip = %+v, want %+v", got, original)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2,102 +2,130 @@ package plugin_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/moby/moby/api/types/plugin"
|
||||
"pgregory.net/rapid"
|
||||
)
|
||||
|
||||
type pluginCapabilityID plugin.CapabilityID
|
||||
|
||||
// unmarshalJSON is a copy of the original PluginInterfaceType.UnmarshalJSON
|
||||
// parser, used to test that the new parser produces the same results for
|
||||
// well-formed inputs.
|
||||
func (t *pluginCapabilityID) unmarshalJSON(p []byte) error {
|
||||
versionIndex := len(p)
|
||||
prefixIndex := 0
|
||||
if len(p) < 2 || p[0] != '"' || p[len(p)-1] != '"' {
|
||||
return fmt.Errorf("%q is not a plugin interface type", p)
|
||||
}
|
||||
p = p[1 : len(p)-1]
|
||||
loop:
|
||||
for i, b := range p {
|
||||
switch b {
|
||||
case '.':
|
||||
prefixIndex = i
|
||||
case '/':
|
||||
versionIndex = i
|
||||
break loop
|
||||
}
|
||||
}
|
||||
t.Prefix = string(p[:prefixIndex])
|
||||
t.Capability = string(p[prefixIndex+1 : versionIndex])
|
||||
if versionIndex < len(p) {
|
||||
t.Version = string(p[versionIndex+1:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCapabilityID_MarshalUnmarshal(t *testing.T) {
|
||||
stringgen := rapid.StringMatching(`[a-z0-9-./]*`)
|
||||
rapid.Check(t, func(t *rapid.T) {
|
||||
typ := plugin.CapabilityID{
|
||||
Capability: stringgen.Draw(t, "Capability"),
|
||||
Prefix: stringgen.Draw(t, "Prefix"),
|
||||
Version: stringgen.Draw(t, "Version"),
|
||||
}
|
||||
b, err := typ.MarshalText()
|
||||
if err != nil {
|
||||
t.Skipf("unmarshalable value: %v", err)
|
||||
}
|
||||
t.Logf("InterfaceType(%q)", b)
|
||||
|
||||
var roundtrip plugin.CapabilityID
|
||||
if err := roundtrip.UnmarshalText(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if typ != roundtrip {
|
||||
t.Errorf("roundtrip = %+v, want %+v", roundtrip, typ)
|
||||
}
|
||||
|
||||
jb, err := json.Marshal(string(b))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var oldparser pluginCapabilityID
|
||||
if err := oldparser.unmarshalJSON(jb); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if typ != plugin.CapabilityID(oldparser) {
|
||||
t.Errorf("new parser = %+v, old parser = %+v", typ, oldparser)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCapabilityID_JSONMarshalUnmarshal(t *testing.T) {
|
||||
type rt struct {
|
||||
Type plugin.CapabilityID
|
||||
}
|
||||
a := rt{
|
||||
Type: plugin.CapabilityID{
|
||||
Capability: "foo",
|
||||
Prefix: "bar",
|
||||
Version: "baz",
|
||||
tests := []struct {
|
||||
name string
|
||||
id plugin.CapabilityID
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "standard",
|
||||
id: plugin.CapabilityID{Prefix: "docker", Capability: "networkdriver", Version: "1.0"},
|
||||
want: "docker.networkdriver/1.0",
|
||||
},
|
||||
{
|
||||
name: "dotted prefix",
|
||||
id: plugin.CapabilityID{Prefix: "com.example", Capability: "driver", Version: "1.0"},
|
||||
want: "com.example.driver/1.0",
|
||||
},
|
||||
{
|
||||
name: "empty prefix",
|
||||
id: plugin.CapabilityID{Capability: "driver", Version: "1.0"},
|
||||
want: ".driver/1.0",
|
||||
},
|
||||
{
|
||||
name: "empty version",
|
||||
id: plugin.CapabilityID{Prefix: "docker", Capability: "driver"},
|
||||
want: "docker.driver/",
|
||||
},
|
||||
{
|
||||
name: "zero value",
|
||||
id: plugin.CapabilityID{},
|
||||
want: "./",
|
||||
},
|
||||
}
|
||||
b, err := json.Marshal(a)
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
b, err := tc.id.MarshalText()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(b) != tc.want {
|
||||
t.Errorf("MarshalText() = %q, want %q", b, tc.want)
|
||||
}
|
||||
|
||||
var got plugin.CapabilityID
|
||||
if err := got.UnmarshalText(b); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != tc.id {
|
||||
t.Errorf("roundtrip = %+v, want %+v", got, tc.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCapabilityID_JSONMarshalUnmarshal verifies that encoding/json uses
|
||||
// the text marshaling methods and preserves the JSON representation.
|
||||
func TestCapabilityID_JSONMarshalUnmarshal(t *testing.T) {
|
||||
id := plugin.CapabilityID{Prefix: "docker", Capability: "networkdriver", Version: "1.0"}
|
||||
|
||||
data, err := json.Marshal(id)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("JSON: %s", b)
|
||||
if want := `"docker.networkdriver/1.0"`; string(data) != want {
|
||||
t.Errorf("json.Marshal() = %s, want %s", data, want)
|
||||
}
|
||||
|
||||
var roundtrip rt
|
||||
if err := json.Unmarshal(b, &roundtrip); err != nil {
|
||||
var got plugin.CapabilityID
|
||||
if err := json.Unmarshal(data, &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a != roundtrip {
|
||||
t.Errorf("roundtrip = %+v, want %+v", roundtrip, a)
|
||||
if got != id {
|
||||
t.Errorf("JSON roundtrip = %+v, want %+v", got, id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCapabilityID_JSONUnmarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data string
|
||||
want plugin.CapabilityID
|
||||
}{
|
||||
{
|
||||
name: "standard",
|
||||
data: `"docker.networkdriver/1.0"`,
|
||||
want: plugin.CapabilityID{
|
||||
Prefix: "docker",
|
||||
Capability: "networkdriver",
|
||||
Version: "1.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dotted prefix",
|
||||
data: `"com.example.driver/1.0"`,
|
||||
want: plugin.CapabilityID{
|
||||
Prefix: "com.example",
|
||||
Capability: "driver",
|
||||
Version: "1.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no version",
|
||||
data: `"docker.driver"`,
|
||||
want: plugin.CapabilityID{
|
||||
Prefix: "docker",
|
||||
Capability: "driver",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var got plugin.CapabilityID
|
||||
if err := json.Unmarshal([]byte(tc.data), &got); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != tc.want {
|
||||
t.Errorf("UnmarshalJSON(%s) = %+v, want %+v", tc.data, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -61,5 +61,3 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
@@ -1 +0,0 @@
|
||||
testdata/rapid/**
|
||||
Reference in new issue
Block a user