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

[OSC-829] Refactoring unit test #1631

Merged
merged 19 commits into from
Aug 12, 2024
Merged

[OSC-829] Refactoring unit test #1631

merged 19 commits into from
Aug 12, 2024

Conversation

harshshahsumo
Copy link
Contributor

Refactoring unit test. Link to the ticket https://sumologic.atlassian.net/browse/OSC-829

@harshshahsumo harshshahsumo requested a review from a team as a code owner July 22, 2024 20:44
@@ -31,12 +31,36 @@ import (
semconv "go.opentelemetry.io/collector/semconv/v1.18.0"
)

func TestNewOpampAgent(t *testing.T) {
cfg := createDefaultConfig()
func setupOpampAgent(t *testing.T) (*opampAgent, *Config) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe the goal here is to convert this to be parameterized tests in go - https://stackoverflow.com/questions/72856565/go-golang-test-parameterized

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we should follow the manner:

func TestInitExporterInvalidConfiguration(t *testing.T) {
testcases := []struct {
name string
cfg *Config
expectedError error
}{
{
name: "unexpected log format",
expectedError: errors.New("unexpected log format: test_format"),
cfg: &Config{
LogFormat: "test_format",
MetricFormat: "otlp",
TraceFormat: "otlp",
ClientConfig: confighttp.ClientConfig{
Timeout: defaultTimeout,
Endpoint: "test_endpoint",
},
},
},
{
name: "unexpected metric format",
expectedError: errors.New("unexpected metric format: test_format"),
cfg: &Config{
LogFormat: "json",
MetricFormat: "test_format",
ClientConfig: confighttp.ClientConfig{
Timeout: defaultTimeout,
Endpoint: "test_endpoint",
Compression: "gzip",
},
},
},
{
name: "unsupported Carbon2 metrics format",
expectedError: errors.New("support for the carbon2 metric format was removed, please use prometheus or otlp instead"),
cfg: &Config{
LogFormat: "json",
MetricFormat: "carbon2",
ClientConfig: confighttp.ClientConfig{
Timeout: defaultTimeout,
Endpoint: "test_endpoint",
Compression: "gzip",
},
},
},
{
name: "unsupported Graphite metrics format",
expectedError: errors.New("support for the graphite metric format was removed, please use prometheus or otlp instead"),
cfg: &Config{
LogFormat: "json",
MetricFormat: "graphite",
ClientConfig: confighttp.ClientConfig{
Timeout: defaultTimeout,
Endpoint: "test_endpoint",
Compression: "gzip",
},
},
},
{
name: "unexpected trace format",
expectedError: errors.New("unexpected trace format: text"),
cfg: &Config{
LogFormat: "json",
MetricFormat: "otlp",
TraceFormat: "text",
ClientConfig: confighttp.ClientConfig{
Timeout: defaultTimeout,
Endpoint: "test_endpoint",
Compression: "gzip",
},
},
},
{
name: "no endpoint and no auth extension specified",
expectedError: errors.New("no endpoint and no auth extension specified"),
cfg: &Config{
LogFormat: "json",
MetricFormat: "otlp",
TraceFormat: "otlp",
ClientConfig: confighttp.ClientConfig{
Timeout: defaultTimeout,
Compression: "gzip",
},
},
},
}
for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
err := component.ValidateConfig(tc.cfg)
if tc.expectedError != nil {
assert.EqualError(t, err, tc.expectedError.Error())
} else {
assert.NoError(t, err)
}
})
}
}

so, we have the testcases struct with parameters and we iterate over it, so instead of having 5 different Test functions, we have just 5-element list

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed

Comment on lines 63 to 68
setup: func() (*Config, extension.Settings) {
cfg := createDefaultConfig()
set := extensiontest.NewNopSettings()
set.BuildInfo = component.BuildInfo{Version: "test version", Command: "otelcoltest"}
return cfg.(*Config), set
},
Copy link
Contributor

@sumo-drosiek sumo-drosiek Jul 26, 2024

Choose a reason for hiding this comment

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

I see the setup is the same or almost the same across many tests? Can we unify it, so we put it into test body and not as test argument?

If there are more groups (2) we can split them to two tests

In general we shouldn't repeat any code if thats possible

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed

@sumo-drosiek
Copy link
Contributor

I am have an impression that you are trying to put all tests into one function, but not sure if thats possible. The aim of the issue was to group the tests such way, we avoid copying code. In current shape, we still copy code, just we do it in the struct instead of having separate functions

Comment on lines 192 to 203
rc := remoteConfig(t, path)

changed, err := o.applyRemoteConfig(rc)
assert.NoError(t, err)
assert.True(t, changed)
assert.NotEqual(t, len(o.effectiveConfig), 0)

