This repository was archived by the owner on Jun 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathcontract.go
109 lines (100 loc) · 3.48 KB
/
contract.go
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
package gandalf
import (
"testing"
)
// Contract is at the core of Gandalf, it represents the contract between a
// consumer and provider in two main parts, the Request, and the Check.
// The Request object is responsible for geting information into Gandalf
// from the provider for testing. Then the response is given to the Check
// object to that the response meets whatever criteria the Checker supports.
type Contract struct {
// Unique identifier for this contract.
Name string
Check Checker
Request Requester
Export Exporter
// Run stores the number of times that Assert has been run and executed all parts of the contract.
// This allows for some information such as the request to differ per call if desired.
Run int
// If an Optional Contract fails its checks it will not fail the whole test run.
Optional bool
// Set to true after this contract is tested the first time, pass or fail.
Tested bool // internal state to mark if a contract has already been tested
notHonored bool // internal state to mark when a test for this contract failed
}
// Checks the error and skips or fails the Testable based on the Optional switch.
func (c *Contract) honorCheck(t Testable, err error) {
t.Helper()
if c.notHonored = err != nil; c.notHonored {
if c.Optional {
t.Skipf("Contract %#v was not honored, but does not have to be, due to error:\n%s", c.Name, err)
} else {
t.Fatalf("Contract %#v was not honored, and it must be, due to error:\n%s", c.Name, err)
}
}
}
// Testable is the common interface between tests and benchmarks required to handle them interchangeably.
type Testable interface {
Helper()
Fatalf(format string, args ...interface{})
Skipf(format string, args ...interface{})
}
func (c *Contract) export(t Testable) {
if c.Export != nil {
if e := c.Export.Save(c); e != nil {
t.Fatalf("Export save failed due to error: %s", e)
}
}
}
// Assert runs a request and checks response on the contract causing a pass or fail.
// This executes the Exporter before and after calling the Requester, allowing
// for pre and post exporters.
func (c *Contract) Assert(t Testable) {
t.Helper()
defer func() { c.Run++ }()
defer c.export(t)
c.export(t)
resp, err := c.Request.Call(c.Run)
c.honorCheck(t, err)
c.honorCheck(t, c.Check.Assert(resp))
c.Tested = true
}
// Benchmark just the Requester's ability to provider responses in sequence.
// This uses the benchmark run counter instead of the Contract.Run field.
func (c *Contract) Benchmark(b *testing.B) {
b.Helper()
if c.Tested && c.notHonored {
b.Skipf("Contract %s benchmark skipped as the contract was not honored by passing its test\n", c.Name)
}
errors := 0
for n := 0; n < b.N; n++ {
_, err := c.Request.Call(n)
if err != nil {
errors++
}
}
if errors > 0 {
b.Logf("%d calls out of %d (%.2f%%) resulted in an error",
errors, b.N, float32(errors)*float32(100)/float32(b.N))
}
}
// BenchmarkInOrder takes a list of contracts and benchmarks the time it takes
// to call each of their requests in sequence before starting the next run.
// This may be useful if a list of contracts defined, for example, a common
// customer journey to be benchmarked.
func BenchmarkInOrder(b *testing.B, contracts []*Contract) {
b.Helper()
errors := 0
for n := 0; n < b.N; n++ {
for _, c := range contracts {
_, err := c.Request.Call(n)
if err != nil {
errors++
}
}
}
if errors > 0 {
b.Logf("%d calls out of %d (%.2f%%) resulted in an error",
errors, b.N, float32(errors)*float32(100)/float32(b.N))
}
}