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

TestSuite Timestamps Report All Constant #147

Closed
cameron-dunn-sublime opened this issue Oct 21, 2022 · 6 comments
Closed

TestSuite Timestamps Report All Constant #147

cameron-dunn-sublime opened this issue Oct 21, 2022 · 6 comments

Comments

@cameron-dunn-sublime
Copy link
Contributor

I recently began using go-junit-report and noticed that all the testsuite timestamp tags were the same for a given report, even though my tests are taking several minutes to run.

Some tools, such as DataDog CI Test Integration use these timestamps to calculate the total time for all tests.

I took a closer look at the code and can see that there's already functionality in place to use time.Now for each testsuite, e.g. here and here. However, CreatePackage is called within a loop that runs after all lines are read. This means that the timestamp represents the current time while generating the report, not the time the test result became available.

I've played around and was able to pretty quickly get a working solution. I was wondering if you'd consider accepting a pull request for this change? It passes existing tests at least, and seems to work as desired when run against my tests. If you are interested in this change, I can do some additional validation and look into expanding tests.

diff --git a/parser/gotest/gotest.go b/parser/gotest/gotest.go
index 2f38282..6d4a09a 100644
--- a/parser/gotest/gotest.go
+++ b/parser/gotest/gotest.go
@@ -8,6 +8,7 @@ import (
        "regexp"
        "strconv"
        "strings"
+       "sync"
        "time"

        "github.com/jstemmer/go-junit-report/v2/gtr"
@@ -136,6 +137,17 @@ func (p *Parser) Parse(r io.Reader) (gtr.Report, error) {

 func (p *Parser) parse(r reader.LineReader) (gtr.Report, error) {
        p.events = nil
+
+       var (
+               finalReport  gtr.Report
+               events       = make(chan Event)
+               waitOnReport sync.WaitGroup
+       )
+       waitOnReport.Add(1)
+       go func() {
+               finalReport = p.report(events)
+               waitOnReport.Add(-1)
+       }()
        for {
                line, metadata, err := r.ReadLine()
                if err == io.EOF {
@@ -162,21 +174,24 @@ func (p *Parser) parse(r reader.LineReader) (gtr.Report, error) {

                for _, ev := range evs {
                        ev.applyMetadata(metadata)
-                       p.events = append(p.events, ev)
+                       events <- ev
                }
        }
-       return p.report(p.events), nil
+       close(events)
+       waitOnReport.Wait()
+
+       return finalReport, nil
 }

 // report generates a gtr.Report from the given list of events.
-func (p *Parser) report(events []Event) gtr.Report {
+func (p *Parser) report(events <-chan Event) gtr.Report {
        rb := newReportBuilder()
        rb.packageName = p.packageName
        rb.subtestMode = p.subtestMode
        if p.timestampFunc != nil {
                rb.timestampFunc = p.timestampFunc
        }
-       for _, ev := range events {
+       for ev := range events {
                rb.ProcessEvent(ev)
        }
        return rb.Build()

By using a channel we can call ProcessEvent at about the same time the test is finished. An alternative approach would be to evaluate a timestamp function in *Parser.summary and add a time.Time to the Event.

Thanks for making and maintaining a great tool!

@jstemmer
Copy link
Owner

Thanks for the report.

Looks like the main problem is, as you pointed out, that the report is constructed only after everything has been parsed. Your suggested solution appears to work, but I think there's an alternative way to fix this that doesn't involve the added complexity of running report concurrently to parse and communicating via a channel. Instead, we can merge the report code into the parse function. I.e. move the report builder initialization before the for loop in parse and call rb.ProcessEvent at the same place where the events are added to the p.events slice.

Another potential problem I found is that the package timestamp in the report builder only gets set when we encounter a summary line (in CreatePackage), which is pretty much when the test has already completed. I'm not sure whether timestamp field was intended to contain the starting or ending timestamp of the testsuite, but I suspect it's the former. To fix this we'd have to modify the packageBuilder to store the creation timestamp in newPackageBuilder (which typically happens when we process the first event of a new package) and use that rather than the current timestamp in CreatePackage.

If you're interested in fixing these, feel free to send some PR's. Otherwise I'm happy to make the change once I have some time.

@cameron-dunn-sublime
Copy link
Contributor Author

Thanks! I'll take a look at implementing it as you describe, that makes sense as well.

I'm not sure whether timestamp field was intended to contain the starting or ending timestamp of the testsuite, but I suspect it's the former. To fix this we'd have to modify the packageBuilder to store the creation timestamp in newPackageBuilder (which typically happens when we process the first event of a new package) and use that rather than the current timestamp in CreatePackage.

I suspect it's meant to be the start timestamp as well. This wasn't impacting for me, but it probably makes sense to address at the same time. I was thinking we could also use the timestamp function minus the duration. I think if it was based on the first event of a package the reported timestamps per test could be well before the test actually started. I might be misunderstanding the fields though!

cameron-dunn-sublime added a commit to cameron-dunn-sublime/go-junit-report that referenced this issue Mar 15, 2023
jstemmer#147

By moving the ProcessEvent call up to parse we call it as soon
as each event is read. This ensures that the timestampFunc is
called approximately when the test is run. This ensures that
the time of the whole test suite can be measured.
@cameron-dunn-sublime
Copy link
Contributor Author

Hey @jstemmer, sorry for the extreme delay/lack of comms. Anyways I finally took the time to take a closer look at implement the suggested solution. PR up and it works great!

@cameron-dunn-sublime
Copy link
Contributor Author

@jstemmer, sorry for the extra ping -- I just wanted to see if you may have an opportunity to review and merge the change.

@jstemmer
Copy link
Owner

No problem. I've been busy lately, so didn't get a chance to take a look until just now. Thanks for taking the time to fix this!

@cameron-dunn-sublime
Copy link
Contributor Author

Thanks, much appreciated and totally understand being busy -- took me 5 months to actually make the change 🤷‍♂️

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

No branches or pull requests

2 participants