o.cfg.AcceptsRemoteConfiguration = false
changed, err = o.applyRemoteConfig(rc)
assert.False(t, changed)
assert.Error(t, err)
assert.Equal(t, "OpAMP agent does not accept remote configuration", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

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

seems like there're more than 2 duplicates, should this be some type of function? 🤔

@chan-tim-sumo
Copy link
Contributor

should this be broken down into multiple (2) PRs? kinda hard to see whats being changed/refactored here 🤔

assert.NotEqual(t, len(o.effectiveConfig), 0)

o.cfg.AcceptsRemoteConfiguration = false
changed, err = o.applyRemoteConfig(rc)
Copy link
Contributor

@chan-tim-sumo chan-tim-sumo Aug 2, 2024

Choose a reason for hiding this comment

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

looks like alot of the ApplyRemote tests are repeated. i feel like the test should look something like this (didn't include all the different config yaml files and a little more adjustments)

func TestApplyRemoteConfig(t *testing.T) {
    tests := []struct {
        name           string
        file           string
        acceptsRemote  bool
        expectChanged  bool
        expectError    bool
        errorMessage   string
    }{
        {"Valid Config", "testdata/opamp.d/opamp-remote-config.yaml", true, true, false, ""},
        {"Apache Config", "testdata/opamp.d/opamp-apache-config.yaml", false, false, true, "OpAMP agent does not accept remote configuration"},
        {"Invalid Config", "testdata/opamp.d/opamp-invalid-remote-config.yaml", true, false, true, "'max_elapsed_time' must be non-negative"},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            d, err := os.MkdirTemp("", "opamp.d")
            assert.NoError(t, err)
            defer os.RemoveAll(d)

            cfg := createDefaultConfig().(*Config)
            cfg.RemoteConfigurationDirectory = d
            set := extensiontest.NewNopSettings()
            o, err := newOpampAgent(cfg, set.Logger, set.BuildInfo, set.Resource)
            assert.NoError(t, err)

            cfg.AcceptsRemoteConfiguration = tt.acceptsRemote

            path := filepath.Join(tt.file)
            rb, err := os.ReadFile(path)
            assert.NoError(t, err)

            rc := &protobufs.AgentRemoteConfig{
                Config: &protobufs.AgentConfigMap{
                    ConfigMap: map[string]*protobufs.AgentConfigFile{
                        "default": {
                            Body: rb,
                        },
                    },
                },
                ConfigHash: []byte("b2b1e3e7f45d564db1c0b621bbf67008"),
            }

            changed, err := o.applyRemoteConfig(rc)
            if tt.expectError {
                assert.Error(t, err)
                assert.ErrorContains(t, err, tt.errorMessage)
            } else {
                assert.NoError(t, err)
            }
            assert.Equal(t, tt.expectChanged, changed)
        })
    }
}

so now the code isn't as repeated :D

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice!! Thanks, updated

}{
{
name: "GetAgentCapabilities",
validate: func(o *opampAgent, t *testing.T) {
Copy link
Contributor

@chan-tim-sumo chan-tim-sumo Aug 5, 2024

Choose a reason for hiding this comment

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

we shouldn't have validate as a input (I see the rest of the changes have this).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as discussed offline, we reduced 15-20 lines by using defaultSetup for all the testcases in TestDefaultOpampAgent hence not touching it

Copy link
Contributor

Choose a reason for hiding this comment

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

I mentioned the defaultSetup was good for refactoring. I feel these tests doesn't need to be inside a table test since most of them (if any) in here doesn't have repeated code. In my opinion, validate shouldn't be an input, but in this specific test case, it isn't WRONG to leave it like this.

What does the team think about this?

Copy link
Contributor

Choose a reason for hiding this comment

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

I tend to agree, validations/assertions are typically not seen as a test input.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed

cfg := createDefaultConfig().(*Config)
cfg.RemoteConfigurationDirectory = d
set := extensiontest.NewNopSettings()
o, err := newOpampAgent(cfg, set.Logger, set.BuildInfo, set.Resource)
Copy link
Contributor

@chan-tim-sumo chan-tim-sumo Aug 7, 2024

Choose a reason for hiding this comment

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

might be super nit, but 2 small things:

  1. this line newOpampAgent seems to be repeated in a bunch of tests, should this be refactored as well?
  2. i see that this test uses createDefaultConfig(), a bunch of other tests also used that before but is now replaced with defaultSetup(), should this also use that? just want to keep things as consistent if possible

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done!

Copy link
Contributor

Choose a reason for hiding this comment

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

newOpampAgent is repeated in about 5+ tests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed

cfg := createDefaultConfig().(*Config)
cfg.RemoteConfigurationDirectory = d
set := extensiontest.NewNopSettings()
o, err := newOpampAgent(cfg, set.Logger, set.BuildInfo, set.Resource)
Copy link
Contributor

Choose a reason for hiding this comment

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

newOpampAgent is repeated in about 5+ tests

semconv "go.opentelemetry.io/collector/semconv/v1.18.0"
)

func TestNewOpampAgent(t *testing.T) {
cfg := createDefaultConfig()
const errMsgRemoteConfigNotAccepted = "OpAMP agent does not accept remote configuration"
Copy link
Contributor

Choose a reason for hiding this comment

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

more than 1 const should in a block.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed

@@ -565,14 +231,13 @@ func TestHackSetEndpoint(t *testing.T) {
name: "prod sumologic url",
url: "https://open-collectors.sumologic.com/api/v1",
wantEndpoint: "wss://opamp-collectors.sumologic.com/v1/opamp",
},
{
}, {
Copy link
Contributor

Choose a reason for hiding this comment

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

typo?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

addressed

return cfg, set
}

func setupWithBuildInfo(version, command string) (*Config, extension.Settings) {
Copy link
Contributor

@chan-tim-sumo chan-tim-sumo Aug 12, 2024

Choose a reason for hiding this comment

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

can't this line be in defaultSetup?

Copy link
Contributor

@chan-tim-sumo chan-tim-sumo Aug 12, 2024

Choose a reason for hiding this comment

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

this line was being used in the test setupWithBuildInfo("test version", "otelcoltest"), shouldn't this be in the setup function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

some functions use set.BuildInfo = component.BuildInfo{Version: version, Command: command}
while some do not, so in such places we use either defaultSetup or setupWithBuilderInfo depending upon the testcase

Copy link
Contributor

Choose a reason for hiding this comment

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

i agree with that, then you can use a blank identifier to make it cleaner. so that way instead of writing smaller functions, it can all be in one function instead.

@harshshahsumo harshshahsumo merged commit a4e4ab8 into main Aug 12, 2024
32 checks passed
@harshshahsumo harshshahsumo deleted the osc829_refactor_unitTest branch August 12, 2024 22:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants