LCOV - code coverage report
Current view: top level - pebble/metamorphic - history.go (source / functions) Hit Total Coverage
Test: 2023-11-14 08:18Z c0b4bd44 - meta test only.lcov Lines: 35 97 36.1 %
Date: 2023-11-14 08:20:03 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright 2019 The LevelDB-Go and Pebble Authors. All rights reserved. Use
       2             : // of this source code is governed by a BSD-style license that can be found in
       3             : // the LICENSE file.
       4             : 
       5             : package metamorphic
       6             : 
       7             : import (
       8             :         "fmt"
       9             :         "io"
      10             :         "log"
      11             :         "os"
      12             :         "regexp"
      13             :         "strconv"
      14             :         "strings"
      15             :         "sync/atomic"
      16             :         "unicode"
      17             : 
      18             :         "github.com/cockroachdb/errors"
      19             :         "github.com/pmezard/go-difflib/difflib"
      20             :         "github.com/stretchr/testify/require"
      21             : )
      22             : 
      23             : // history records the results of running a series of operations.
      24             : //
      25             : // history also implements the pebble.Logger interface, outputting to a stdlib
      26             : // logger, prefixing the log messages with "//"-style comments.
      27             : type history struct {
      28             :         err    atomic.Value
      29             :         failRE *regexp.Regexp
      30             :         log    *log.Logger
      31             : }
      32             : 
      33           1 : func newHistory(failRE *regexp.Regexp, writers ...io.Writer) *history {
      34           1 :         h := &history{failRE: failRE}
      35           1 :         h.log = log.New(io.MultiWriter(writers...), "", 0)
      36           1 :         return h
      37           1 : }
      38             : 
      39             : // Recordf records the results of a single operation.
      40           1 : func (h *history) Recordf(op int, format string, args ...interface{}) {
      41           1 :         if strings.Contains(format, "\n") {
      42           0 :                 // We could remove this restriction but suffixing every line with "#<seq>".
      43           0 :                 panic(fmt.Sprintf("format string must not contain \\n: %q", format))
      44             :         }
      45             : 
      46             :         // We suffix every line with #<op> in order to provide a marker to locate
      47             :         // the line using the diff output. This is necessary because the diff of two
      48             :         // histories is done after stripping comment lines (`// ...`) from the
      49             :         // history output, which ruins the line number information in the diff
      50             :         // output.
      51           1 :         m := fmt.Sprintf(format, args...) + fmt.Sprintf(" #%d", op)
      52           1 :         h.log.Print(m)
      53           1 : 
      54           1 :         if h.failRE != nil && h.failRE.MatchString(m) {
      55           0 :                 err := errors.Errorf("failure regexp %q matched output: %s", h.failRE, m)
      56           0 :                 h.err.Store(err)
      57           0 :         }
      58             : }
      59             : 
      60             : // Error returns an error if the test has failed from log output, either a
      61             : // failure regexp match or a call to Fatalf.
      62           1 : func (h *history) Error() error {
      63           1 :         if v := h.err.Load(); v != nil {
      64           0 :                 return v.(error)
      65           0 :         }
      66           1 :         return nil
      67             : }
      68             : 
      69           1 : func (h *history) format(prefix, format string, args ...interface{}) string {
      70           1 :         var buf strings.Builder
      71           1 :         orig := fmt.Sprintf(format, args...)
      72           1 :         for _, line := range strings.Split(strings.TrimSpace(orig), "\n") {
      73           1 :                 buf.WriteString(prefix)
      74           1 :                 buf.WriteString(line)
      75           1 :                 buf.WriteString("\n")
      76           1 :         }
      77           1 :         return buf.String()
      78             : }
      79             : 
      80             : // Infof implements the pebble.Logger interface. Note that the output is
      81             : // commented.
      82           1 : func (h *history) Infof(format string, args ...interface{}) {
      83           1 :         _ = h.log.Output(2, h.format("// INFO: ", format, args...))
      84           1 : }
      85             : 
      86             : // Errorf implements the pebble.Logger interface. Note that the output is
      87             : // commented.
      88           0 : func (h *history) Errorf(format string, args ...interface{}) {
      89           0 :         _ = h.log.Output(2, h.format("// ERROR: ", format, args...))
      90           0 : }
      91             : 
      92             : // Fatalf implements the pebble.Logger interface. Note that the output is
      93             : // commented.
      94           0 : func (h *history) Fatalf(format string, args ...interface{}) {
      95           0 :         _ = h.log.Output(2, h.format("// FATAL: ", format, args...))
      96           0 :         h.err.Store(errors.Errorf(format, args...))
      97           0 : }
      98             : 
      99           1 : func (h *history) recorder(thread int, op int) historyRecorder {
     100           1 :         return historyRecorder{
     101           1 :                 history: h,
     102           1 :                 op:      op,
     103           1 :         }
     104           1 : }
     105             : 
     106             : // historyRecorder pairs a history with an operation, annotating all lines
     107             : // recorded through it with the operation number.
     108             : type historyRecorder struct {
     109             :         history *history
     110             :         op      int
     111             : }
     112             : 
     113             : // Recordf records the results of a single operation.
     114           1 : func (h historyRecorder) Recordf(format string, args ...interface{}) {
     115           1 :         h.history.Recordf(h.op, format, args...)
     116           1 : }
     117             : 
     118             : // Error returns an error if the test has failed from log output, either a
     119             : // failure regexp match or a call to Fatalf.
     120           0 : func (h historyRecorder) Error() error {
     121           0 :         return h.history.Error()
     122           0 : }
     123             : 
     124             : // CompareHistories takes a slice of file paths containing history files. It
     125             : // performs a diff comparing the first path to all other paths. CompareHistories
     126             : // returns the index and diff for the first history that differs. If all the
     127             : // histories are identical, CompareHistories returns a zero index and an empty
     128             : // string.
     129           0 : func CompareHistories(t TestingT, paths []string) (i int, diff string) {
     130           0 :         base := readHistory(t, paths[0])
     131           0 :         base = reorderHistory(base)
     132           0 : 
     133           0 :         for i := 1; i < len(paths); i++ {
     134           0 :                 lines := readHistory(t, paths[i])
     135           0 :                 lines = reorderHistory(lines)
     136           0 :                 diff := difflib.UnifiedDiff{
     137           0 :                         A:       base,
     138           0 :                         B:       lines,
     139           0 :                         Context: 5,
     140           0 :                 }
     141           0 :                 text, err := difflib.GetUnifiedDiffString(diff)
     142           0 :                 require.NoError(t, err)
     143           0 :                 if text != "" {
     144           0 :                         return i, text
     145           0 :                 }
     146             :         }
     147           0 :         return 0, ""
     148             : }
     149             : 
     150             : // reorderHistory takes lines from a history file and reorders the operation
     151             : // results to be in the order of the operation index numbers. Runs with more
     152             : // than 1 thread may produce out-of-order histories. Comment lines must've
     153             : // already been filtered out.
     154           0 : func reorderHistory(lines []string) []string {
     155           0 :         reordered := make([]string, len(lines))
     156           0 :         for _, l := range lines {
     157           0 :                 if cleaned := strings.TrimSpace(l); cleaned == "" {
     158           0 :                         continue
     159             :                 }
     160           0 :                 reordered[extractOp(l)] = l
     161             :         }
     162           0 :         return reordered
     163             : }
     164             : 
     165             : // extractOp parses out an operation's index from the trailing comment. Every
     166             : // line of history output is suffixed with a comment containing `#<op>`
     167           0 : func extractOp(line string) int {
     168           0 :         i := strings.LastIndexByte(line, '#')
     169           0 :         j := strings.IndexFunc(line[i+1:], unicode.IsSpace)
     170           0 :         if j == -1 {
     171           0 :                 j = len(line[i+1:])
     172           0 :         }
     173           0 :         v, err := strconv.Atoi(line[i+1 : i+1+j])
     174           0 :         if err != nil {
     175           0 :                 panic(fmt.Sprintf("unable to parse line %q: %s", line, err))
     176             :         }
     177           0 :         return v
     178             : }
     179             : 
     180             : // Read a history file, stripping out lines that begin with a comment.
     181           0 : func readHistory(t TestingT, historyPath string) []string {
     182           0 :         data, err := os.ReadFile(historyPath)
     183           0 :         require.NoError(t, err)
     184           0 :         lines := difflib.SplitLines(string(data))
     185           0 :         newLines := make([]string, 0, len(lines))
     186           0 :         for _, line := range lines {
     187           0 :                 if strings.HasPrefix(line, "// ") {
     188           0 :                         continue
     189             :                 }
     190           0 :                 newLines = append(newLines, line)
     191             :         }
     192           0 :         return newLines
     193             : }

Generated by: LCOV version 1.14