Skip to content

Commit 9640cd7

Browse files
committed
Fix stopping of notification query on error
+ minor test fixes just btw
1 parent 90b6ebb commit 9640cd7

File tree

3 files changed

+75
-27
lines changed

3 files changed

+75
-27
lines changed

decoder_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,8 @@ func TestDecoder_Unmarshal(t *testing.T) {
4949
// Time pointer:
5050
if system.CreationDate == nil {
5151
t.Errorf("Failed to fetch Win32_Process.CreationDate")
52-
} else {
53-
if !(system.CreationDate.After(someOldDate) && system.CreationDate.Before(testStart)) {
54-
t.Errorf("Unexoected System process creation date; %s", system.CreationDate)
55-
}
52+
} else if !(system.CreationDate.After(someOldDate) && system.CreationDate.Before(testStart)) {
53+
t.Errorf("Unexoected System process creation date; %s", system.CreationDate)
5654
}
5755
// uint64:
5856
if system.KernelModeTime == 0 {
@@ -242,7 +240,7 @@ func TestDecoder_Unmarshal_Slices(t *testing.T) {
242240
}
243241
if len(bios.BIOSVersion) < 1 {
244242
t.Errorf("Empty BIOS versions list")
245-
} else if len(bios.BIOSVersion[0]) < 0 {
243+
} else if len(bios.BIOSVersion[0]) < 1 {
246244
t.Errorf("Empty string in BIOS version liss; %v", bios.Version)
247245
}
248246
if len(bios.BiosCharacteristics) < 1 {
@@ -319,7 +317,9 @@ func TestDecoder_Unmarshal_References(t *testing.T) {
319317
// Extract user profile of the current user.
320318
var users []loggedUser
321319
if err := Query(`SELECT * FROM Win32_LoggedOnUser`, &users); err != nil {
322-
t.Fatalf("Failed to query Win32_LoggedOnUser; %v", err)
320+
// Sometimes we can't fetch full info about all users but for the current test it's
321+
// pretty ok.
322+
t.Logf("Got errors querying Win32_LoggedOnUser; %v", err)
323323
}
324324
if len(users) < 1 {
325325
t.Fatalf("No logged users found")

notification_query.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ func NewNotificationQuery(eventCh interface{}, query string) (*NotificationQuery
5353
}
5454
q := NotificationQuery{
5555
state: stateNotStarted,
56-
doneCh: make(chan struct{}),
5756
eventCh: eventCh,
5857
query: query,
5958
}
@@ -106,9 +105,16 @@ func (q *NotificationQuery) StartNotifications() (err error) {
106105
q.Unlock()
107106
return nil
108107
}
108+
q.doneCh = make(chan struct{})
109109
q.state = stateStarted
110110
q.Unlock()
111111

112+
// Mark as stopped on any return.
113+
defer func() {
114+
q.state = stateStopped
115+
close(q.doneCh)
116+
}()
117+
112118
// Be aware of reflections and COM usage.
113119
defer func() {
114120
if r := recover(); r != nil {
@@ -150,6 +156,7 @@ func (q *NotificationQuery) StartNotifications() (err error) {
150156
reflectedDoneChan := reflect.ValueOf(q.doneCh)
151157
reflectedResChan := reflect.ValueOf(q.eventCh)
152158
eventType := reflectedResChan.Type().Elem()
159+
153160
for {
154161
// If it is a time to stop somebody will listen on doneCh.
155162
select {

notification_query_test.go

+61-20
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ func TestNewNotificationQuery(t *testing.T) {
2121
{&T{}, true},
2222
{make([]T, 0), true},
2323
{make([]*T, 0), true},
24-
{make(map[interface{}]T, 0), true},
25-
{make(map[interface{}]*T, 0), true},
24+
{make(map[interface{}]T), true},
25+
{make(map[interface{}]*T), true},
2626
}
2727
for _, test := range cases {
2828
_, err := NewNotificationQuery(test.ch, "any")
@@ -68,14 +68,7 @@ func TestNotificationQuery(t *testing.T) {
6868

6969
// Stop the query and confirm routine is dead.
7070
query.Stop()
71-
done := make(chan struct{})
72-
go func() {
73-
wg.Wait()
74-
close(done)
75-
}()
76-
select {
77-
case <-done:
78-
case <-time.After(500 * time.Millisecond):
71+
if stopped := wgWaitTimeout(&wg, 500*time.Millisecond); !stopped {
7972
t.Errorf("Failed to stop query in 5x NotificationTimeout's")
8073
}
8174

@@ -122,14 +115,7 @@ func TestNotificationQuery_StartStop(t *testing.T) {
122115
// Do not get the event!
123116
// Stop the query and confirm routine is dead.
124117
query.Stop()
125-
done := make(chan struct{})
126-
go func() {
127-
wg.Wait()
128-
close(done)
129-
}()
130-
select {
131-
case <-done:
132-
case <-time.After(500 * time.Millisecond):
118+
if stopped := wgWaitTimeout(&wg, 500*time.Millisecond); !stopped {
133119
t.Errorf("Failed to stop query in 5x NotificationTimeout's")
134120
}
135121
}
@@ -170,14 +156,69 @@ WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Hour = 25` // Shou
170156

171157
// Stop the query and confirm routine is dead.
172158
query.Stop()
159+
if stopped := wgWaitTimeout(&wg, 500*time.Millisecond); !stopped {
160+
t.Errorf("Failed to stop query in 5x NotificationTimeout's")
161+
}
162+
}
163+
164+
func TestNotificationQuery_StopWithError(t *testing.T) {
165+
// A struct with incorrect fields that can't be unmarshaled.
166+
type event struct {
167+
StrangeField uint64 `wmi:"me_should_not_be_in_event"`
168+
}
169+
170+
// Create a query that will receive an event in a short time.
171+
resultCh := make(chan event)
172+
queryString := `
173+
SELECT * FROM __InstanceModificationEvent
174+
WHERE TargetInstance ISA 'Win32_LocalTime'`
175+
176+
query, err := NewNotificationQuery(resultCh, queryString)
177+
if err != nil {
178+
t.Fatalf("Failed to create NotificationQuery; %s", err)
179+
}
180+
query.SetNotificationTimeout(20 * time.Millisecond)
181+
const deadline = 1500 * time.Millisecond
182+
183+
var wg sync.WaitGroup
184+
wg.Add(1)
185+
go func() {
186+
if err := query.StartNotifications(); err == nil { // Here!
187+
t.Error("Failed to report an error from StartNotifications call")
188+
}
189+
wg.Done()
190+
}()
191+
192+
// Wait some time to get the error from StartNotifications about inability to
193+
// parse the received object.
194+
if stopped := wgWaitTimeout(&wg, deadline); !stopped {
195+
t.Errorf("Failed to receive an event in %s", deadline)
196+
}
197+
198+
// Stop the query and confirm routine is dead.
199+
var stopWG sync.WaitGroup
200+
stopWG.Add(1)
201+
go func() {
202+
query.Stop()
203+
stopWG.Done()
204+
}()
205+
if stopped := wgWaitTimeout(&stopWG, deadline); !stopped {
206+
t.Errorf("Failed to stop query in %s", deadline)
207+
}
208+
}
209+
210+
// Waits for wg.Wait() no more than timeout. Returns true if wg.Wait returned
211+
// before timeout.
212+
func wgWaitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
173213
done := make(chan struct{})
174214
go func() {
175215
wg.Wait()
176216
close(done)
177217
}()
178218
select {
179219
case <-done:
180-
case <-time.After(500 * time.Millisecond):
181-
t.Errorf("Failed to stop query in 5x NotificationTimeout's")
220+
return true
221+
case <-time.After(timeout):
222+
return false
182223
}
183224
}

0 commit comments

Comments
 (0)