-
Notifications
You must be signed in to change notification settings - Fork 18k
testing: structured output for test attributes #43936
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
Comments
I looked into this a bit; Unfortunately I don't think it can work quite the way you've proposed (at least, not with the current testing architecture). In particular, testing likes to emit everything in a text stream, and the JSON blobs are reconstituted with As an additional wrinkle, It SHOULD be possible, however, to have something which worked like: func TestFoo(t *testing.T) {
t.Log("Foo")
// outputs: {"action": "output", "output": "foo_test.go:12 Foo\n"}
t.Meta("requestID", "123")
// outputs: {"action": "meta", "meta": {"requestID": "123"}}
t.Log("Something Else")
// outputs: {"action": "output", "output": "foo_test.go:16 Foo\n"}
} And it could work by emitting an output like:
Where ":" is a forbidden character in the key, and the value is trimmed for whitespace. I think that this functionality might be "good enough" when parsing the test output JSON; metadata would effectively accumulate for the duration of the test, since the test JSON is effectively scanned top-to-bottom anyway to extract information about a given test. I can write up a CL that we can poke at, if folks don't hate this :) |
Actually just went ahead and made a CL: https://go-review.googlesource.com/c/go/+/357914 |
Change https://golang.org/cl/357914 mentions this issue: |
+1 for this feature. Being able to set arbitrary metadata would be a great way of helping to get our go test results into a test management system without having to rely on a third party testing library. I see the CL is kinda stalled out, I can offer time to help push this forward if anything is needed. |
Now that we have slog, I wonder if this proposal should be about adding some kind of |
cc @aclements @dmitshur as I believe y'all have been looking at structured output with cmd/dist. |
This proposal has been added to the active column of the proposals project |
Good discussion on #59928 to figure out how to hook up slog to testing. If we do that, I think that will take care of the need here. |
Actually, #59928 (comment) convinced me of the opposite. Slog output should be If we do keep these separate, then I suggest |
One thing I would very much like to have (but maybe cannot 😄 ) is that if For example:
could yield
IIUC one of the constraints here which makes this unpleasant is that |
The proposal in #43936 (comment) looks more practical to me than #43936 (comment). Specifically, different behavior in
could be a source of a lot of issues. How would it behave with input like |
Yeah I think you're right, given the constraints that "testing" cannot validate if a string is valid json or not. Small errors would lead to confusion in the output. |
Though I was thinking that, practically, test2json CAN validate, and the str/json division would show prominently when developing a producer/consumer pair. But really it's not a big deal for a consumer to decode the string as json, and pushing it out of test2json also means less overhead in test2json itself, too. I think a bigger error would be if you had some consumer which expected a string (in some other encoding), but it just SO HAPPENS to be valid json, and test2json decodes it... that would definitely be annoying. |
@riannucci How would LUCI make use of the proposed feature (something like t.WithMetadata)? It seems like it helps individual tests get structured output through to something like LUCI. Is that all you are going for? It would not let the overall execution of a test binary be annotated with JSON metadata. |
So the original context of my involvement in this proposal was maybe frivolous, but I think the proposal does generally have merit beyond that. Originally I was interested in this because I was involved with the I was thinking about how to improve this metadata output situation though, and I think the general objective of "I want to be able to communicate data, correlated with individual tests, from within the test to a higher level tool sitting outside of The direct consumer of such data in LUCI would be ResultDB's streaming test result system; it has the ability to associate test artifacts and other metadata directly with test cases, archiving them to e.g. BigQuery. It's possible to emulate this, of course, with specially crafted Log lines... but I would prefer if there was some out-of-band way to communicate (even if under the hood, currently, it's really 'testing' and 'test2json' trying their best to produce/parse stdout). I would rather have the 'communication channels' be something that An alternative to this proposal which I thought of, but don't especially like, which would be to produce a second, independent channel/file/pipe from the test binary which only has metadata. There are a number of downsides to this, though:
(Now that I think of it... https://pkg.go.dev/cmd/go#hdr-Test_packages doesn't mention
I understand this to mean "adding metadata to edit: formatting Footnotes
|
(Oh, I forgot the other bit that goconvey did; for failing assertions it was able to write them out in a structured way, again so that the web UI had better ability to display them; this included things like outputting diffs between actual/expected values) |
I think there are several distinct proposals here, all of which are about getting structured information out of tests in some way, but all of which seem to differ significantly in intent:
I think we need concrete use cases to actually move this discussion forward. |
From my read of the original post the proposal could arguably be for category 3. That data may also be in regular log output, but the goal is for some other program to read the data. The data doesn't need to be associated with any particular log line, just the test case. The proposal happened to include it with a log line, but the benefits section seems to highlight the "read it from another program" more than the association with a log line. The use case I'm familiar with is integration with systems like TestRail. My understanding is that they may have their own identifier for a test case separate from the name. And this "metadata" would be a way to associate test cases with their identifier. As far as I can tell all the use cases described in the original post and in comments are all in category 3. Some of the comments related to log lines were an attempt to propose a solution, but none of the use cases required association or ordering with existing log lines. |
Just chiming in to add another use case to the discussion: We use the Go test framework to run a fairly large set of integration tests across our telephony infrastructure. Hundreds of tests and subtests that take about 30 minutes to run in total. Most of these tests start by initiating one or more calls and then check if our services handle the calls and user actions on those calls correctly. Every once in a while, one of these tests will fail after we've made a change or added a new feature. The cause of the failure can not always be found in the logging of the integration tests. Sometimes, something will have gone wrong somewhere in the SIP path and we have to look at logging in other places of our infrastructure. Instead of having to first also dig through the logging of the integration tests to find the associated Call-ID's to query on and such, it would be nice if the Go test framework had a way of exposing some metadata for each test so that we can nicely present it in our test reports (generated from test2json output). I'm not sure if the Go test framework is intended to be used in this fashion, but figured I'd explain our use case anyway just in case. I believe the proposed |
Given that artifact saving has been split off into #71287, I think we're all happy with I'd like the discussion on #71287 to get a little further. I'm concerned that if we release |
With progress on #71287, I'm wondering about correlation versus collation in this domain. I think there's a notion of well-collated or well-correlated results, such that an accurate understanding of the order of events can be established. Beyond unit tests, more on the integration side, I've certainly made mistakes, or had systems misbehave during cleanup, etc. and having an accurate order of events is an essential of the detective work. I'd like I'd describe standard testing output (vanilla or I get the impression that some proportion of An alternative approach that occurs to me is producing well-collated artifacts, via a tracing(-ish) callback at changes in
A notional example here is that I could maintain an event log of systems-under-test, but well-collated/interleaved with events that |
@AndrewHarrisSPU , I'm not sure I understand your argument. I'll note that I enumerated the expected use cases for test attributes in #43936 (comment). I think a basic property of attributes is that they are clearly correlated with a test, but are not collated with its other output. That's what log messages are for. |
@aclements It sounds like, decisively, I'm stuck on case-2 "logs-as-artifacts" uses, and I think there's a solution that's not interleaving structured data into the test log, but interleaving |
We've got at least two concrete use cases for test attributes:
Both of these are nominally ordered (the representation of properties is a list) but do not appear to treat ordering as significant. Neither assigns any form of time order to properties or associates properties with test output. The properties are an additional set of key/value pairs associated with a test run; nothing more. (I note that both JUinit and Sponge call these "properties" rather than "attributes". Perhaps we should use the "property" terminology as well?) |
We called them "attributes" in slog because the standard library uses "attribute" more than "property." I just did a deep dive on that, and it turns out almost all stdlib uses come from the spec being implemented. All of the following systems use "attribute":
The only system implemented in the stdlib that uses "property" is Unicode. I mention this for background. I don't think it bears too much on what we should pick here. We could go with "use what's more common in the stdlib" or "use what the reference system(s) use." |
This might be a reason why JUnit and Sponge, which output XML, do not call them attributes, because it is not a mechanism to add an XML attribute to an element but to add a new type of XML element. |
(A rabbit hole in a bike shed...) I wonder if |
The ensuing discussion made is clear that people did in fact want case 3, "a way to attach metadata to a test, not in any way ordered with respect to the test log" and not 1 or 2. This is what JUnit and Sponge use test properties for.
It doesn't help that "attribute" and "property" are defined in terms of each other, at least in Google's dictionary. 😆 Given that "attribute" is widely used for this concept in Go std, even if it is only because it's widely used for this concept across various standards, let's stick with "attribute". |
Have all remaining concerns about this proposal been addressed? The proposal is to add the following to package type TB interface {
...
// Attr emits a test attribute associated with this test.
//
// The key must not contain whitespace.
//
// The meaning of different attribute keys is left up to
// continuous integration systems and test frameworks.
//
// Test attributes are emitted immediately in the test log,
// but they are intended to be treated as unordered.
Attr(key, value string)
} Attributes are emitted in the test log as
test2json translates these to
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Sorry, I definitely didn't mean to leave out "Package". I think we should include "Time" as well for consistency with all other test events. |
Based on the discussion above, this proposal seems like a likely accept. The proposal is to add the following to package type TB interface {
...
// Attr emits a test attribute associated with this test.
//
// The key must not contain whitespace.
//
// The meaning of different attribute keys is left up to
// continuous integration systems and test frameworks.
//
// Test attributes are emitted immediately in the test log,
// but they are intended to be treated as unordered.
Attr(key, value string)
} Attributes are emitted in the test log as
test2json translates these to
|
No change in consensus, so accepted. 🎉 The proposal is to add the following to package type TB interface {
...
// Attr emits a test attribute associated with this test.
//
// The key must not contain whitespace.
//
// The meaning of different attribute keys is left up to
// continuous integration systems and test frameworks.
//
// Test attributes are emitted immediately in the test log,
// but they are intended to be treated as unordered.
Attr(key, value string)
} Attributes are emitted in the test log as
test2json translates these to
|
Change https://go.dev/cl/662437 mentions this issue: |
Should the documentation be updated to say that the value cannot contain a CR or LF? type TB interface {
...
// Attr emits a test attribute associated with this test.
//
// The key must not contain whitespace.
//
// The value must not contain a newline (CR or LF).
//
// The meaning of different attribute keys is left up to
// continuous integration systems and test frameworks.
//
// Test attributes are emitted immediately in the test log,
// but they are intended to be treated as unordered.
Attr(key, value string)
} I guess we don't need to mention what happens when the value contains binary data or terminal escape sequences as we don't do that for other methods like Logf. |
CR and LF are whitespace, so I don't think we need to call them out specifically. |
The value can contain whitespace, only the key cannot. Did you mean newline? So it would read as follows: // The value must not contain a newline. I agree that should be sufficient. |
Does the API allow emitting attributes with the same key but different values? e.g.,
As far as I can tell, the intention of this API is that doing this is a mistake (which I agree with). In the wild, I think this mistake will happen somewhat often. e.g., my above "log-file" attribute may come from a test helper that launches a subprocess and adds the path to the process's logs. The helper didn't consider that some tests will launch multiple instances of the subprocess, and thus have multiple log files. Does the API detect this case? Given the lack of mention in the doc, I assume not. I assume that the attribute is simply written to the output for each value, immediately when In that case, I think this is insufficient:
In practice, CI systems will likely select either the first value in the output, or the last. I suspect that once this is widely used Hyrum's law will prevent us from changing the ordering lest we break lots of tests. In my opinion, we should do one of:
|
If we want to enforce uniqueness amongst attribute keys, should they be unique amongst the set of all attribute keys, or unique amongst the set of (test name, attribute key) combinations? Towards the latter, I think |
For reference, JUnit XML test properties allow duplicate keys, and the choice of how to interpret duplicates is generally up to the consumer. |
Test2json, more particularly
go test -json
, has been quite a pleasant discovery. It allows for programs to analyze go tests and create their own formatted output.For example, using GitHub Actions' formatting capabilities, I was able to better format go tests to look more user friendly when running in the UI:
Before:
After:
With that said, there are still some missing features that would allow programs to better understand the JSON output of a test.
Proposal
It would be great if Go Tests can attach metadata to be included in the JSON output of a test2json run.
Something along these lines:
Benefits:
This allows for a few highly beneficial use cases:
test2json
cannot distinguish between when a user calledt.Fatal(...)
ort.Log(...)
which makes sense ast.Fatal
just callst.Log
-- but the user can include metadata so we know exactly where the error occurred and use CI capabilities such as Actions' error command to set the file and line number to be displayed in the UI.Alternative solutions:
Include directives in the output string that the json-parsing program can analyze to see if there's metadata. But this solution is very fragile and prone to error.
Thanks!
The text was updated successfully, but these errors were encountered: