-
Notifications
You must be signed in to change notification settings - Fork 49
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
c42040f
to
d292347
Compare
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
@invidian PTAL at the latest commit, I have tried to make the test generic using some reflect magic. This way is better to test conversion because we don't have to change the tests on every update. The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your effort on that @surajssd. I think this testing code doesn't look that bad from implementation point of view, even tough it uses reflect
, so seems a bit like magic, but test definitions looks really crispy now, I really like them (except reflect.ValueOf
bit I mentioned).
Perhaps @johananl could also have a look into it.
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
pkg/components/prometheus-operator/component_conversion_test.go
Outdated
Show resolved
Hide resolved
0107dcb
to
8efc6f5
Compare
6b3f2dd
to
c414107
Compare
c414107
to
556ee55
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some more thoughts/suggestions.
pkg/components/internal/jsonpath.go
Outdated
// TODO: Add type switch here for concrete types. | ||
return got.Interface() | ||
default: | ||
t.Fatalf("Unknown type of the object extracted from the converted YAML: %v", got.Kind()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this ever occur?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know yet. Since our tests are not that expansive. But it is best to capture it here than someone wasting time later debugging as to why some json paths won't work.
pkg/components/internal/jsonpath.go
Outdated
// getJSONPathValue extracts an object at a JSON path from a YAML config, and returns interface | ||
// object. | ||
func getJSONPathValue(t *testing.T, yamlConfig string, jsonPath string) interface{} { | ||
u := getUnstructredObj(t, yamlConfig) | ||
got := getValFromObject(t, jsonPath, u) | ||
|
||
switch got.Kind() { //nolint:exhaustive | ||
case reflect.Interface: | ||
// TODO: Add type switch here for concrete types. | ||
return got.Interface() | ||
default: | ||
t.Fatalf("Unknown type of the object extracted from the converted YAML: %v", got.Kind()) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// MatchJSONPathStringValue is a helper function for component's unit tests. It compares a string | ||
// value of a JSON path in a YAML config. | ||
func MatchJSONPathStringValue(t *testing.T, yamlConfig string, jsonPath string, expected string) { | ||
obj := getJSONPathValue(t, yamlConfig, jsonPath) | ||
|
||
got, ok := obj.(string) | ||
if !ok { | ||
t.Fatalf("Value is not string: %#v", obj) | ||
} | ||
|
||
if got != expected { | ||
t.Fatalf("Expected: %s, Got: %s", expected, got) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really have a feeling that using a struct here would make it more extensible (with basic types, not with reflect.Value
) and make code simpler, because then switch got.Kind() {
can just compare with ExpectedString
field if found value is string and fail the test otherwise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With reflect.Value or with the struct we will have a switch eventually. As we start supporting more values we will end up with it, its just matter of when.
component := newComponent() | ||
m := testutil.RenderManifests(t, component, name, tc.inputConfig) | ||
gotConfig := testutil.ConfigFromMap(t, m, tc.expectedManifestName) | ||
|
||
testutil.MatchJSONPathStringValue(t, gotConfig, tc.jsonPath, tc.expected) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could have one more helper to avoid copy-pasting those lines across components. The only changing bit here is component
, though we need to make sure it's the "fresh" one for every test case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be fresh one because we are calling newComponent
unlike some tests where we extract the component object from the global map of components.
About putting it into a separate helper to avoid copying these three lines, I think lets wait until there are multiple of such places where we see it becoming very painful to maintain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is to re-use this code for multiple components from the very beginning. I don't understand why we can't make it easy to use for other components from the first version. It seems to me just like deferring this work to the person adding tests for other components...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the same argument is true for not using reflect.Value
. Why can't we use it right now it is like deferring the work to next person adding tests.
Also the problem with adding a helper here is that now we just don't have to pass component, but we also need to pass the name, the expected manifest name, json path and expected value. We can solve it by using struct like you mentioned before, dump everything in the struct and send that. But like I said we decided to revisit the struct vs reflect.Value later so I will defer this as well.
556ee55
to
a1cf893
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR @surajssd.
My main concern is around moving a lot of the unit test logic to a separate package (which has "util" in its name...). I think that a prime concern when writing code is how easy it is to understand the code. It's a trade-off: On one hand, moving code to a central place introduces more "jumping around" when reading the code. On the other hand, excessive repetition within a source file can also make it hard to read and as a result - hard to understand.
In this case I am not convinced that we actually need to move any code to a new package. I feel that right now the principle we are following is "eliminate duplication at all costs" whereas I believe a better principle is "make code easy to understand at (nearly) all costs" 🙂 When faced with a choice between adding duplication and improving clarity, my bias is towards clarity. As a side note, unit tests specifically are an area in Go code which tends to be repetitive. I think this is perfectly fine because you want unit tests to be self-contained.
I don't want to block the PR on this and I would have to spend extra time to come up with an alternative, but I am worried that we are introducing a new pattern here (managing most of our test logic under testutil
) which will be replicated from now on. I'll leave it to your good judgement, but please keep in mind that we may be introducing a new form of technical debt here.
Added a few additional comments inline, mainly around language.
func ConfigFromMap(t *testing.T, m map[string]string, k string) string { | ||
ret, ok := m[k] | ||
if !ok { | ||
t.Fatalf("Config not found with filename: %q", k) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we shouldn't call t.Fatalf
from a helper function. The caller has to look at the implementation to figure out we're doing this, and the result could be that the helper function decides to terminate all the unit tests (which is what Fatalf
does). IMO this is taking too much responsibility. I think we should consider returning an error and letting the caller decide what to do.
This applies for all other similar cases in pkg/components/internal/testutil
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The caller has to look at the implementation to figure out we're doing this, and the result could be that the helper function decides to terminate all the unit tests (which is what Fatalf does).
Fatalf
only terminates single unit test. See https://golang.org/pkg/testing/#T.FailNow for more details.
Errorf
can be used if you have data ready and you perform N checks on it, then each check may use Errorf
. And we use Fatal
here to break the execution flow, as otherwise we would test on bad data. IMO Fatalf
use here is OK.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To stop the sub-test from proceeding I have used Fatalf
. About returning things around in a test code makes no sense because we are already passing the test object (just like context) which can control the termination of the test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still think a helper function in package B shouldn't terminate a test in package A, but I don't want to block on this. I think we will likely realize we want to refactor this behavior in the future.
got, ok := obj.(string) | ||
if !ok { | ||
t.Fatalf("Value is not string: %#v", obj) | ||
} | ||
|
||
if got != expected { | ||
t.Fatalf("Expected: %s, Got: %s", expected, got) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am strongly against extracting this type of checks to a helper function. In my opinion these checks are the thing we want to include in each individual unit test, even if it makes things a bit repetitive. The reader should be able to figure out what a unit tests does without jumping around the codebase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now it looks simplistic because we are just comparing strings. We would also add support for strings, numbers, arrays of concrete types then it will become complicated. So I would like to keep it here. So it becomes easy to extend.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO we shouldn't refactor things now because things may change in the future. We will be a lot smarter regarding how to refactor and simplify things when we actually see the problem. If the reader has to jump to a different source file when reading a unit test to understand what the test does, I think it's not ideal.
Forgot to say that I wasn't sure how to test the |
a1cf893
to
be79773
Compare
be79773
to
dbf93ea
Compare
This commit adds the feature removed sometime back called `external_url` this allows the user to choose where(URL) the prometheus is exposed. Signed-off-by: Suraj Deshmukh <suraj@kinvolk.io>
1ede981
to
79255db
Compare
There is still few threads opened which I would like to get resolved/fixed, but on the other hand I don't want to block merging this PR, so if others are fine with it, we can merge it and maybe we can re-visit it next time someone touches this code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to merge as far as I'm concerned (after fixing the typo).
I still don't like the approach we've taken regarding managing unit tests as I'm convinced we are reducing duplication at the cost of reducing readability and clarity. Let's leave it at that and consider refactoring if this code becomes hard to maintain.
- This commit adds new conversion test to verify if the external_url works fine. - This commit adds test cases in TestRenderManifest to verify if the prometheus external_url and prometheus ingress host matches. Signed-off-by: Suraj Deshmukh <suraj@kinvolk.io>
79255db
to
de70624
Compare
This commit adds the feature removed sometime back called
external_url
this allows the user to choose where(URL) the prometheus is exposed.
Fixes #826
TODOS: