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

Add junit support for non-e2e tests/multi-suite #851

Merged
merged 1 commit into from
Aug 23, 2019
Merged

Add junit support for non-e2e tests/multi-suite #851

merged 1 commit into from
Aug 23, 2019

Conversation

johnSchnake
Copy link
Contributor

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

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 using junit. So this change makes older sonobuoy 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 let junit be the e2e-junit/single-suite and we introduce a new junit-suites or something similar for the new format.

Release note:

JUnit output which containts a `testsuites` object consisting of multiple test suites can now be post-processed by setting the result-format to `TBD`

@johnSchnake johnSchnake requested a review from zubron August 21, 2019 19:27
@johnSchnake
Copy link
Contributor Author

After discussion on this we decided that the preferred route would be to try and make the junit result-format be compatible with both.

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"
Copy link
Contributor Author

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 {
Copy link
Contributor Author

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)...)
Copy link
Contributor Author

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.

pkg/client/gen.go Outdated Show resolved Hide resolved
// 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 {
Copy link
Contributor Author

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'
Copy link
Contributor Author

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
Copy link
Contributor Author

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)
Copy link
Contributor Author

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"`
Copy link
Contributor Author

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.

Copy link
Contributor Author

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(
Copy link
Contributor Author

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"}]}]}]}
Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@johnSchnake
Copy link
Contributor Author

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 16 files changed, 1664 deletions(-)

@codecov-io
Copy link

codecov-io commented Aug 22, 2019

Codecov Report

Merging #851 into master will increase coverage by 0.46%.
The diff coverage is 90.21%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
pkg/client/results/raw.go 53.84% <100%> (ø) ⬆️
pkg/client/results/processing.go 81.81% <100%> (ø) ⬆️
pkg/client/results/junit.go 80.21% <89.33%> (+16.58%) ⬆️
pkg/client/e2e.go 89.74% <91.66%> (ø) ⬆️
pkg/plugin/aggregation/aggregator.go 79.2% <0%> (-4%) ⬇️
pkg/plugin/driver/job/job.go 74.19% <0%> (+0.2%) ⬆️
pkg/plugin/driver/daemonset/daemonset.go 66.25% <0%> (+0.21%) ⬆️
cmd/sonobuoy/app/worker.go 10.63% <0%> (+0.96%) ⬆️
cmd/sonobuoy/app/status.go 59.43% <0%> (+1.88%) ⬆️
cmd/sonobuoy/app/master.go 50% <0%> (+10%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 451b313...9ba8ede. Read the comment docs.

Copy link
Contributor

@zubron zubron 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 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>`,
Copy link
Contributor

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"}]}]}]}
Copy link
Contributor

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.

Copy link
Contributor

@zubron zubron left a 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!

@johnSchnake
Copy link
Contributor Author

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",
Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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)
Copy link
Contributor Author

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

Copy link
Contributor

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.

@johnSchnake
Copy link
Contributor Author

@zubron , rerequesting review since I found issues/resolved them again. Open to discussion if you dont agree with my approach.

@johnSchnake johnSchnake requested a review from zubron August 23, 2019 15:07
// 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)
Copy link
Contributor

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.

@johnSchnake johnSchnake merged commit a268401 into vmware-tanzu:master Aug 23, 2019
@johnSchnake johnSchnake deleted the multiJunitSuites branch August 23, 2019 15:32
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.

Valid junit fails to be parsed correctly
3 participants