-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
[WIP] Add testutils package #423
Conversation
Signed-off-by: Frederic Branczyk <fbranczyk@gmail.com>
I'll look at this ASAP. @grobie also wants to compare this with his various test implementations he did. |
"wants" ;-) But I'll try to give it a look over the weekend. |
FTR: Travis test was flaky, I restarted it, now it is green. |
// GatherAndCompare retrieves all metrics exposed by a collector and compares it | ||
// to an expected output in the Prometheus text exposition format. | ||
// metricNames allows only comparing the given metrics. All are compared if it's nil. | ||
func GatherAndCompare(c prometheus.Collector, expected string, metricNames ...string) error { |
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.
Yes, that's exactly what I had in mind for this package.
|
||
// The below sorting code is copied form the Prometheus client library modulo the added | ||
// label pair sorting. | ||
// https://github.com/prometheus/client_golang/blob/ea6e1db4cb8127eeb0b6954f7320363e5451820f/prometheus/registry.go#L642-L684 |
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.
Which raises the question if we should move this code to an internal package…
|
||
"github.com/prometheus/client_golang/prometheus" | ||
dto "github.com/prometheus/client_model/go" | ||
"github.com/prometheus/common/expfmt" |
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.
Nit: Traditional Prometheus grouping here would be:
import (
 "bytes"
 "fmt"
 "reflect"
 "sort"
 "strings"

"github.com/prometheus/common/expfmt"
 dto "github.com/prometheus/client_model/go"
 "github.com/prometheus/client_golang/prometheus"
)
As in the comments (besides the nits), this is what I had in mind as one of the helpers in this upcoming package. So yes, full agreement we should have this here. Suggested course of action:
(Reading the following is optional: Very fundamentally, I think this single-value retrieval is not the right (tm) approach to testing, but we can/should still provide it here for now. The "gather all the metric and compare it to a golden set of metrics" is a nice end-to-end test. For unit-testing, the only thing I want to test is if, for example, an |
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 minor remarks after a more detailed look. Otherwise, this looks very good to me. Let's give @grobie time to respond over the weekend.
I don't think you need to invest extra effort into documentation at this point. As said, I'll add that single-value extraction helper to this package in a separate PR, and then I can write a package doc comment to explain how to do the testing.
expected = removeUnusedWhitespace(expected) | ||
|
||
reg := prometheus.NewPedanticRegistry() | ||
if err := reg.Register(c); err != nil { |
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.
How about leaving these two lines to the caller? I.e. make GatherAndCompare
accept a Gatherer
rather than a Collector
. This would also let the name of the function appear more consistent.
With this approach, a caller could act on the registry level, which allows testing even if metrics are coming from a library that does not allow access to individual collectors, but a registry with all the collectors already registered is readily available. Also, the caller can decide if a pedantic registry should be used or not.
If a caller wants to test a single Collector, it's easy to wrap GatherAndCompare
with these two lines. Or wo could even provide a CollectAndCompare
.
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.
As a concrete example, right now you couldn't test many Collectors because the "cheat" by providing a dummy Desc, which the pedantic registry would flag. (#47 will allow to implement those Collectors cleanly, and I'll work in this issue this evening (coincidentally).)
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 using a gatherer.
} | ||
|
||
// The Prometheus metrics representation parser expects an empty line at the | ||
// end otherwise fails with an unexpected EOF error. |
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.
That's misleading. It expects the last line to end on a \n
, but that line does not need to be empty.
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 is a great start! I'd wish for a more generic interface. Shorthands can be provided with additional functions if desired.
// GatherAndCompare retrieves all metrics exposed by a collector and compares it | ||
// to an expected output in the Prometheus text exposition format. | ||
// metricNames allows only comparing the given metrics. All are compared if it's nil. | ||
func GatherAndCompare(c prometheus.Collector, expected string, metricNames ...string) error { |
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 interface and implementation couple two independent concerns: verifying the given collector and formatting a custom input format. Instead of this coupling and assuming all users will format their expectation in this special way, using the io.Reader
interface would leave it to the user how to provide the expectation. For example, this allows projects with extensive end to end tests to simply provide a File reader to such fixtures.
The current custom sanitizer implementation in removeUnusedWhitespace
can be provided as another public function which will satisfy the reader interface.
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 certainly makes sense to use an io.Reader
instead of a string
as input.
I don't quite follow the line of argument with the removeUnusedWhitespace
, though. In fact, now that I have looked more closely, I got the suspicion the removeUnusedWhitespace
is not even needed. The provided text for "expected" is parsed with the text format parses. (Thus, the parser should eat all the spurious whitespace.) For the diff output, we then re-create the text format from the protobuf representation and get a sanitized output anyway.
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.
Even better if we don't need it.
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 played with the tests, and it really looks to me we can just remove removeUnusedWhitespace
. :o)
expected = removeUnusedWhitespace(expected) | ||
|
||
reg := prometheus.NewPedanticRegistry() | ||
if err := reg.Register(c); err != nil { |
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 using a gatherer.
Another point: Since these testutils are only for testing the exposition client, I'd make it a sub-package of client_golang/prometheus rather than client_golang directly. |
@brancz will you be able to address the comments given? I think the changes will be quite straight forward, and then we can merge this. If you are busy right now, I can take over from here. |
Yeah I have a big deadline that is keeping me busy at least for today and next week, feel free to take over! 🙂 |
I was a bit optimistic. Preparations for my grand cycle tour took precedence, and now I'm on said cycle tour, with not much time for anything else. @brancz if you find time, please go ahead to work on this. Should I find time before that, I'll let you know here. |
I'm still busy as well, but might be able to get to it next week. |
Superseded by #445 . |
This is mostly a copy and paste from the testutils package that we have originally extracted from the node-exporter test code (I think). I opened #422 about this and @beorn7 suggested me to open a PR and we start discussing. I haven't added documentation yet in case we want to change any of this. If we are happy with this I am more than happy to add more documentation.
Note that this primarily revolves around testing (custom) collectors, it is less useful for standard Counter/Gauge/Histogram/Summary metrics, unless the tested entity is a wrapping object around those (I suppose it could also be useful for single metrics but I see it being most useful for collectors).
Also related: #58
Signed-off-by: Frederic Branczyk fbranczyk@gmail.com
@beorn7 @brian-brazil @grobie