-
Notifications
You must be signed in to change notification settings - Fork 346
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
Add junit support for non-e2e tests/multi-suite #851
Conversation
After discussion on this we decided that the preferred route would be to try and make the There will be a bit more work to accomplish this but we want to try and avoid having N different flavors of junit. Instead, it would be preferable to have logic that will figure them all out and report on them as needed. |
@@ -27,16 +27,15 @@ import ( | |||
|
|||
"github.com/heptio/sonobuoy/pkg/client/results" | |||
"github.com/heptio/sonobuoy/pkg/client/results/e2e" | |||
"github.com/onsi/ginkgo/reporters" |
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.
Now use our own type which is an amalgamation of the ginkgo/reporters and the jstemmer types. Paired with a custom unmarshaller, this allows us to handle both cases with a single type.
@@ -46,7 +45,7 @@ func (*SonobuoyClient) GetTests(reader io.Reader, show string) ([]reporters.JUni | |||
} | |||
// TODO(chuckha) consider reusing this function for any generic e2e-esque plugin results. | |||
// TODO(chuckha) consider using path.Join() | |||
if path == e2eJunitPath || path == legacye2eJunitPath { | |||
if path == e2eJUnitPath || path == legacye2eJUnitPath { |
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.
Updated a bunch of little lines for consistent naming: JUnit
preferred over Junit
if show == "passed" || show == "all" { | ||
out = append(out, results.Filter(results.Passed, junitResults)...) | ||
out = append(out, results.JUnitFilter(results.JUnitPassed, junitResults)...) |
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.
Although not strictly requiring this prefix now that there isnt a junit
and a e2e
filter/passed/etc, it makes sense to me to leave a prefix since this is type specific.
As we add other formats we will probably need similar named things. At that point we may also want to consider some interface to do this instead though.
// testsuite whereas other tools report a top-level testsuites object | ||
// which may have 1+ testsuite children. Only one of the fields should be | ||
// set, not both. | ||
type junitResult struct { |
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 type is "where the magic happens" since it has a custom unmarshal method which will handle either case based on the top level object.
suites JUnitTestSuites | ||
} | ||
|
||
// UnmarshalXML will unmarshal the document into either the 'Suites' |
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.
Needs updated; in another implementation I had Suites
and Suite
and only one would get set. Instead, now there is just suites and we will just add the lone suite to the list if that is what we are given.
// which may have 1+ testsuite children. Only one of the fields should be | ||
// set, not both. | ||
type junitResult struct { | ||
suites JUnitTestSuites |
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 slightly awkward naming but I dont have a better way to name this really. It just leads to junitResults.suites.Suites...
. I hate the repetition but I couldn't come up with something that would be clear/intutive/didnt repeat.
} | ||
for i := range j.suites.Suites { | ||
if j.suites.Suites[i].Name == "" { | ||
j.suites.Suites[i].Name = fmt.Sprintf("testsuite-%03d", i+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.
When processing junit results, each suite is on its own branch. As a result, if you dont have a suite name you get a weird branch that you can't target very well.
In particular, this happens for the e2e tests so I wanted to ensure that non-empty names were set.
SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"` | ||
Failure *JUnitFailureMessage `xml:"failure,omitempty"` | ||
SystemOut string `xml:"system-out,omitempty"` | ||
SystemErr string `xml:"system-err,omitempty"` |
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.
New field; not really used right now but could be. Didnt add this into the metadata but could.
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.
Update: Went ahead and added the processing to support this now too. Just seems too intuitive a field to not include
resultObj.Status = StatusUnknown | ||
resultObj.Metadata["error"] = err.Error() | ||
return resultObj, errors.Wrap(err, "error decoding json into object") | ||
resultObj, err = junitProcessReader( |
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.
Removed all the file handling and put the rest of the logic into this new method.
{"name":"job-junit-02","status":"failed","items":[{"name":"output.xml","status":"passed","meta":{"file":"results/global/output.xml"},"items":[{"name":"testsuite-001","status":"passed","items":[{"name":"[k8s.io] Pods should be submitted and removed [NodeConformance] [Conformance]","status":"passed"},{"name":"[sig-node] ConfigMap should fail to create ConfigMap with empty key [Conformance]","status":"passed"},{"name":"[sig-storage] Downward API volume should set DefaultMode on files [LinuxOnly] [NodeConformance] [Conformance]","status":"passed"},{"name":"[sig-storage] In-tree Volumes [Driver: local][LocalVolumeType: dir-link-bindmounted] [Testpattern: Dynamic PV (default fs)] subPath should support existing directories when readOnly specified in the volumeSource","status":"skipped"},{"name":"[sig-storage] In-tree Volumes [Driver: rbd][Feature:Volumes] [Testpattern: Pre-provisioned PV (default fs)] subPath should support restarting containers using file as subpath [Slow]","status":"skipped"}]}]},{"name":"output2.xml","status":"failed","meta":{"file":"results/global/output2.xml"},"items":[{"name":"testsuite-001","status":"failed","items":[{"name":"[k8s.io] Pods should be submitted and removed [NodeConformance] [Conformance]","status":"passed"},{"name":"[sig-apps] Daemon set [Serial] should rollback without unnecessary restarts [Conformance]","status":"failed"},{"name":"[sig-storage] In-tree Volumes [Driver: local][LocalVolumeType: dir-link-bindmounted] [Testpattern: Dynamic PV (default fs)] subPath should support existing directories when readOnly specified in the volumeSource","status":"skipped"},{"name":"[sig-storage] In-tree Volumes [Driver: rbd][Feature:Volumes] [Testpattern: Pre-provisioned PV (default fs)] subPath should support restarting containers using file as subpath [Slow]","status":"skipped"}]}]}]} |
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 main thing that changes in all of these is that the entire 'testsuite' is put into a new level. This way when you have N test suites, each is on their own branch and can be filtered/walked appropriately.
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.
There are a number of these golden files where it looks like the failure messages aren't being recorded. They seem to get captured correctly in junit_good_junit.xml
file though.
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 catch. When developing I do tend to be a little less than critical with my review of those goldenfiles.
Probably worth it to do something like change (for tests) to MarshalIndent (with 0 indent so changes in depth don't cascade to every sub item) so that the diffs are more clean.
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 that would help. It can be really easy to miss changes otherwise. I didn't notice the issue on my first look at this change :)
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 will fail CI now because nothing uses the ginkgo reporters and they can be removed; will leave that commit out for now just for clarity. That diff will be |
Codecov Report
@@ Coverage Diff @@
## master #851 +/- ##
==========================================
+ Coverage 46.54% 47.01% +0.46%
==========================================
Files 75 75
Lines 4830 4867 +37
==========================================
+ Hits 2248 2288 +40
+ Misses 2453 2449 -4
- Partials 129 130 +1
Continue to review full report at Codecov.
|
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 looking good so far, thanks @johnSchnake. The only issue I've found is that there are some more significant changes to the existing golden file tests, the failure messages aren't being included in the output.
}{ | ||
{ | ||
desc: "top-level testsuites", | ||
input: `<testsuites><testsuite name="testsuite1"></testsuite><testsuite name="testsuite2"></testsuite></testsuites>`, |
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 adding these tests. I know some of this functionality is covered in the other tests but I like having this where the data isn't in a separate file and the behaviour is clear 😄
{"name":"job-junit-02","status":"failed","items":[{"name":"output.xml","status":"passed","meta":{"file":"results/global/output.xml"},"items":[{"name":"testsuite-001","status":"passed","items":[{"name":"[k8s.io] Pods should be submitted and removed [NodeConformance] [Conformance]","status":"passed"},{"name":"[sig-node] ConfigMap should fail to create ConfigMap with empty key [Conformance]","status":"passed"},{"name":"[sig-storage] Downward API volume should set DefaultMode on files [LinuxOnly] [NodeConformance] [Conformance]","status":"passed"},{"name":"[sig-storage] In-tree Volumes [Driver: local][LocalVolumeType: dir-link-bindmounted] [Testpattern: Dynamic PV (default fs)] subPath should support existing directories when readOnly specified in the volumeSource","status":"skipped"},{"name":"[sig-storage] In-tree Volumes [Driver: rbd][Feature:Volumes] [Testpattern: Pre-provisioned PV (default fs)] subPath should support restarting containers using file as subpath [Slow]","status":"skipped"}]}]},{"name":"output2.xml","status":"failed","meta":{"file":"results/global/output2.xml"},"items":[{"name":"testsuite-001","status":"failed","items":[{"name":"[k8s.io] Pods should be submitted and removed [NodeConformance] [Conformance]","status":"passed"},{"name":"[sig-apps] Daemon set [Serial] should rollback without unnecessary restarts [Conformance]","status":"failed"},{"name":"[sig-storage] In-tree Volumes [Driver: local][LocalVolumeType: dir-link-bindmounted] [Testpattern: Dynamic PV (default fs)] subPath should support existing directories when readOnly specified in the volumeSource","status":"skipped"},{"name":"[sig-storage] In-tree Volumes [Driver: rbd][Feature:Volumes] [Testpattern: Pre-provisioned PV (default fs)] subPath should support restarting containers using file as subpath [Slow]","status":"skipped"}]}]}]} |
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.
There are a number of these golden files where it looks like the failure messages aren't being recorded. They seem to get captured correctly in junit_good_junit.xml
file though.
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 making the changes!
After merging the easy-json-diff PR I can see I still have a small problem here. Going to fix up. |
"name": "[k8s.io] Pods should be submitted and removed [NodeConformance] [Conformance]", | ||
"status": "passed" | ||
}, | ||
{ | ||
"name": "[sig-apps] Daemon set [Serial] should rollback without unnecessary restarts [Conformance]", | ||
"status": "failed", | ||
"details": { | ||
"failure": "/go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/test/e2e/framework/framework.go:696\nConformance test suite needs a cluster with at least 2 nodes.\nExpected\n \u003cint\u003e: 1\nto be \u003e\n \u003cint\u003e: 1\n/go/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes/test/e2e/apps/daemon_set.go:385", |
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 should still be here; not sure why it got removed. I see a few other tests seem to show the failure stuck around... Should be an easy fix when I find 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.
The problem seems to be that I was looking for the failure.Message
but that is 0, this is the chardata
from the xml which (in our datatype) is called Contents
.
The fix is fast (use the new field) but raises the question about what we should do with this new field. Should we just concat the message/contents or should we save them separately? Going to take a peek at some of the other junit (non-e2e) and see what makes sense there to 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.
:) So it seems they just use the opposite on the other output I've tested against (chef/inspec with junit output). They only use the message for the failure message and https://github.com/inspec/inspec/blob/a35d37043e65ce5fd528d58cd48ecb369c74a432/lib/inspec/reporters/junit.rb#L48-L54 seems to show they never even use the contents.
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.
Settled on joining message/contents together (space separated) so the data is all present. Users could still grep the data and have one main location to look still. I thought this was better than adding a new Details
entry for every type of attribute/field. Open to discussion though.
The e2e tests report via junit but with the top-level item in the xml being a `<testsuite>`. Other implementations seem to default to allowing multiple testsuites and so they have a higher level `testsuites` object. This PR enables a new post-processing path and makes the older junit parsing only apply to the result-type `e2e`. The new, `junit` result-type will use this more generalized logic. Fixes #849 Signed-off-by: John Schnake <[email protected]>
// the values, separated with a space. | ||
hasFailureContents := (t.Failure != nil && (t.Failure.Message != "" || t.Failure.Contents != "")) | ||
if hasFailureContents { | ||
testItem.Details[JUnitFailureKey] = strings.TrimSpace(t.Failure.Message + " " + t.Failure.Contents) |
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 (and the lines above it) where the most recent change. Just checks that either value exists and joins them (trimming spaces at the end).
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 this is fine. You could also build the string up gradually by checking whether either Message
or Contents
was empty and appending if they aren't but I don't feel particularly strongly about it. I'm happy for you to leave it as is.
@zubron , rerequesting review since I found issues/resolved them again. Open to discussion if you dont agree with my approach. |
// the values, separated with a space. | ||
hasFailureContents := (t.Failure != nil && (t.Failure.Message != "" || t.Failure.Contents != "")) | ||
if hasFailureContents { | ||
testItem.Details[JUnitFailureKey] = strings.TrimSpace(t.Failure.Message + " " + t.Failure.Contents) |
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 this is fine. You could also build the string up gradually by checking whether either Message
or Contents
was empty and appending if they aren't but I don't feel particularly strongly about it. I'm happy for you to leave it as is.
What this PR does / why we need it:
The e2e tests report via junit but with the top-level item
in the xml being a
<testsuite>
. Other implementations seemto default to allowing multiple testsuites and so they have a
higher level
testsuites
object.This PR enables a new post-processing path and makes the older
junit parsing only apply to the result-type
e2e
. The new,junit
result-type will use this more generalized logic.Which issue(s) this PR fixes
Fixes #849
Special notes for your reviewer:
TBD; need to discuss this because the above commit message isn't accurate, nor is the issue I originally filed. I thought we were setting the result format for
e2e
but we were not; we were usingjunit
. So this change makes oldersonobuoy gen
output no longer get post-processed correctly.That is a brand new feature so maybe not a huge deal, but we should consider not changing that.
Instead of
junit
being the 'new' format, maybe we continue to letjunit
be the e2e-junit/single-suite and we introduce a newjunit-suites
or something similar for the new format.Release note: