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

[WIP] Add testutils package #423

Closed
wants to merge 1 commit into from
Closed

Conversation

brancz
Copy link
Member

@brancz brancz commented Jul 3, 2018

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

Signed-off-by: Frederic Branczyk <fbranczyk@gmail.com>
@beorn7
Copy link
Member

beorn7 commented Jul 6, 2018

I'll look at this ASAP. @grobie also wants to compare this with his various test implementations he did.
We both are somewhat busy, though, but we'll keep this on our respective plates.

@grobie
Copy link
Member

grobie commented Jul 6, 2018

"wants" ;-) But I'll try to give it a look over the weekend.

@beorn7
Copy link
Member

beorn7 commented Jul 6, 2018

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 {
Copy link
Member

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
Copy link
Member

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"
Copy link
Member

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"
)

@beorn7
Copy link
Member

beorn7 commented Jul 6, 2018

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:

  1. Let's wait for @grobie to chime in whether this is what he wants, too.
  2. Improve code-level details (started with a few nits from my side already, see comments).
  3. Merge this.
  4. I will then add the single-value retrieval helper suggested in Improve testability #58 in a follow-up PR.

(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 Inc() call has happened on a Counter. However, retrieving the counter value before and after the Inc call to verify the increment by one involves the whole counter machinery (which I don't want to test) and doesn't play well with concurrency. It would be better to use test mocks of metric types that directly report if the call has happened and do nothing else. However, with the current state of client_golang, the interfaces are less clear than they could be, and not everything is an interface that one could implement with test mocks. Thus, this part will follow later, cf. #230, and for now I'll provide that single-value retrieval helper, but at least it is nicely locked away in this package, and it can be deprecated without breaking anything in the main packages.)

Copy link
Member

@beorn7 beorn7 left a 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 {
Copy link
Member

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.

Copy link
Member

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).)

Copy link
Member

@grobie grobie Jul 9, 2018

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.
Copy link
Member

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.

Copy link
Member

@grobie grobie left a 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 {
Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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.

Copy link
Member

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 {
Copy link
Member

@grobie grobie Jul 9, 2018

Choose a reason for hiding this comment

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

👍 to using a gatherer.

@beorn7
Copy link
Member

beorn7 commented Jul 9, 2018

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.

@beorn7
Copy link
Member

beorn7 commented Jul 13, 2018

@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.

@brancz
Copy link
Member Author

brancz commented Jul 13, 2018

Yeah I have a big deadline that is keeping me busy at least for today and next week, feel free to take over! 🙂

@beorn7
Copy link
Member

beorn7 commented Jul 19, 2018

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.

@brancz
Copy link
Member Author

brancz commented Jul 19, 2018

I'm still busy as well, but might be able to get to it next week.

@beorn7 beorn7 mentioned this pull request Aug 22, 2018
@beorn7
Copy link
Member

beorn7 commented Aug 22, 2018

Superseded by #445 .

@beorn7 beorn7 closed this Aug 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants