Merge lp:~axwalk/juju-core/testing-mgo-wait-listening into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: no longer in the source branch.
Merged at revision: 2704
Proposed branch: lp:~axwalk/juju-core/testing-mgo-wait-listening
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 368 lines (+88/-140)
3 files modified
store/mgo_test.go (+0/-103)
store/store_test.go (+10/-10)
testing/mgo.go (+78/-27)
To merge this branch: bzr merge lp:~axwalk/juju-core/testing-mgo-wait-listening
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+218571@code.launchpad.net

Commit message

Improve mgo testing

- Use testing.MgoSuite in the store package
- Wait for "waiting for connections" message in mongod
  output before starting tests.
- Fix testing.MgoInstance.DialInfo to configure
  the client based on whether TLS is being used.

https://codereview.appspot.com/97110043/

Description of the change

Improve mgo testing

- Use testing.MgoSuite in the store package
- Wait for "waiting for connections" message in mongod
  output before starting tests.
- Fix testing.MgoInstance.DialInfo to configure
  the client based on whether TLS is being used.

https://codereview.appspot.com/97110043/

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) wrote :

Reviewers: mp+218571_code.launchpad.net,

Message:
Please take a look.

Description:
Improve mgo testing

- Use testing.MgoSuite in the store package
- Wait for "waiting for connections" message in mongod
   output before starting tests.
- Fix testing.MgoInstance.DialInfo to configure
   the client based on whether TLS is being used.

https://code.launchpad.net/~axwalk/juju-core/testing-mgo-wait-listening/+merge/218571

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/97110043/

Affected files (+90, -140 lines):
   A [revision details]
   D store/mgo_test.go
   M store/store_test.go
   M testing/mgo.go

Revision history for this message
William Reade (fwereade) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'store/mgo_test.go'
2--- store/mgo_test.go 2013-09-13 14:48:13 +0000
3+++ store/mgo_test.go 1970-01-01 00:00:00 +0000
4@@ -1,103 +0,0 @@
5-// Copyright 2011, 2012, 2013 Canonical Ltd.
6-// Licensed under the AGPLv3, see LICENCE file for details.
7-
8-package store_test
9-
10-import (
11- "bytes"
12- "os/exec"
13- "time"
14-
15- "labix.org/v2/mgo"
16- gc "launchpad.net/gocheck"
17-
18- "launchpad.net/juju-core/testing"
19-)
20-
21-// ----------------------------------------------------------------------------
22-// The mgo test suite
23-
24-type MgoSuite struct {
25- Addr string
26- Session *mgo.Session
27- output bytes.Buffer
28- server *exec.Cmd
29-}
30-
31-func (s *MgoSuite) SetUpSuite(c *gc.C) {
32- mgo.SetDebug(true)
33- mgo.SetStats(true)
34- dbdir := c.MkDir()
35- args := []string{
36- "--dbpath", dbdir,
37- "--bind_ip", "127.0.0.1",
38- "--port", "50017",
39- "--nssize", "1",
40- "--noprealloc",
41- "--smallfiles",
42- "--nojournal",
43- }
44- s.server = exec.Command("mongod", args...)
45- s.server.Stdout = &s.output
46- s.server.Stderr = &s.output
47- err := s.server.Start()
48- c.Assert(err, gc.IsNil)
49-}
50-
51-func (s *MgoSuite) TearDownSuite(c *gc.C) {
52- s.server.Process.Kill()
53- s.server.Process.Wait()
54-}
55-
56-func (s *MgoSuite) SetUpTest(c *gc.C) {
57- err := DropAll("localhost:50017")
58- c.Assert(err, gc.IsNil)
59- mgo.SetLogger(c)
60- mgo.ResetStats()
61- s.Addr = "127.0.0.1:50017"
62- s.Session, err = mgo.Dial(s.Addr)
63- c.Assert(err, gc.IsNil)
64-}
65-
66-func (s *MgoSuite) TearDownTest(c *gc.C) {
67- if s.Session != nil {
68- s.Session.Close()
69- }
70- t0 := time.Now()
71- for i := 0; ; i++ {
72- stats := mgo.GetStats()
73- if stats.SocketsInUse == 0 && stats.SocketsAlive == 0 {
74- break
75- }
76- if time.Since(t0) > testing.LongWait {
77- // We wait up to 10s for all workers to finish up
78- c.Fatal("Test left sockets in a dirty state")
79- }
80- c.Logf("Waiting for sockets to die: %d in use, %d alive", stats.SocketsInUse, stats.SocketsAlive)
81- time.Sleep(testing.ShortWait)
82- }
83-}
84-
85-func DropAll(mongourl string) (err error) {
86- session, err := mgo.Dial(mongourl)
87- if err != nil {
88- return err
89- }
90- defer session.Close()
91-
92- names, err := session.DatabaseNames()
93- if err != nil {
94- return err
95- }
96- for _, name := range names {
97- switch name {
98- case "admin", "local", "config":
99- default:
100- err = session.DB(name).DropDatabase()
101- if err != nil {
102- return err
103- }
104- }
105- }
106- return nil
107-}
108
109=== modified file 'store/store_test.go'
110--- store/store_test.go 2014-04-12 05:53:58 +0000
111+++ store/store_test.go 2014-05-07 08:33:15 +0000
112@@ -24,14 +24,14 @@
113 )
114
115 func Test(t *stdtesting.T) {
116- gc.TestingT(t)
117+ testing.MgoTestPackageSsl(t, false)
118 }
119
120 var _ = gc.Suite(&StoreSuite{})
121 var _ = gc.Suite(&TrivialSuite{})
122
123 type StoreSuite struct {
124- MgoSuite
125+ testing.MgoSuite
126 testing.HTTPSuite
127 testbase.LoggingSuite
128 store *store.Store
129@@ -42,10 +42,9 @@
130 type TrivialSuite struct{}
131
132 func (s *StoreSuite) SetUpSuite(c *gc.C) {
133+ s.LoggingSuite.SetUpSuite(c)
134 s.MgoSuite.SetUpSuite(c)
135 s.HTTPSuite.SetUpSuite(c)
136- s.LoggingSuite.SetUpSuite(c)
137-
138 if os.Getenv("JUJU_NOTEST_MONGOJS") == "1" {
139 c.Log("Tests requiring MongoDB Javascript will be skipped")
140 *noTestMongoJs = true
141@@ -53,26 +52,27 @@
142 }
143
144 func (s *StoreSuite) TearDownSuite(c *gc.C) {
145- s.LoggingSuite.TearDownSuite(c)
146 s.HTTPSuite.TearDownSuite(c)
147 s.MgoSuite.TearDownSuite(c)
148+ s.LoggingSuite.TearDownSuite(c)
149 }
150
151 func (s *StoreSuite) SetUpTest(c *gc.C) {
152+ s.LoggingSuite.SetUpTest(c)
153 s.MgoSuite.SetUpTest(c)
154- s.LoggingSuite.SetUpTest(c)
155+ s.HTTPSuite.SetUpTest(c)
156 var err error
157- s.store, err = store.Open(s.Addr)
158+ s.store, err = store.Open(testing.MgoServer.Addr())
159 c.Assert(err, gc.IsNil)
160 }
161
162 func (s *StoreSuite) TearDownTest(c *gc.C) {
163- s.LoggingSuite.TearDownTest(c)
164- s.HTTPSuite.TearDownTest(c)
165 if s.store != nil {
166 s.store.Close()
167 }
168+ s.HTTPSuite.TearDownTest(c)
169 s.MgoSuite.TearDownTest(c)
170+ s.LoggingSuite.TearDownTest(c)
171 }
172
173 // FakeCharmDir is a charm that implements the interface that the
174@@ -839,7 +839,7 @@
175 }
176
177 // Use a different store to exercise cache filling.
178- st, err := store.Open(s.Addr)
179+ st, err := store.Open(testing.MgoServer.Addr())
180 c.Assert(err, gc.IsNil)
181 defer st.Close()
182
183
184=== modified file 'testing/mgo.go'
185--- testing/mgo.go 2014-04-17 00:19:01 +0000
186+++ testing/mgo.go 2014-05-07 08:33:15 +0000
187@@ -5,6 +5,7 @@
188
189 import (
190 "bufio"
191+ "bytes"
192 "crypto/tls"
193 "crypto/x509"
194 "fmt"
195@@ -14,6 +15,7 @@
196 "os"
197 "os/exec"
198 "path/filepath"
199+ "regexp"
200 "strconv"
201 "strings"
202 stdtesting "testing"
203@@ -31,6 +33,9 @@
204 // MgoServer is a shared mongo server used by tests.
205 MgoServer = &MgoInstance{ssl: true}
206 logger = loggo.GetLogger("juju.testing")
207+
208+ // regular expression to match output of mongod
209+ waitingForConnectionsRe = regexp.MustCompile(".*initandlisten.*waiting for connections.*")
210 )
211
212 type MgoInstance struct {
213@@ -150,10 +155,21 @@
214 server.Stderr = server.Stdout
215 exited := make(chan struct{})
216 started := make(chan struct{})
217+ listening := make(chan error, 1)
218 go func() {
219 <-started
220- lines := readLines(fmt.Sprintf("mongod:%v", mgoport), out, 20)
221- err := server.Wait()
222+ // Wait until the server is listening.
223+ var buf bytes.Buffer
224+ prefix := fmt.Sprintf("mongod:%v", mgoport)
225+ if readUntilMatching(prefix, io.TeeReader(out, &buf), waitingForConnectionsRe) {
226+ listening <- nil
227+ } else {
228+ listening <- fmt.Errorf("mongod failed to listen on port %v", mgoport)
229+ }
230+ // Capture the last 20 lines of output from mongod, to log
231+ // in the event of unclean exit.
232+ lines := readLastLines(prefix, io.MultiReader(&buf, out), 20)
233+ err = server.Wait()
234 exitErr, _ := err.(*exec.ExitError)
235 if err == nil || exitErr != nil && exitErr.Exited() {
236 // mongodb has exited without being killed, so print the
237@@ -171,6 +187,11 @@
238 if err != nil {
239 return err
240 }
241+ err = <-listening
242+ close(listening)
243+ if err != nil {
244+ return err
245+ }
246 inst.server = server
247
248 return nil
249@@ -220,27 +241,41 @@
250 if MgoServer.addr == "" {
251 panic("MgoSuite tests must be run with MgoTestPackage")
252 }
253+ mgo.SetDebug(true)
254 mgo.SetStats(true)
255 // Make tests that use password authentication faster.
256 utils.FastInsecureHash = true
257 }
258
259-// readLines reads lines from the given reader and returns
260+// readUntilMatching reads lines from the given reader until the reader
261+// is depleted or a line matches the given regular expression.
262+func readUntilMatching(prefix string, r io.Reader, re *regexp.Regexp) bool {
263+ sc := bufio.NewScanner(r)
264+ for sc.Scan() {
265+ line := sc.Text()
266+ logger.Tracef("%s: %s", prefix, line)
267+ if re.MatchString(line) {
268+ return true
269+ }
270+ }
271+ return false
272+}
273+
274+// readLastLines reads lines from the given reader and returns
275 // the last n non-empty lines, ignoring empty lines.
276-func readLines(prefix string, r io.Reader, n int) []string {
277- br := bufio.NewReader(r)
278+func readLastLines(prefix string, r io.Reader, n int) []string {
279+ sc := bufio.NewScanner(r)
280 lines := make([]string, n)
281 i := 0
282- for {
283- line, err := br.ReadString('\n')
284- if line = strings.TrimRight(line, "\n"); line != "" {
285+ for sc.Scan() {
286+ if line := strings.TrimRight(sc.Text(), "\n"); line != "" {
287 logger.Tracef("%s: %s", prefix, line)
288 lines[i%n] = line
289 i++
290 }
291- if err != nil {
292- break
293- }
294+ }
295+ if err := sc.Err(); err != nil {
296+ panic(err)
297 }
298 final := make([]string, 0, n+1)
299 if i > n {
300@@ -276,7 +311,7 @@
301 // DialInfo returns information suitable for dialling the
302 // receiving MongoDB instance.
303 func (inst *MgoInstance) DialInfo() *mgo.DialInfo {
304- return MgoDialInfo(inst.addr)
305+ return MgoDialInfoTls(inst.ssl, inst.addr)
306 }
307
308 // DialDirect returns a new direct connection to the shared MongoDB server. This
309@@ -301,28 +336,44 @@
310 // for dialling an MgoInstance at any of the
311 // given addresses.
312 func MgoDialInfo(addrs ...string) *mgo.DialInfo {
313- pool := x509.NewCertPool()
314- xcert, err := cert.ParseCert(CACert)
315- if err != nil {
316- panic(err)
317- }
318- pool.AddCert(xcert)
319- tlsConfig := &tls.Config{
320- RootCAs: pool,
321- ServerName: "anything",
322- }
323- return &mgo.DialInfo{
324- Addrs: addrs,
325- Dial: func(addr net.Addr) (net.Conn, error) {
326+ return MgoDialInfoTls(true, addrs...)
327+}
328+
329+// MgoDialInfoTls returns a DialInfo suitable
330+// for dialling an MgoInstance at any of the
331+// given addresses, optionally using TLS.
332+func MgoDialInfoTls(useTls bool, addrs ...string) *mgo.DialInfo {
333+ var dial func(addr net.Addr) (net.Conn, error)
334+ if useTls {
335+ pool := x509.NewCertPool()
336+ xcert, err := cert.ParseCert(CACert)
337+ if err != nil {
338+ panic(err)
339+ }
340+ pool.AddCert(xcert)
341+ tlsConfig := &tls.Config{
342+ RootCAs: pool,
343+ ServerName: "anything",
344+ }
345+ dial = func(addr net.Addr) (net.Conn, error) {
346 conn, err := tls.Dial("tcp", addr.String(), tlsConfig)
347 if err != nil {
348 logger.Debugf("tls.Dial(%s) failed with %v", addr, err)
349 return nil, err
350 }
351 return conn, nil
352- },
353- Timeout: mgoDialTimeout,
354+ }
355+ } else {
356+ dial = func(addr net.Addr) (net.Conn, error) {
357+ conn, err := net.Dial("tcp", addr.String())
358+ if err != nil {
359+ logger.Debugf("net.Dial(%s) failed with %v", addr, err)
360+ return nil, err
361+ }
362+ return conn, nil
363+ }
364 }
365+ return &mgo.DialInfo{Addrs: addrs, Dial: dial, Timeout: mgoDialTimeout}
366 }
367
368 func (s *MgoSuite) SetUpTest(c *gc.C) {

Subscribers

People subscribed via source and target branches

to status/vote changes: