dbserver.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package dbtest
  2. import (
  3. "bytes"
  4. "fmt"
  5. "net"
  6. "os"
  7. "os/exec"
  8. "strconv"
  9. "time"
  10. "gopkg.in/mgo.v2-unstable"
  11. "gopkg.in/tomb.v2"
  12. )
  13. // DBServer controls a MongoDB server process to be used within test suites.
  14. //
  15. // The test server is started when Session is called the first time and should
  16. // remain running for the duration of all tests, with the Wipe method being
  17. // called between tests (before each of them) to clear stored data. After all tests
  18. // are done, the Stop method should be called to stop the test server.
  19. //
  20. // Before the DBServer is used the SetPath method must be called to define
  21. // the location for the database files to be stored.
  22. type DBServer struct {
  23. session *mgo.Session
  24. output bytes.Buffer
  25. server *exec.Cmd
  26. dbpath string
  27. host string
  28. tomb tomb.Tomb
  29. }
  30. // SetPath defines the path to the directory where the database files will be
  31. // stored if it is started. The directory path itself is not created or removed
  32. // by the test helper.
  33. func (dbs *DBServer) SetPath(dbpath string) {
  34. dbs.dbpath = dbpath
  35. }
  36. func (dbs *DBServer) start() {
  37. if dbs.server != nil {
  38. panic("DBServer already started")
  39. }
  40. if dbs.dbpath == "" {
  41. panic("DBServer.SetPath must be called before using the server")
  42. }
  43. mgo.SetStats(true)
  44. l, err := net.Listen("tcp", "127.0.0.1:0")
  45. if err != nil {
  46. panic("unable to listen on a local address: " + err.Error())
  47. }
  48. addr := l.Addr().(*net.TCPAddr)
  49. l.Close()
  50. dbs.host = addr.String()
  51. args := []string{
  52. "--dbpath", dbs.dbpath,
  53. "--bind_ip", "127.0.0.1",
  54. "--port", strconv.Itoa(addr.Port),
  55. "--nssize", "1",
  56. "--noprealloc",
  57. "--smallfiles",
  58. "--nojournal",
  59. }
  60. dbs.tomb = tomb.Tomb{}
  61. dbs.server = exec.Command("mongod", args...)
  62. dbs.server.Stdout = &dbs.output
  63. dbs.server.Stderr = &dbs.output
  64. err = dbs.server.Start()
  65. if err != nil {
  66. panic(err)
  67. }
  68. dbs.tomb.Go(dbs.monitor)
  69. dbs.Wipe()
  70. }
  71. func (dbs *DBServer) monitor() error {
  72. dbs.server.Process.Wait()
  73. if dbs.tomb.Alive() {
  74. // Present some debugging information.
  75. fmt.Fprintf(os.Stderr, "---- mongod process died unexpectedly:\n")
  76. fmt.Fprintf(os.Stderr, "%s", dbs.output.Bytes())
  77. fmt.Fprintf(os.Stderr, "---- mongod processes running right now:\n")
  78. cmd := exec.Command("/bin/sh", "-c", "ps auxw | grep mongod")
  79. cmd.Stdout = os.Stderr
  80. cmd.Stderr = os.Stderr
  81. cmd.Run()
  82. fmt.Fprintf(os.Stderr, "----------------------------------------\n")
  83. panic("mongod process died unexpectedly")
  84. }
  85. return nil
  86. }
  87. // Stop stops the test server process, if it is running.
  88. //
  89. // It's okay to call Stop multiple times. After the test server is
  90. // stopped it cannot be restarted.
  91. //
  92. // All database sessions must be closed before or while the Stop method
  93. // is running. Otherwise Stop will panic after a timeout informing that
  94. // there is a session leak.
  95. func (dbs *DBServer) Stop() {
  96. if dbs.session != nil {
  97. dbs.checkSessions()
  98. if dbs.session != nil {
  99. dbs.session.Close()
  100. dbs.session = nil
  101. }
  102. }
  103. if dbs.server != nil {
  104. dbs.tomb.Kill(nil)
  105. dbs.server.Process.Signal(os.Interrupt)
  106. select {
  107. case <-dbs.tomb.Dead():
  108. case <-time.After(5 * time.Second):
  109. panic("timeout waiting for mongod process to die")
  110. }
  111. dbs.server = nil
  112. }
  113. }
  114. // Session returns a new session to the server. The returned session
  115. // must be closed after the test is done with it.
  116. //
  117. // The first Session obtained from a DBServer will start it.
  118. func (dbs *DBServer) Session() *mgo.Session {
  119. if dbs.server == nil {
  120. dbs.start()
  121. }
  122. if dbs.session == nil {
  123. mgo.ResetStats()
  124. var err error
  125. dbs.session, err = mgo.Dial(dbs.host + "/test")
  126. if err != nil {
  127. panic(err)
  128. }
  129. }
  130. return dbs.session.Copy()
  131. }
  132. // checkSessions ensures all mgo sessions opened were properly closed.
  133. // For slightly faster tests, it may be disabled setting the
  134. // environmnet variable CHECK_SESSIONS to 0.
  135. func (dbs *DBServer) checkSessions() {
  136. if check := os.Getenv("CHECK_SESSIONS"); check == "0" || dbs.server == nil || dbs.session == nil {
  137. return
  138. }
  139. dbs.session.Close()
  140. dbs.session = nil
  141. for i := 0; i < 100; i++ {
  142. stats := mgo.GetStats()
  143. if stats.SocketsInUse == 0 && stats.SocketsAlive == 0 {
  144. return
  145. }
  146. time.Sleep(100 * time.Millisecond)
  147. }
  148. panic("There are mgo sessions still alive.")
  149. }
  150. // Wipe drops all created databases and their data.
  151. //
  152. // The MongoDB server remains running if it was prevoiusly running,
  153. // or stopped if it was previously stopped.
  154. //
  155. // All database sessions must be closed before or while the Wipe method
  156. // is running. Otherwise Wipe will panic after a timeout informing that
  157. // there is a session leak.
  158. func (dbs *DBServer) Wipe() {
  159. if dbs.server == nil || dbs.session == nil {
  160. return
  161. }
  162. dbs.checkSessions()
  163. sessionUnset := dbs.session == nil
  164. session := dbs.Session()
  165. defer session.Close()
  166. if sessionUnset {
  167. dbs.session.Close()
  168. dbs.session = nil
  169. }
  170. names, err := session.DatabaseNames()
  171. if err != nil {
  172. panic(err)
  173. }
  174. for _, name := range names {
  175. switch name {
  176. case "admin", "local", "config":
  177. default:
  178. err = session.DB(name).DropDatabase()
  179. if err != nil {
  180. panic(err)
  181. }
  182. }
  183. }
  184. }