Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ package goqtestlib
++
++ import (
++ "archive/tar"
++ "compress/gzip"
++ "encoding/xml"
++ "fmt"
++ "io"
++ "io/ioutil"
++ "log"
++ "os"
++ )
++
++ // Environment provides information about the environment used to
++ // run the unit tests, such as the Qt version used.
++ type Environment struct {
++ QtVersion string
++ QtBuild string
++ QTestVersion string
++ }
++
++ // IncidentHeader provides the fields that are common for Incident
++ // and Message, basically the file name and line numbers of
++ // where the message was produced or the incident happened.
++ type IncidentHeader struct {
++ Type string `xml:"type,attr"`
++ File string `xml:"file,attr"`
++ Line int `xml:"line,attr"`
++ }
++
++ // Duration measures the time it took to run the test.
++ type Duration struct {
++ Msecs float64 `xml:"msecs,attr"`
++ }
++
++ // Incident usually refers to a failing or a passing test function.
++ type Incident struct {
++ IncidentHeader
++ DataTag string `xml:",omitempty"`
++ }
++
++ // Message represents an arbitrary message produced by QTestLib, usually qDebug() output.
++ type Message struct {
++ IncidentHeader
++ Description string
++ }
++
++ // BenchmarkResult represents the results produced when running benchmarks with QTestLib.
++ type BenchmarkResult struct {
++ Metric string `xml:"metric,attr"`
++ Tag string `xml:"tag,attr"`
++ Value float64 `xml:"value,attr"`
++ Iterations int `xml:"iterations,attr"`
++ }
++
++ // TestFunction represents the results of running a single test function.
++ type TestFunction struct {
++ Name string `xml:"name,attr"`
++ Incidents []Incident `xml:"Incident"`
++ Messages []Message `xml:"Message"`
++ BenchmarkResults []BenchmarkResult `xml:"BenchmarkResult"`
++ Duration Duration `xml:"Duration"`
++ }
++
++ // FailingIncidents returns a list of incidents that represent tests failures
++ // while the test function was running. For example in table driven tests
++ // that is one entry per failed test row.
++ func (t *TestFunction) FailingIncidents() []string {
++ var failures []string
++ for _, incident := range t.Incidents {
++ if incident.Type == "fail" {
++ name := t.Name
++ /* No support for tags at the moment due to quoting issues on Windows
++ if incident.DataTag != "" {
++ name += ":"
++ name += incident.DataTag
++ }
++ */
++ failures = append(failures, name)
++ break
++ }
++ }
++ return failures
++ }
++
++ // ParsedTestResult represents the parsed output of running tests with QTestLib.
++ type ParsedTestResult struct {
++ XMLName xml.Name `xml:"TestCase"`
++ Name string `xml:"name,attr"`
++ Env Environment `xml:"Environment"`
++ Functions []TestFunction `xml:"TestFunction"`
++ Duration Duration `xml:"Duration"`
++ }
++
++ // FailingFunctions returns list of test functions that failed during an earlier run.
++ func (p *ParsedTestResult) FailingFunctions() []string {
++ var failing []string
++ for _, f := range p.Functions {
++ failing = append(failing, f.FailingIncidents()...)
++ }
++ return failing
++ }
++
++ // TestResult provides access to the results of executing a single unit test, such as tst_qiodevice in Qt.
++ type TestResult struct {
++ TestCaseName string // local to the module, for example tests/auto/corelib/io/qiodevice
++ PathToResultsXML string // path where the testlib xml output is stored
++ }
++
++ // Parse attempts to read the XML file the PathToResultsXML field points to.
++ func (r *TestResult) Parse() (*ParsedTestResult, error) {
++ xmlContents, err := ioutil.ReadFile(r.PathToResultsXML)
++ if err != nil {
++ return nil, fmt.Errorf("Error reading results xml file %s: %s", r.PathToResultsXML, err)
++ }
++
++ testCase := &ParsedTestResult{}
++ if err = xml.Unmarshal(xmlContents, &testCase); err != nil {
++ return nil, fmt.Errorf("Error unmarshalling testlib xml output: %s", err)
++ }
++
++ return testCase, err
++ }
++
++ // TestResultCollection is a collection of test results after running tests on a module of source code.
++ type TestResultCollection []TestResult
++
++ // Archive produces a .tar.gz archive of the collection and writes it into the given destination writer.
++ func (collection *TestResultCollection) Archive(destination io.Writer) (err error) {
++ compressor := gzip.NewWriter(destination)
++
++ archiver := tar.NewWriter(compressor)
++
++ log.Printf("Collecting %v test results ...\n", len(*collection))
++
++ for _, result := range *collection {
++ stat, err := os.Stat(result.PathToResultsXML)
++ if err != nil {
++ return fmt.Errorf("Error call stat() on %s: %s", result.PathToResultsXML, err)
++ }
++ header, err := tar.FileInfoHeader(stat, "")
++ if err != nil {
++ return fmt.Errorf("Error creating tar file header for %s: %s", result.PathToResultsXML, err)
++ }
++ header.Name = result.TestCaseName + ".xml"
++
++ if err := archiver.WriteHeader(header); err != nil {
++ return fmt.Errorf("Error writing tar file header for %s: %s", result.PathToResultsXML, err)
++ }
++
++ file, err := os.Open(result.PathToResultsXML)
++ if err != nil {
++ return fmt.Errorf("Error opening results file %s: %s", result.PathToResultsXML, err)
++ }
++
++ if _, err := io.Copy(archiver, file); err != nil {
++ file.Close()
++ return fmt.Errorf("Error writing results file %s into archive: %s", result.PathToResultsXML, err)
++ }
++
++ file.Close()
++ }
++
++ if err := archiver.Close(); err != nil {
++ return err
++ }
++
++ if err := compressor.Close(); err != nil {
++ return err
++ }
++
++ return nil
++ }