-
Notifications
You must be signed in to change notification settings - Fork 5.1k
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
Use test builder. #1871
Use test builder. #1871
Conversation
/cc @marco-jantke happy to have your review too. :-) |
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.
Added some minor comments. PR looks great to me, uniformity FTW :D
BTW the validation make target is currently still failing.
provider/marathon/marathon_test.go
Outdated
}, | ||
}), | ||
), | ||
|
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.
Unnecessary newline.
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.
Argh, damn you unintentional blank lines!
Removed. :-)
provider/marathon/marathon_test.go
Outdated
Ports: []int{}, | ||
}, | ||
desc: "port definition available", | ||
application: application( |
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.
Shouldn't you use the ipAddrPerTask
here to simulate the same behaviour or did you remove it by intend?
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 did remove it intentionally for two reasons (should have commented this in the first place, sorry):
- My original thinking was that the IP-per-task setup would serve as a safeguard to running into an undesirable happy path due to a bug in the code (i.e., false positive). On closer inspection, this seems highly unlikely since we can't really produce the expected port result of 443 without configuring it somewhere, which we only do for the test case at hand that we intend to cover.
- Configuring both port definitions and IPs-per-task should actually not be allowed: go-marathon explicitly clears the former when setting the latter because the Marathon API would otherwise reject our API request. In that sense, it makes even less sense to set up both in the test (though I wouldn't relax the production code logic because of that because you never know how the Marathon API may evolve over time).
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.
Np, SGTM.
provider/marathon/marathon_test.go
Outdated
provider := &Provider{} | ||
actual := provider.getPassHostHeader(c.application) | ||
if actual != c.expected { | ||
t.Fatalf("actual %q, expected %q", actual, c.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.
I think t.Errorf
be sufficient here.
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.
Here and almost everywhere else -- updated.
provider/marathon/marathon_test.go
Outdated
{ | ||
desc: "label missing", | ||
application: application(), | ||
expected: "NetworkErrorRatio() > 1", |
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.
Just out of curiosity. This means there is always a CircuitBreaker in place for Backends provided by Marathon, but the expression should never evaluate to true? Is this intended? It looks like it would only add one middleware which can not have any effect.
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.
This part is somewhat confusing: While we do return a default value, we only include circuit-breaker settings when the label is actually set.
It might make sense to change the default return value to an empty string to improve clarity. Let's consider that for a future PR.
provider/marathon/marathon_test.go
Outdated
want string | ||
cases := []struct { | ||
desc string | ||
value *string |
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.
AFAICS its not necessary anymore to use *string
as we only dereference the pointer when passing it to AddLabel
. Or do I miss something?
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.
You're right: There's still one test left where we want to distinguish set/unset/omitted, but not this one.
Simplified.
provider/marathon/marathon_test.go
Outdated
want string | ||
cases := []struct { | ||
desc string | ||
value *string |
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.
Same as previous comment.
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.
Simplified too.
@marco-jantke all comments addressed. |
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 addressing. LGTM 👍
@timoreimann could you fix the linting?
|
@ldez should be good now. |
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.
Could you do the same approach with backends and frontends:
expectedFrontends: map[string]*types.Frontend{
"frontend-app": {
Backend: "backend-app",
Routes: map[string]types.Route{
"route-host-app": {
Rule: "Host:app.docker.localhost",
},
},
},
},
expectedBackends: map[string]*types.Backend{
"backend-app": {
Servers: map[string]types.Server{
"server-task": {
URL: "http://localhost:80",
Weight: 0,
},
},
CircuitBreaker: nil,
},
},
provider/marathon/marathon_test.go
Outdated
} | ||
}) | ||
} | ||
} | ||
|
||
func stringp(s string) *string { |
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.
maybe move this into testhelpers
like Intp
?
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 idea, done.
provider/marathon/marathon_test.go
Outdated
application: marathon.Application{ | ||
Labels: &map[string]string{}}, | ||
desc: "label missing", | ||
application: application(), |
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 avoid the ambiguity of names, I rather name builders like anApplication(...)
and if necessary options like withLabels(...)
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.
In general, I'd prefer not to use longer names in order to keep things concise since builder functions are used very frequently. IMHO, we shouldn't have extra verbs on builder operations.
anApplication
looks quite weird to my eyes. :) How about createApp
and createTask
instead?
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.
create
prefix is not fluent syntax 😉 (builders must be fluent, I think)
@vdemeester do that like me http://vincent.demeester.fr/posts/2017-01-01-go-testing-functionnal-builders/
But in this case, I prefer you don't change anything instead of use a create
prefix.
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.
Okay, now I'm confused: The blog post uses a/an and with prefixes. However, our Docker builder comes with just two with functions and otherwise uses no prefixes. ¯_(ツ)_/¯
@vdemeester: can you help me out here? :)
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.
@vdemeester's out-of-band feedback is that he more often than not drops the with
prefix in his builders. :-)
So I just added the create
prefix. Not sure if it violates fluent builder syntaxes. I did this a lot back in my Java days; maybe that can be considered a sin of the past. ;)
provider/marathon/builder_test.go
Outdated
} | ||
} | ||
|
||
func labels(l map[string]string) func(*marathon.Application) { |
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 really show the need to use a map here.
Why not use a simple function like this:
func label(key, value string) {
return func(app *marathon.Application) {
app.AddLabel(k, v)
}
}
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 was leaning towards such an implementation too at some point, so I made the change. 👍
Re: backend/frontend refactoring: I'd like to leave that for a separate PR as this one is already fairly big. Plus, we need to "builderize" the configuration in numerous places (server, kubernetes, etc.), so I'd rather do it in one focused wash (which will also make it easier to see how we need to design that builder). |
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.
Few comments but overall great work !
LGTM 🦁
func Stringp(s string) *string { | ||
return &s | ||
} | ||
|
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.
one \n
too much 😝
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.
Begone!
provider/marathon/marathon_test.go
Outdated
}, | ||
}, | ||
desc: "simple application", | ||
application: application(appPorts(80)), |
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.
You seem to use appPorts(80)
a lot, maybe it should be a default value in the application (and setting it only when you actually care about the value in your 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 should already only be setting it when I actually need it. (There are a number of application()
-only invocations where I don't pass the appPorts
builder function.)
I thought about setting default ports on the app and task but dropped the idea eventually: We have a lot of different cases where we sometimes need just the Marathon port and sometimes the task port; sometimes they need to be equal and sometimes they don't.
So if I added a default, I'd also need to override it explicitly sometimes. In the end, this was too intransparent for me, so I went down the slightly more explicit and but also more clear route.
provider/marathon/marathon_test.go
Outdated
label(types.LabelBackendLoadbalancerMethod, "drr"), | ||
label(types.LabelBackendCircuitbreakerExpression, "NetworkErrorRatio() > 0.5"), | ||
), | ||
task: localhostTask(taskPorts(80)), |
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.
Same for taskPorts
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.
See above. :)
94f762e
to
62eaa4d
Compare
Rebased and addressed all comments for now. PTAL again. |
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.
LGTM
30e50ae
to
6dcc278
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.
LGTM
|
||
// Functions related to building applications. | ||
|
||
func createApplication(ops ...func(*marathon.Application)) marathon.Application { |
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 conventional way in Java it's a build
prefix.
- https://docs.oracle.com/javase/8/docs/api/java/util/Calendar.Builder.html
- https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.Builder.html
- https://github.com/spring-projects/spring-framework/blob/bc14c5ba83e1f211628456bbccce7b2531aac58c/spring-test/src/test/java/org/springframework/test/context/support/BootstrapTestUtilsMergedConfigTests.java
- https://github.com/spring-projects/spring-framework/blob/7018804e8451fa935e8715754de43a3ef62fa5c8/spring-webflux/src/test/java/org/springframework/web/reactive/function/server/RequestPredicatesTests.java
I prefer you simply remove the create
prefix.
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 approve but I don't agree with the create
prefix.
This change introduces the builder pattern to the Marathon unit tests in order to simplify and reduce the amount of testing boilerplate. Additional changes: - Add missing unit tests. - Make all tests look consistent. - Use dedicated type for task states for increased type safety. - Remove obsoleted getApplication function.
6dcc278
to
4546613
Compare
I only added the prefix because I understood you asked for it. Will happily remove it again in another PR. :-) |
This change introduces the builder pattern to the Marathon unit tests in order to simplify and reduce the amount of testing boilerplate.
Additional changes:
getApplication
function.Note: I pushed the branch to the containous repo because I intend to submit another PR based onto this one.
@containous/marathon @containous/traefik PTAL.