Skip to content

Commit

Permalink
Merge pull request GoogleCloudPlatform#2995 from justinsb/fuzzer_unified
Browse files Browse the repository at this point in the history
tests: create generic fuzz tester
  • Loading branch information
google-oss-prow[bot] authored Oct 24, 2024
2 parents 2aa2617 + 7cd446d commit b33a34f
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 5 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/presubmit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,17 @@ jobs:
dev/tasks/build-images
env:
PROJECT_ID: cnrm-test
test-mappers-roundtrip:
runs-on: ubuntu-22.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: "dev/ci/presubmits/test-mappers-roundtrip"
run: |
dev/ci/presubmits/test-mappers-roundtrip
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: true
2 changes: 1 addition & 1 deletion apis/workstations/v1alpha1/workstationcluster_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ type WorkstationClusterObservedState struct {
type WorkstationClusterGCPCondition struct {
// The status code, which should be an enum value of
// [google.rpc.Code][google.rpc.Code].
Code *int `json:"code,omitempty"`
Code *int32 `json:"code,omitempty"`

// A developer-facing error message, which should be in English. Any
// user-facing error message should be localized and sent in the
Expand Down
2 changes: 1 addition & 1 deletion apis/workstations/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ spec:
code:
description: The status code, which should be an enum value
of [google.rpc.Code][google.rpc.Code].
format: int32
type: integer
message:
description: A developer-facing error message, which should
Expand Down
23 changes: 23 additions & 0 deletions dev/ci/presubmits/test-mappers-roundtrip
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -o errexit
set -o nounset
set -o pipefail

REPO_ROOT="$(git rev-parse --show-toplevel)"
cd ${REPO_ROOT}

go test -v ./pkg/fuzztesting/fuzztests/ -fuzz=FuzzAllMappers -fuzztime 60s

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ import (
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/directbase"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/registry"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/fuzztesting"

gcp "cloud.google.com/go/workstations/apiv1"
pb "cloud.google.com/go/workstations/apiv1/workstationspb"
workstationspb "cloud.google.com/go/workstations/apiv1/workstationspb"
"google.golang.org/api/option"
"google.golang.org/protobuf/types/known/fieldmaskpb"
Expand All @@ -44,6 +46,38 @@ const (

func init() {
registry.RegisterModel(krm.WorkstationClusterGVK, NewModel)
fuzztesting.RegisterKRMFuzzer(workstationclusterFuzzer())
}

func workstationclusterFuzzer() fuzztesting.KRMFuzzer {
f := fuzztesting.NewKRMTypedFuzzer(&pb.WorkstationCluster{},
WorkstationClusterSpec_FromProto, WorkstationClusterSpec_ToProto,
WorkstationClusterObservedState_FromProto, WorkstationClusterObservedState_ToProto,
)

f.UnimplementedFields.Insert(".name")

f.UnimplementedFields.Insert(".labels")
f.UnimplementedFields.Insert(".reconciling")
f.UnimplementedFields.Insert(".degraded")
f.UnimplementedFields.Insert(".conditions")
f.UnimplementedFields.Insert(".private_cluster_config.cluster_hostname")
f.UnimplementedFields.Insert(".private_cluster_config.service_attachment_uri")

f.SpecFields.Insert(".display_name")
f.SpecFields.Insert(".private_cluster_config")
f.SpecFields.Insert(".annotations")
f.SpecFields.Insert(".subnetwork")
f.SpecFields.Insert(".network")

f.StatusFields.Insert(".create_time")
f.StatusFields.Insert(".delete_time")
f.StatusFields.Insert(".update_time")
f.StatusFields.Insert(".control_plane_ip")
f.StatusFields.Insert(".etag")
f.StatusFields.Insert(".uid")

return f
}

func NewModel(ctx context.Context, config *config.ControllerConfig) (directbase.Model, error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,24 @@ func WorkstationClusterObservedState_FromProto(mapCtx *direct.MapContext, in *pb
return out
}

func WorkstationClusterObservedState_ToProto(mapCtx *direct.MapContext, in *krm.WorkstationClusterObservedState) *pb.WorkstationCluster {
if in == nil {
return nil
}
out := &pb.WorkstationCluster{}
// MISSING: Name
out.Uid = direct.ValueOf(in.Uid)
out.Reconciling = direct.ValueOf(in.Reconciling)
out.CreateTime = direct.StringTimestamp_ToProto(mapCtx, in.CreateTime)
out.UpdateTime = direct.StringTimestamp_ToProto(mapCtx, in.UpdateTime)
out.DeleteTime = direct.StringTimestamp_ToProto(mapCtx, in.DeleteTime)
out.Etag = direct.ValueOf(in.Etag)
out.ControlPlaneIp = direct.ValueOf(in.ControlPlaneIP)
out.Degraded = direct.ValueOf(in.Degraded)
out.Conditions = WorkstationClusterGCPConditions_ToProto(mapCtx, in.GCPConditions)
return out
}

func WorkstationClusterClusterHostname_FromProto(mapCtx *direct.MapContext, in *pb.WorkstationCluster_PrivateClusterConfig) *string {
if in == nil {
return nil
Expand All @@ -194,9 +212,22 @@ func WorkstationClusterGCPConditions_FromProto(mapCtx *direct.MapContext, in []*
var out []krm.WorkstationClusterGCPCondition
for _, c := range in {
out = append(out, krm.WorkstationClusterGCPCondition{
Code: direct.LazyPtr(int(c.Code)),
Code: direct.LazyPtr(c.Code),
Message: direct.LazyPtr(c.Message),
})
}
return out
}
func WorkstationClusterGCPConditions_ToProto(mapCtx *direct.MapContext, in []krm.WorkstationClusterGCPCondition) []*status.Status {
if in == nil {
return nil
}
var out []*status.Status
for _, c := range in {
out = append(out, &status.Status{
Code: direct.ValueOf(c.Code),
Message: direct.ValueOf(c.Message),
})
}
return out
}
149 changes: 149 additions & 0 deletions pkg/fuzztesting/fuzzkrm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fuzztesting

import (
"math/rand"
"testing"

"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/test/fuzz"
"github.com/google/go-cmp/cmp"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/testing/protocmp"
"k8s.io/apimachinery/pkg/util/sets"
)

type FuzzFn func(t *testing.T, seed int64)

var fuzzers []FuzzFn

func RegisterKRMFuzzer(fuzzer KRMFuzzer) {
RegisterFuzzer(fuzzer.FuzzSpec)
RegisterFuzzer(fuzzer.FuzzStatus)
}

func RegisterFuzzer(fuzzer FuzzFn) {
fuzzers = append(fuzzers, fuzzer)
}

func ChooseFuzzer(n int64) FuzzFn {
return fuzzers[n%int64(len(fuzzers))]
}

type KRMTypedFuzzer[ProtoT proto.Message, SpecType any, StatusType any] struct {
ProtoType ProtoT

SpecFromProto func(ctx *direct.MapContext, in ProtoT) *SpecType
SpecToProto func(ctx *direct.MapContext, in *SpecType) ProtoT

StatusFromProto func(ctx *direct.MapContext, in ProtoT) *StatusType
StatusToProto func(ctx *direct.MapContext, in *StatusType) ProtoT

UnimplementedFields sets.Set[string]
SpecFields sets.Set[string]
StatusFields sets.Set[string]
}

type KRMFuzzer interface {
FuzzSpec(t *testing.T, seed int64)
FuzzStatus(t *testing.T, seed int64)
}

func NewKRMTypedFuzzer[ProtoT proto.Message, SpecType any, StatusType any](
protoType ProtoT,
specFromProto func(ctx *direct.MapContext, in ProtoT) *SpecType, specToProto func(ctx *direct.MapContext, in *SpecType) ProtoT,
statusFromProto func(ctx *direct.MapContext, in ProtoT) *StatusType, statusToProto func(ctx *direct.MapContext, in *StatusType) ProtoT,
) *KRMTypedFuzzer[ProtoT, SpecType, StatusType] {
return &KRMTypedFuzzer[ProtoT, SpecType, StatusType]{
ProtoType: protoType,
SpecFromProto: specFromProto,
SpecToProto: specToProto,
StatusFromProto: statusFromProto,
StatusToProto: statusToProto,
UnimplementedFields: sets.New[string](),
SpecFields: sets.New[string](),
StatusFields: sets.New[string](),
}
}

func (f *KRMTypedFuzzer[ProtoT, SpecType, StatusType]) FuzzSpec(t *testing.T, seed int64) {
fuzzer := NewFuzzTest(f.ProtoType, f.SpecFromProto, f.SpecToProto)
fuzzer.IgnoreFields = f.StatusFields
fuzzer.UnimplementedFields = f.UnimplementedFields
fuzzer.Fuzz(t, seed)
}

func (f *KRMTypedFuzzer[ProtoT, SpecType, StatusType]) FuzzStatus(t *testing.T, seed int64) {
fuzzer := NewFuzzTest(f.ProtoType, f.StatusFromProto, f.StatusToProto)
fuzzer.IgnoreFields = f.SpecFields
fuzzer.UnimplementedFields = f.UnimplementedFields
fuzzer.Fuzz(t, seed)
}

type FuzzTest[ProtoT proto.Message, KRMType any] struct {
ProtoType ProtoT

FromProto func(ctx *direct.MapContext, in ProtoT) *KRMType
ToProto func(ctx *direct.MapContext, in *KRMType) ProtoT

UnimplementedFields sets.Set[string]
IgnoreFields sets.Set[string]
}

func NewFuzzTest[ProtoT proto.Message, KRMType any](protoType ProtoT, fromProto func(ctx *direct.MapContext, in ProtoT) *KRMType, toProto func(ctx *direct.MapContext, in *KRMType) ProtoT) *FuzzTest[ProtoT, KRMType] {
return &FuzzTest[ProtoT, KRMType]{
ProtoType: protoType,
FromProto: fromProto,
ToProto: toProto,
UnimplementedFields: sets.New[string](),
IgnoreFields: sets.New[string](),
}
}

func (f *FuzzTest[ProtoT, KRMType]) Fuzz(t *testing.T, seed int64) {
randStream := rand.New(rand.NewSource(seed))

p1 := proto.Clone(f.ProtoType).(ProtoT)
fuzz.FillWithRandom(t, randStream, p1)

ignoreFields := sets.New[string]()
ignoreFields = ignoreFields.Union(f.IgnoreFields)
ignoreFields = ignoreFields.Union(f.UnimplementedFields)

// Remove any output only or known-unimplemented fields
clearFields := &fuzz.ClearFields{
Paths: ignoreFields,
}
fuzz.Visit("", p1.ProtoReflect(), nil, clearFields)

ctx := &direct.MapContext{}
k := f.FromProto(ctx, p1)
if ctx.Err() != nil {
t.Fatalf("error mapping from proto to krm: %v", ctx.Err())
}

p2 := f.ToProto(ctx, k)
if ctx.Err() != nil {
t.Fatalf("error mapping from krm to proto: %v", ctx.Err())
}

if diff := cmp.Diff(p1, p2, protocmp.Transform()); diff != "" {
t.Logf("p1 = %v", prototext.Format(p1))
t.Logf("p2 = %v", prototext.Format(p2))
t.Errorf("roundtrip failed; diff:\n%s", diff)
}
}
33 changes: 33 additions & 0 deletions pkg/fuzztesting/fuzztests/fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fuzztesting

import (
"math/rand"
"testing"

_ "github.com/GoogleCloudPlatform/k8s-config-connector/pkg/controller/direct/register"
"github.com/GoogleCloudPlatform/k8s-config-connector/pkg/fuzztesting"
)

func FuzzAllMappers(f *testing.F) {
f.Fuzz(func(t *testing.T, seed int64) {
randStream := rand.New(rand.NewSource(seed))

fuzzer := fuzztesting.ChooseFuzzer(randStream.Int63())
nextSeed := randStream.Int63()
fuzzer(t, nextSeed)
})
}

0 comments on commit b33a34f

Please sign in to comment.