...
1
16
17 package ktesting
18
19 import (
20 "context"
21 "errors"
22 "io"
23 "os"
24 "os/signal"
25 "strings"
26 "sync"
27 )
28
29 var (
30 interruptCtx context.Context
31
32 defaultProgressReporter = new(progressReporter)
33 defaultSignalChannel chan os.Signal
34 )
35
36 const ginkgoSpecContextKey = "GINKGO_SPEC_CONTEXT"
37
38 type ginkgoReporter interface {
39 AttachProgressReporter(reporter func() string) func()
40 }
41
42 func init() {
43
44
45
46 signalCtx, _ := signal.NotifyContext(context.Background(), os.Interrupt)
47 cancelCtx, cancel := context.WithCancelCause(context.Background())
48 go func() {
49 <-signalCtx.Done()
50 cancel(errors.New("received interrupt signal"))
51 }()
52
53
54
55
56
57
58
59 interruptCtx = context.WithValue(cancelCtx, ginkgoSpecContextKey, defaultProgressReporter)
60
61 defaultSignalChannel = make(chan os.Signal, 1)
62
63 if len(progressSignals) > 0 {
64 signal.Notify(defaultSignalChannel, progressSignals...)
65 }
66
67
68
69 defaultProgressReporter.setOutput(os.Stderr)
70 go defaultProgressReporter.run(interruptCtx, defaultSignalChannel)
71 }
72
73 type progressReporter struct {
74 mutex sync.Mutex
75 reporterCounter int64
76 reporters map[int64]func() string
77 out io.Writer
78 }
79
80 var _ ginkgoReporter = &progressReporter{}
81
82 func (p *progressReporter) setOutput(out io.Writer) io.Writer {
83 p.mutex.Lock()
84 defer p.mutex.Unlock()
85 oldOut := p.out
86 p.out = out
87 return oldOut
88 }
89
90
91 func (p *progressReporter) AttachProgressReporter(reporter func() string) func() {
92 p.mutex.Lock()
93 defer p.mutex.Unlock()
94
95
96 p.reporterCounter++
97 id := p.reporterCounter
98 if p.reporters == nil {
99 p.reporters = make(map[int64]func() string)
100 }
101 p.reporters[id] = reporter
102 return func() {
103 p.detachProgressReporter(id)
104 }
105 }
106
107 func (p *progressReporter) detachProgressReporter(id int64) {
108 p.mutex.Lock()
109 defer p.mutex.Unlock()
110
111 delete(p.reporters, id)
112 }
113
114 func (p *progressReporter) run(ctx context.Context, progressSignalChannel chan os.Signal) {
115 for {
116 select {
117 case <-ctx.Done():
118 return
119 case <-progressSignalChannel:
120 p.dumpProgress()
121 }
122 }
123 }
124
125
126
127
128
129
130
131 func (p *progressReporter) dumpProgress() {
132 p.mutex.Lock()
133 defer p.mutex.Unlock()
134
135 var buffer strings.Builder
136 buffer.WriteString("You requested a progress report.\n")
137 if len(p.reporters) == 0 {
138 buffer.WriteString("Currently there is no information about test progress available.\n")
139 }
140 for _, reporter := range p.reporters {
141 report := reporter()
142 buffer.WriteRune('\n')
143 buffer.WriteString(report)
144 if !strings.HasSuffix(report, "\n") {
145 buffer.WriteRune('\n')
146 }
147 }
148
149 _, _ = p.out.Write([]byte(buffer.String()))
150 }
151
View as plain text