You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
11 KiB

10 years ago
  1. // Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
  2. //
  3. // Copyright 2013 Google Inc. All Rights Reserved.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. package glog
  17. import (
  18. "bytes"
  19. "fmt"
  20. stdLog "log"
  21. "path/filepath"
  22. "runtime"
  23. "strconv"
  24. "strings"
  25. "testing"
  26. "time"
  27. )
  28. // Test that shortHostname works as advertised.
  29. func TestShortHostname(t *testing.T) {
  30. for hostname, expect := range map[string]string{
  31. "": "",
  32. "host": "host",
  33. "host.google.com": "host",
  34. } {
  35. if got := shortHostname(hostname); expect != got {
  36. t.Errorf("shortHostname(%q): expected %q, got %q", hostname, expect, got)
  37. }
  38. }
  39. }
  40. // flushBuffer wraps a bytes.Buffer to satisfy flushSyncWriter.
  41. type flushBuffer struct {
  42. bytes.Buffer
  43. }
  44. func (f *flushBuffer) Flush() error {
  45. return nil
  46. }
  47. func (f *flushBuffer) Sync() error {
  48. return nil
  49. }
  50. // swap sets the log writers and returns the old array.
  51. func (l *loggingT) swap(writers [numSeverity]flushSyncWriter) (old [numSeverity]flushSyncWriter) {
  52. l.mu.Lock()
  53. defer l.mu.Unlock()
  54. old = l.file
  55. for i, w := range writers {
  56. logging.file[i] = w
  57. }
  58. return
  59. }
  60. // newBuffers sets the log writers to all new byte buffers and returns the old array.
  61. func (l *loggingT) newBuffers() [numSeverity]flushSyncWriter {
  62. return l.swap([numSeverity]flushSyncWriter{new(flushBuffer), new(flushBuffer), new(flushBuffer), new(flushBuffer)})
  63. }
  64. // contents returns the specified log value as a string.
  65. func contents(s severity) string {
  66. return logging.file[s].(*flushBuffer).String()
  67. }
  68. // contains reports whether the string is contained in the log.
  69. func contains(s severity, str string, t *testing.T) bool {
  70. return strings.Contains(contents(s), str)
  71. }
  72. // setFlags configures the logging flags how the test expects them.
  73. func setFlags() {
  74. logging.toStderr = false
  75. }
  76. // Test that Info works as advertised.
  77. func TestInfo(t *testing.T) {
  78. setFlags()
  79. defer logging.swap(logging.newBuffers())
  80. Info("test")
  81. if !contains(infoLog, "I", t) {
  82. t.Errorf("Info has wrong character: %q", contents(infoLog))
  83. }
  84. if !contains(infoLog, "test", t) {
  85. t.Error("Info failed")
  86. }
  87. }
  88. func TestInfoDepth(t *testing.T) {
  89. setFlags()
  90. defer logging.swap(logging.newBuffers())
  91. f := func() { InfoDepth(1, "depth-test1") }
  92. // The next three lines must stay together
  93. _, _, wantLine, _ := runtime.Caller(0)
  94. InfoDepth(0, "depth-test0")
  95. f()
  96. msgs := strings.Split(strings.TrimSuffix(contents(infoLog), "\n"), "\n")
  97. if len(msgs) != 2 {
  98. t.Fatalf("Got %d lines, expected 2", len(msgs))
  99. }
  100. for i, m := range msgs {
  101. if !strings.HasPrefix(m, "I") {
  102. t.Errorf("InfoDepth[%d] has wrong character: %q", i, m)
  103. }
  104. w := fmt.Sprintf("depth-test%d", i)
  105. if !strings.Contains(m, w) {
  106. t.Errorf("InfoDepth[%d] missing %q: %q", i, w, m)
  107. }
  108. // pull out the line number (between : and ])
  109. msg := m[strings.LastIndex(m, ":")+1:]
  110. x := strings.Index(msg, "]")
  111. if x < 0 {
  112. t.Errorf("InfoDepth[%d]: missing ']': %q", i, m)
  113. continue
  114. }
  115. line, err := strconv.Atoi(msg[:x])
  116. if err != nil {
  117. t.Errorf("InfoDepth[%d]: bad line number: %q", i, m)
  118. continue
  119. }
  120. wantLine++
  121. if wantLine != line {
  122. t.Errorf("InfoDepth[%d]: got line %d, want %d", i, line, wantLine)
  123. }
  124. }
  125. }
  126. func init() {
  127. CopyStandardLogTo("INFO")
  128. }
  129. // Test that CopyStandardLogTo panics on bad input.
  130. func TestCopyStandardLogToPanic(t *testing.T) {
  131. defer func() {
  132. if s, ok := recover().(string); !ok || !strings.Contains(s, "LOG") {
  133. t.Errorf(`CopyStandardLogTo("LOG") should have panicked: %v`, s)
  134. }
  135. }()
  136. CopyStandardLogTo("LOG")
  137. }
  138. // Test that using the standard log package logs to INFO.
  139. func TestStandardLog(t *testing.T) {
  140. setFlags()
  141. defer logging.swap(logging.newBuffers())
  142. stdLog.Print("test")
  143. if !contains(infoLog, "I", t) {
  144. t.Errorf("Info has wrong character: %q", contents(infoLog))
  145. }
  146. if !contains(infoLog, "test", t) {
  147. t.Error("Info failed")
  148. }
  149. }
  150. // Test that the header has the correct format.
  151. func TestHeader(t *testing.T) {
  152. setFlags()
  153. defer logging.swap(logging.newBuffers())
  154. defer func(previous func() time.Time) { timeNow = previous }(timeNow)
  155. timeNow = func() time.Time {
  156. return time.Date(2006, 1, 2, 15, 4, 5, .067890e9, time.Local)
  157. }
  158. pid = 1234
  159. Info("test")
  160. var line int
  161. format := "I0102 15:04:05 1234 glog_test.go:%d] test\n"
  162. n, err := fmt.Sscanf(contents(infoLog), format, &line)
  163. if n != 1 || err != nil {
  164. t.Errorf("log format error: %d elements, error %s:\n%s", n, err, contents(infoLog))
  165. }
  166. // Scanf treats multiple spaces as equivalent to a single space,
  167. // so check for correct space-padding also.
  168. want := fmt.Sprintf(format, line)
  169. if contents(infoLog) != want {
  170. t.Errorf("log format error: got:\n\t%q\nwant:\t%q", contents(infoLog), want)
  171. }
  172. }
  173. // Test that an Error log goes to Warning and Info.
  174. // Even in the Info log, the source character will be E, so the data should
  175. // all be identical.
  176. func TestError(t *testing.T) {
  177. setFlags()
  178. defer logging.swap(logging.newBuffers())
  179. Error("test")
  180. if !contains(errorLog, "E", t) {
  181. t.Errorf("Error has wrong character: %q", contents(errorLog))
  182. }
  183. if !contains(errorLog, "test", t) {
  184. t.Error("Error failed")
  185. }
  186. str := contents(errorLog)
  187. if !contains(warningLog, str, t) {
  188. t.Error("Warning failed")
  189. }
  190. if !contains(infoLog, str, t) {
  191. t.Error("Info failed")
  192. }
  193. }
  194. // Test that a Warning log goes to Info.
  195. // Even in the Info log, the source character will be W, so the data should
  196. // all be identical.
  197. func TestWarning(t *testing.T) {
  198. setFlags()
  199. defer logging.swap(logging.newBuffers())
  200. Warning("test")
  201. if !contains(warningLog, "W", t) {
  202. t.Errorf("Warning has wrong character: %q", contents(warningLog))
  203. }
  204. if !contains(warningLog, "test", t) {
  205. t.Error("Warning failed")
  206. }
  207. str := contents(warningLog)
  208. if !contains(infoLog, str, t) {
  209. t.Error("Info failed")
  210. }
  211. }
  212. // Test that a V log goes to Info.
  213. func TestV(t *testing.T) {
  214. setFlags()
  215. defer logging.swap(logging.newBuffers())
  216. logging.verbosity.Set("2")
  217. defer logging.verbosity.Set("0")
  218. V(2).Info("test")
  219. if !contains(infoLog, "I", t) {
  220. t.Errorf("Info has wrong character: %q", contents(infoLog))
  221. }
  222. if !contains(infoLog, "test", t) {
  223. t.Error("Info failed")
  224. }
  225. }
  226. // Test that a vmodule enables a log in this file.
  227. func TestVmoduleOn(t *testing.T) {
  228. setFlags()
  229. defer logging.swap(logging.newBuffers())
  230. logging.vmodule.Set("glog_test=2")
  231. defer logging.vmodule.Set("")
  232. if !V(1) {
  233. t.Error("V not enabled for 1")
  234. }
  235. if !V(2) {
  236. t.Error("V not enabled for 2")
  237. }
  238. if V(3) {
  239. t.Error("V enabled for 3")
  240. }
  241. V(2).Info("test")
  242. if !contains(infoLog, "I", t) {
  243. t.Errorf("Info has wrong character: %q", contents(infoLog))
  244. }
  245. if !contains(infoLog, "test", t) {
  246. t.Error("Info failed")
  247. }
  248. }
  249. // Test that a vmodule of another file does not enable a log in this file.
  250. func TestVmoduleOff(t *testing.T) {
  251. setFlags()
  252. defer logging.swap(logging.newBuffers())
  253. logging.vmodule.Set("notthisfile=2")
  254. defer logging.vmodule.Set("")
  255. for i := 1; i <= 3; i++ {
  256. if V(Level(i)) {
  257. t.Errorf("V enabled for %d", i)
  258. }
  259. }
  260. V(2).Info("test")
  261. if contents(infoLog) != "" {
  262. t.Error("V logged incorrectly")
  263. }
  264. }
  265. // vGlobs are patterns that match/don't match this file at V=2.
  266. var vGlobs = map[string]bool{
  267. // Easy to test the numeric match here.
  268. "glog_test=1": false, // If -vmodule sets V to 1, V(2) will fail.
  269. "glog_test=2": true,
  270. "glog_test=3": true, // If -vmodule sets V to 1, V(3) will succeed.
  271. // These all use 2 and check the patterns. All are true.
  272. "*=2": true,
  273. "?l*=2": true,
  274. "????_*=2": true,
  275. "??[mno]?_*t=2": true,
  276. // These all use 2 and check the patterns. All are false.
  277. "*x=2": false,
  278. "m*=2": false,
  279. "??_*=2": false,
  280. "?[abc]?_*t=2": false,
  281. }
  282. // Test that vmodule globbing works as advertised.
  283. func testVmoduleGlob(pat string, match bool, t *testing.T) {
  284. setFlags()
  285. defer logging.swap(logging.newBuffers())
  286. defer logging.vmodule.Set("")
  287. logging.vmodule.Set(pat)
  288. if V(2) != Verbose(match) {
  289. t.Errorf("incorrect match for %q: got %t expected %t", pat, V(2), match)
  290. }
  291. }
  292. // Test that a vmodule globbing works as advertised.
  293. func TestVmoduleGlob(t *testing.T) {
  294. for glob, match := range vGlobs {
  295. testVmoduleGlob(glob, match, t)
  296. }
  297. }
  298. func TestRollover(t *testing.T) {
  299. setFlags()
  300. var err error
  301. defer func(previous func(error)) { logExitFunc = previous }(logExitFunc)
  302. logExitFunc = func(e error) {
  303. err = e
  304. }
  305. defer func(previous uint64) { MaxSize = previous }(MaxSize)
  306. MaxSize = 512
  307. Info("x") // Be sure we have a file.
  308. info, ok := logging.file[infoLog].(*syncBuffer)
  309. if !ok {
  310. t.Fatal("info wasn't created")
  311. }
  312. if err != nil {
  313. t.Fatalf("info has initial error: %v", err)
  314. }
  315. fname0 := info.file.Name()
  316. Info(strings.Repeat("x", int(MaxSize))) // force a rollover
  317. if err != nil {
  318. t.Fatalf("info has error after big write: %v", err)
  319. }
  320. // Make sure the next log file gets a file name with a different
  321. // time stamp.
  322. //
  323. // TODO: determine whether we need to support subsecond log
  324. // rotation. C++ does not appear to handle this case (nor does it
  325. // handle Daylight Savings Time properly).
  326. time.Sleep(1 * time.Second)
  327. Info("x") // create a new file
  328. if err != nil {
  329. t.Fatalf("error after rotation: %v", err)
  330. }
  331. fname1 := info.file.Name()
  332. if fname0 == fname1 {
  333. t.Errorf("info.f.Name did not change: %v", fname0)
  334. }
  335. if info.nbytes >= MaxSize {
  336. t.Errorf("file size was not reset: %d", info.nbytes)
  337. }
  338. }
  339. func TestLogBacktraceAt(t *testing.T) {
  340. setFlags()
  341. defer logging.swap(logging.newBuffers())
  342. // The peculiar style of this code simplifies line counting and maintenance of the
  343. // tracing block below.
  344. var infoLine string
  345. setTraceLocation := func(file string, line int, ok bool, delta int) {
  346. if !ok {
  347. t.Fatal("could not get file:line")
  348. }
  349. _, file = filepath.Split(file)
  350. infoLine = fmt.Sprintf("%s:%d", file, line+delta)
  351. err := logging.traceLocation.Set(infoLine)
  352. if err != nil {
  353. t.Fatal("error setting log_backtrace_at: ", err)
  354. }
  355. }
  356. {
  357. // Start of tracing block. These lines know about each other's relative position.
  358. _, file, line, ok := runtime.Caller(0)
  359. setTraceLocation(file, line, ok, +2) // Two lines between Caller and Info calls.
  360. Info("we want a stack trace here")
  361. }
  362. numAppearances := strings.Count(contents(infoLog), infoLine)
  363. if numAppearances < 2 {
  364. // Need 2 appearances, one in the log header and one in the trace:
  365. // log_test.go:281: I0511 16:36:06.952398 02238 log_test.go:280] we want a stack trace here
  366. // ...
  367. // github.com/glog/glog_test.go:280 (0x41ba91)
  368. // ...
  369. // We could be more precise but that would require knowing the details
  370. // of the traceback format, which may not be dependable.
  371. t.Fatal("got no trace back; log is ", contents(infoLog))
  372. }
  373. }
  374. func BenchmarkHeader(b *testing.B) {
  375. for i := 0; i < b.N; i++ {
  376. buf, _, _ := logging.header(infoLog, 0)
  377. logging.putBuffer(buf)
  378. }
  379. }