Skip to content

Commit

Permalink
Implement unit and integration tests
Browse files Browse the repository at this point in the history
  • Loading branch information
chrischdi committed Sep 22, 2023
1 parent 0be5616 commit 0b045d6
Show file tree
Hide file tree
Showing 9 changed files with 689 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
Copyright 2023 The Kubernetes Authors All rights reserved.
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 generator

import (
"bytes"
"io"
"os"
"path"
"testing"

"sigs.k8s.io/controller-tools/pkg/genall"
"sigs.k8s.io/controller-tools/pkg/loader"
"sigs.k8s.io/controller-tools/pkg/markers"
)

func Test_Generate(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Error(err)
}

optionsRegistry := &markers.Registry{}

metricGenerator := CustomResourceConfigGenerator{}
if err := metricGenerator.RegisterMarkers(optionsRegistry); err != nil {
t.Error(err)
}

out := &outputRule{
buf: &bytes.Buffer{},
}

// Load the passed packages as roots.
roots, err := loader.LoadRoots(path.Join(cwd, "testdata", "..."))
if err != nil {
t.Errorf("loading packages %v", err)
}

gen := CustomResourceConfigGenerator{}

generationContext := &genall.GenerationContext{
Collector: &markers.Collector{Registry: optionsRegistry},
Roots: roots,
Checker: &loader.TypeChecker{},
OutputRule: out,
}

t.Log("Trying to generate a custom resource configuration from the loaded packages")

if err := gen.Generate(generationContext); err != nil {
t.Error(err)
}
output := out.buf.String()

t.Log("Comparing output to testdata to check for regressions")

expectedFile, err := os.ReadFile(path.Clean(path.Join(cwd, "testdata", "foo-config.yaml")))
if err != nil {
t.Error(err)
}

if string(expectedFile) != output {
t.Log("output:")
t.Log(output)
t.Error("Expected output to match file testdata/foo-config.yaml")
}
}

type outputRule struct {
buf *bytes.Buffer
}

func (o *outputRule) Open(_ *loader.Package, _ string) (io.WriteCloser, error) {
return nopCloser{o.buf}, nil
}

type nopCloser struct {
io.Writer
}

