summaryrefslogtreecommitdiffstats
path: root/src/goqtestlib/result.go
blob: e4c88001ba7abca3ad9d188e013f29fa7d908bcc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
// 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
}