Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature config #82

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package config

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path"

"sigs.k8s.io/yaml"
)

var globalConfig map[string][]byte
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no globals please

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should part of the Environment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you propose to implement it instead? Note that by design the config reading is one per process and I expect the user to invoke it in TestMain

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should part of the Environment

Sounds like a good idea

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nono i'm sorry, just checked, it doesn't work using env, because a feature can be built without the env

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I believe we should leave it as a global as is now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because a feature can be built without the env

But a feature is executed against an environment - and technically are we not 'configuring' the feature in order for the test to pass?

I feel like this is related to: #66

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically are we not 'configuring' the feature in order for the test to pass?

But the configuration happens before the test runs


// ReadConfigNamed is like ReadConfig, but you can specify a file name
func ReadConfigNamed(name string) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of the global, you can return the parsed config.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not the way i thought to configuration. I would love to have a global conf for the whole test suite and then single "feature factories" read that config as they want.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the plan was to have that but do it in the testing domain, like eventing or serving and not the testing framework

// I expect the config in the main
dir, err := os.Getwd()
if err != nil {
panic(err)
}

configPath := path.Join(dir, name)

log.Println("Reading config from ", configPath)

out, err := ioutil.ReadFile(configPath)
if err != nil {
panic(err)
}

m := make(map[string]interface{})
err = yaml.Unmarshal(out, &m)
if err != nil {
panic(err)
}

globalConfig = make(map[string][]byte)
for k, v := range m {
b, err := json.Marshal(v)
if err != nil {
panic(err)
}
globalConfig[k] = b
}
}

// ReadConfig reads the config
func ReadConfig() {
ReadConfigNamed("config.yaml")
}

// UnmarshalConfig reads the global config and unmarshal it
func UnmarshalConfig(testName string, out interface{}) error {
if globalConfig == nil {
return errors.New("there's no global config!!! Make sure you invoke ReadConfig() or ReadConfigNamed(name) in your TestMain")
}
in := globalConfig[testName]
if in == nil {
return fmt.Errorf("there's no config for test name %s", testName)
}
return json.Unmarshal(in, out)
}
16 changes: 16 additions & 0 deletions pkg/feature/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package feature

import (
"fmt"

"knative.dev/reconciler-test/pkg/config"
)

func (f *Feature) Config(out interface{}) interface{} {
err := config.UnmarshalConfig(f.Name, out)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just name might not be enough to know which config to load. For example, "BrokerTest" is likely going to be something that is used a few times?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My expectation is that test names are unique. I'm thinking about a "nested" configuration, but as a next step

if err != nil {
panic(fmt.Sprintf("cannot read config: %v", err))
}

return out
}
21 changes: 21 additions & 0 deletions pkg/feature/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package feature
import (
"context"
"fmt"
"runtime"
"strings"
"testing"
)

Expand All @@ -28,6 +30,25 @@ type Feature struct {
Steps []Step
}

func NewFeatureNamed(name string) *Feature {
f := new(Feature)
f.Name = name
return f
}

func NewFeature() *Feature {
f := new(Feature)

pc, _, _, _ := runtime.Caller(1)
caller := runtime.FuncForPC(pc)
if caller != nil {
splitted := strings.Split(caller.Name(), ".")
f.Name = splitted[len(splitted)-1]
}

return f
}

// StepFn is the function signature for steps.
type StepFn func(ctx context.Context, t *testing.T)

Expand Down
3 changes: 3 additions & 0 deletions test/example/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RecorderFeature:
Sink: my-sink
Source: my-sender
2 changes: 1 addition & 1 deletion test/example/examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestRecorder(t *testing.T) {
// environment settings. Additional options can be passed to Environment()
// if customization is required.
ctx, env := global.Environment(
knative.WithKnativeNamespace("knative-reconciler-test"),
knative.WithKnativeNamespace("knative-eventing"),
knative.WithLoggingConfig,
knative.WithTracingConfig,
k8s.WithEventListener,
Expand Down
4 changes: 4 additions & 0 deletions test/example/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"knative.dev/pkg/injection"
_ "knative.dev/pkg/system/testing"

"knative.dev/reconciler-test/pkg/config"
"knative.dev/reconciler-test/pkg/environment"
)

Expand All @@ -48,6 +49,9 @@ func TestMain(m *testing.M) {
// framework as well as any additional flags included in the integration.
flag.Parse()

// Read config file for the tests
config.ReadConfig()

// EnableInjectionOrDie will enable client injection, this is used by the
// testing framework for namespace management, and could be leveraged by
// features to pull Kubernetes clients or the test environment out of the
Expand Down
14 changes: 10 additions & 4 deletions test/example/recorder_feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ import (
"knative.dev/reconciler-test/pkg/k8s"
)

type recorderFeatureConfig struct {
Sink string
Source string
}

func RecorderFeature() *feature.Feature {
svc := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"}
f := feature.NewFeature()
conf := f.Config(&recorderFeatureConfig{}).(*recorderFeatureConfig)

from := feature.MakeRandomK8sName("sender")
to := feature.MakeRandomK8sName("recorder")
svc := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"}

f := new(feature.Feature)
from := feature.MakeRandomK8sName(conf.Source)
to := feature.MakeRandomK8sName(conf.Sink)

event := FullEvent()

Expand Down