func (n nopCloser) Close() error {
return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
kind: CustomResourceStateMetrics
spec:
resources:
- errorLogV: 0
groupVersionKind:
group: bar.example.com
kind: Foo
version: foo
labelsFromPath:
cluster_name:
- metadata
- labels
- cluster.x-k8s.io/cluster-name
name:
- metadata
- name
metricNamePrefix: foo
metrics:
- each:
gauge:
labelFromKey: ""
nilIsZero: false
path:
- metadata
- creationTimestamp
valueFrom: null
type: Gauge
help: Unix creation timestamp.
name: created
- each:
info:
labelFromKey: ""
labelsFromPath:
owner_is_controller:
- controller
owner_kind:
- kind
owner_name:
- name
owner_uid:
- uid
path:
- metadata
- ownerReferences
type: Info
help: Owner references.
name: owner
- each:
stateSet:
labelName: status
labelsFromPath:
type:
- type
list:
- "True"
- "False"
- Unknown
path:
- status
- conditions
valueFrom:
- status
type: StateSet
help: The condition of a foo.
name: status_condition
- each:
gauge:
labelFromKey: ""
labelsFromPath:
status:
- status
type:
- type
nilIsZero: false
path:
- status
- conditions
valueFrom:
- lastTransitionTime
type: Gauge
help: The condition last transition time of a foo.
name: status_condition_last_transition_time
resourcePlural: ""
66 changes: 66 additions & 0 deletions pkg/customresourcestate/generate/generator/testdata/foo_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Copyright 2023 The Kubernetes Authors All rights reserved.
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.
*/

//go:generate sh -c "go run ../../../../../ generate ./... > foo-config.yaml"

// +groupName=bar.example.com
package foo

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// FooSpec is the spec of Foo.
type FooSpec struct {
// This tests that defaulted fields are stripped for v1beta1,
// but not for v1
DefaultedString string `json:"defaultedString"`
}

// FooStatus is the status of Foo.
type FooStatus struct {
// +Metrics:stateset:name="status_condition",help="The condition of a foo.",labelName="status",JSONPath=".status",list={"True","False","Unknown"},labelsFromPath={"type":".type"}
// +Metrics:gauge:name="status_condition_last_transition_time",help="The condition last transition time of a foo.",valueFrom=.lastTransitionTime,labelsFromPath={"type":".type","status":".status"}
Conditions Condition `json:"conditions,omitempty"`
}

// Foo is a test object.
// +Metrics:gvk:namePrefix="foo"
// +Metrics:labelFromPath:name="name",JSONPath=".metadata.name"
// +Metrics:gauge:name="created",JSONPath=".metadata.creationTimestamp",help="Unix creation timestamp."
// +Metrics:info:name="owner",JSONPath=".metadata.ownerReferences",help="Owner references.",labelsFromPath={owner_is_controller:".controller",owner_kind:".kind",owner_name:".name",owner_uid:".uid"}
// +Metrics:labelFromPath:name="cluster_name",JSONPath=.metadata.labels.cluster\.x-k8s\.io/cluster-name
type Foo struct {
// TypeMeta comments should NOT appear in the CRD spec
metav1.TypeMeta `json:",inline"`
// ObjectMeta comments should NOT appear in the CRD spec
metav1.ObjectMeta `json:"metadata,omitempty"`

// Spec comments SHOULD appear in the CRD spec
Spec FooSpec `json:"spec,omitempty"`
// Status comments SHOULD appear in the CRD spec
Status FooStatus `json:"status,omitempty"`
}

// Condition is a test condition.
type Condition struct {
// Type of condition.
Type string `json:"type"`
// Status of condition.
Status string `json:"status"`
// LastTransitionTime of condition.
LastTransitionTime metav1.Time `json:"lastTransitionTime"`
}
123 changes: 123 additions & 0 deletions pkg/customresourcestate/generate/markers/helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
Copyright 2023 The Kubernetes Authors All rights reserved.
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 markers

import (
"reflect"
"testing"

"k8s.io/kube-state-metrics/v2/pkg/customresourcestate"
)

func Test_jsonPath_Parse(t *testing.T) {
tests := []struct {
name string
j jsonPath
want []string
wantErr bool
}{
{
name: "empty input",
j: "",
want: []string{},
wantErr: false,
},
{
name: "dot input",
j: ".",
want: []string{""},
wantErr: false,
},
{
name: "some path input",
j: ".foo.bar",
want: []string{"foo", "bar"},
wantErr: false,
},
{
name: "invalid character ,",
j: ".foo,.bar",
wantErr: true,
},
{
name: "invalid closure",
j: "{.foo}",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.j.Parse()
if (err != nil) != tt.wantErr {
t.Errorf("jsonPath.Parse() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("jsonPath.Parse() = %v, want %v", got, tt.want)
}
})
}
}

func Test_newMetricMeta(t *testing.T) {
tests := []struct {
name string
basePath []string
j jsonPath
jsonLabelsFromPath map[string]jsonPath
want customresourcestate.MetricMeta
}{
{
name: "with basePath and jsonpath, without jsonLabelsFromPath",
basePath: []string{"foo"},
j: jsonPath(".bar"),
jsonLabelsFromPath: map[string]jsonPath{},
want: customresourcestate.MetricMeta{
Path: []string{"foo", "bar"},
LabelsFromPath: map[string][]string{},
},
},
{
name: "with basePath, jsonpath and jsonLabelsFromPath",
basePath: []string{"foo"},
j: jsonPath(".bar"),
jsonLabelsFromPath: map[string]jsonPath{"some": ".label.from.path"},
want: customresourcestate.MetricMeta{
Path: []string{"foo", "bar"},
LabelsFromPath: map[string][]string{
"some": {"label", "from", "path"},
},
},
},
{
name: "no basePath, jsonpath and jsonLabelsFromPath",
basePath: []string{},
j: jsonPath(""),
jsonLabelsFromPath: map[string]jsonPath{},
want: customresourcestate.MetricMeta{
Path: []string{},
LabelsFromPath: map[string][]string{},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := newMetricMeta(tt.basePath, tt.j, tt.jsonLabelsFromPath); !reflect.DeepEqual(got, tt.want) {
t.Errorf("newMetricMeta() = %v, want %v", got, tt.want)
}
})
}
}
Loading

0 comments on commit 0b045d6

Please sign in to comment.