fix: update glog to v1.2.4
Using the command
    go get github.com/golang/glog@v1.2.4
I upgraded the glog package to version 1.2.4
Signed-off-by: Raymond Etornam <retornam@users.noreply.github.com>
			
			
This commit is contained in:
		
							parent
							
								
									598f2cbd57
								
							
						
					
					
						commit
						66893eb9e7
					
				
							
								
								
									
										4
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										4
									
								
								go.mod
								
								
								
								
							|  | @ -3,7 +3,7 @@ module github.com/kubernetes-sigs/nfs-subdir-external-provisioner | ||||||
| go 1.19 | go 1.19 | ||||||
| 
 | 
 | ||||||
| require ( | require ( | ||||||
| 	github.com/golang/glog v1.0.0 | 	github.com/golang/glog v1.2.4 | ||||||
| 	k8s.io/api v0.23.4 | 	k8s.io/api v0.23.4 | ||||||
| 	k8s.io/apimachinery v0.23.4 | 	k8s.io/apimachinery v0.23.4 | ||||||
| 	k8s.io/client-go v0.23.4 | 	k8s.io/client-go v0.23.4 | ||||||
|  | @ -19,7 +19,7 @@ require ( | ||||||
| 	github.com/gogo/protobuf v1.3.2 // indirect | 	github.com/gogo/protobuf v1.3.2 // indirect | ||||||
| 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect | ||||||
| 	github.com/golang/protobuf v1.5.2 // indirect | 	github.com/golang/protobuf v1.5.2 // indirect | ||||||
| 	github.com/google/go-cmp v0.5.5 // indirect | 	github.com/google/go-cmp v0.6.0 // indirect | ||||||
| 	github.com/google/gofuzz v1.1.0 // indirect | 	github.com/google/gofuzz v1.1.0 // indirect | ||||||
| 	github.com/google/uuid v1.1.2 // indirect | 	github.com/google/uuid v1.1.2 // indirect | ||||||
| 	github.com/googleapis/gnostic v0.5.5 // indirect | 	github.com/googleapis/gnostic v0.5.5 // indirect | ||||||
|  |  | ||||||
							
								
								
									
										7
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										7
									
								
								go.sum
								
								
								
								
							|  | @ -101,8 +101,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me | ||||||
| github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | ||||||
| github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | ||||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||||
| github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= | github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= | ||||||
| github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= | github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= | ||||||
| github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||||
| github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||||
| github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= | ||||||
|  | @ -147,8 +147,9 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ | ||||||
| github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
| github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= |  | ||||||
| github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
|  | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||||||
|  | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||||||
| github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
| github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= | ||||||
| github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -1,6 +1,6 @@ | ||||||
| // Go support for leveled logs, analogous to https://code.google.com/p/google-glog/
 | // Go support for leveled logs, analogous to https://github.com/google/glog.
 | ||||||
| //
 | //
 | ||||||
| // Copyright 2013 Google Inc. All Rights Reserved.
 | // Copyright 2023 Google Inc. All Rights Reserved.
 | ||||||
| //
 | //
 | ||||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||||
| // you may not use this file except in compliance with the License.
 | // you may not use this file except in compliance with the License.
 | ||||||
|  | @ -19,26 +19,33 @@ | ||||||
| package glog | package glog | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bufio" | ||||||
|  | 	"bytes" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"flag" | 	"flag" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"io" | ||||||
| 	"os" | 	"os" | ||||||
| 	"os/user" |  | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
|  | 	"runtime" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"sync" | 	"sync" | ||||||
| 	"time" | 	"time" | ||||||
| ) |  | ||||||
| 
 | 
 | ||||||
| // MaxSize is the maximum size of a log file in bytes.
 | 	"github.com/golang/glog/internal/logsink" | ||||||
| var MaxSize uint64 = 1024 * 1024 * 1800 | ) | ||||||
| 
 | 
 | ||||||
| // logDirs lists the candidate directories for new log files.
 | // logDirs lists the candidate directories for new log files.
 | ||||||
| var logDirs []string | var logDirs []string | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
| 	// If non-empty, overrides the choice of directory in which to write logs.
 | 	// If non-empty, overrides the choice of directory in which to write logs.
 | ||||||
| 	// See createLogDirs for the full list of possible destinations.
 | 	// See createLogDirs for the full list of possible destinations.
 | ||||||
| var logDir = flag.String("log_dir", "", "If non-empty, write log files in this directory") | 	logDir      = flag.String("log_dir", "", "If non-empty, write log files in this directory") | ||||||
|  | 	logLink     = flag.String("log_link", "", "If non-empty, add symbolic links in this directory to the log files") | ||||||
|  | 	logBufLevel = flag.Int("logbuflevel", int(logsink.Info), "Buffer log messages logged at this level or lower"+ | ||||||
|  | 		" (-1 means don't buffer; 0 means buffer INFO only; ...). Has limited applicability on non-prod platforms.") | ||||||
|  | ) | ||||||
| 
 | 
 | ||||||
| func createLogDirs() { | func createLogDirs() { | ||||||
| 	if *logDir != "" { | 	if *logDir != "" { | ||||||
|  | @ -60,13 +67,20 @@ func init() { | ||||||
| 		host = shortHostname(h) | 		host = shortHostname(h) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	current, err := user.Current() | 	if u := lookupUser(); u != "" { | ||||||
| 	if err == nil { | 		userName = u | ||||||
| 		userName = current.Username |  | ||||||
| 	} | 	} | ||||||
| 
 | 	// Sanitize userName since it is used to construct file paths.
 | ||||||
| 	// Sanitize userName since it may contain filepath separators on Windows.
 | 	userName = strings.Map(func(r rune) rune { | ||||||
| 	userName = strings.Replace(userName, `\`, "_", -1) | 		switch { | ||||||
|  | 		case r >= 'a' && r <= 'z': | ||||||
|  | 		case r >= 'A' && r <= 'Z': | ||||||
|  | 		case r >= '0' && r <= '9': | ||||||
|  | 		default: | ||||||
|  | 			return '_' | ||||||
|  | 		} | ||||||
|  | 		return r | ||||||
|  | 	}, userName) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // shortHostname returns its argument, truncating at the first period.
 | // shortHostname returns its argument, truncating at the first period.
 | ||||||
|  | @ -102,23 +116,334 @@ var onceLogDirs sync.Once | ||||||
| // contains tag ("INFO", "FATAL", etc.) and t.  If the file is created
 | // contains tag ("INFO", "FATAL", etc.) and t.  If the file is created
 | ||||||
| // successfully, create also attempts to update the symlink for that tag, ignoring
 | // successfully, create also attempts to update the symlink for that tag, ignoring
 | ||||||
| // errors.
 | // errors.
 | ||||||
| func create(tag string, t time.Time) (f *os.File, filename string, err error) { | func create(tag string, t time.Time, dir string) (f *os.File, filename string, err error) { | ||||||
|  | 	if dir != "" { | ||||||
|  | 		f, name, err := createInDir(dir, tag, t) | ||||||
|  | 		if err == nil { | ||||||
|  | 			return f, name, err | ||||||
|  | 		} | ||||||
|  | 		return nil, "", fmt.Errorf("log: cannot create log: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	onceLogDirs.Do(createLogDirs) | 	onceLogDirs.Do(createLogDirs) | ||||||
| 	if len(logDirs) == 0 { | 	if len(logDirs) == 0 { | ||||||
| 		return nil, "", errors.New("log: no log dirs") | 		return nil, "", errors.New("log: no log dirs") | ||||||
| 	} | 	} | ||||||
| 	name, link := logName(tag, t) |  | ||||||
| 	var lastErr error | 	var lastErr error | ||||||
| 	for _, dir := range logDirs { | 	for _, dir := range logDirs { | ||||||
| 		fname := filepath.Join(dir, name) | 		f, name, err := createInDir(dir, tag, t) | ||||||
| 		f, err := os.Create(fname) |  | ||||||
| 		if err == nil { | 		if err == nil { | ||||||
| 			symlink := filepath.Join(dir, link) | 			return f, name, err | ||||||
| 			os.Remove(symlink)        // ignore err
 |  | ||||||
| 			os.Symlink(name, symlink) // ignore err
 |  | ||||||
| 			return f, fname, nil |  | ||||||
| 		} | 		} | ||||||
| 		lastErr = err | 		lastErr = err | ||||||
| 	} | 	} | ||||||
| 	return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr) | 	return nil, "", fmt.Errorf("log: cannot create log: %v", lastErr) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func createInDir(dir, tag string, t time.Time) (f *os.File, name string, err error) { | ||||||
|  | 	name, link := logName(tag, t) | ||||||
|  | 	fname := filepath.Join(dir, name) | ||||||
|  | 	// O_EXCL is important here, as it prevents a vulnerability. The general idea is that logs often
 | ||||||
|  | 	// live in an insecure directory (like /tmp), so an unprivileged attacker could create fname in
 | ||||||
|  | 	// advance as a symlink to a file the logging process can access, but the attacker cannot. O_EXCL
 | ||||||
|  | 	// fails the open if it already exists, thus prevent our this code from opening the existing file
 | ||||||
|  | 	// the attacker points us to.
 | ||||||
|  | 	f, err = os.OpenFile(fname, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) | ||||||
|  | 	if err == nil { | ||||||
|  | 		symlink := filepath.Join(dir, link) | ||||||
|  | 		os.Remove(symlink)        // ignore err
 | ||||||
|  | 		os.Symlink(name, symlink) // ignore err
 | ||||||
|  | 		if *logLink != "" { | ||||||
|  | 			lsymlink := filepath.Join(*logLink, link) | ||||||
|  | 			os.Remove(lsymlink)         // ignore err
 | ||||||
|  | 			os.Symlink(fname, lsymlink) // ignore err
 | ||||||
|  | 		} | ||||||
|  | 		return f, fname, nil | ||||||
|  | 	} | ||||||
|  | 	return nil, "", err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // flushSyncWriter is the interface satisfied by logging destinations.
 | ||||||
|  | type flushSyncWriter interface { | ||||||
|  | 	Flush() error | ||||||
|  | 	Sync() error | ||||||
|  | 	io.Writer | ||||||
|  | 	filenames() []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | var sinks struct { | ||||||
|  | 	stderr stderrSink | ||||||
|  | 	file   fileSink | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func init() { | ||||||
|  | 	// Register stderr first: that way if we crash during file-writing at least
 | ||||||
|  | 	// the log will have gone somewhere.
 | ||||||
|  | 	if shouldRegisterStderrSink() { | ||||||
|  | 		logsink.TextSinks = append(logsink.TextSinks, &sinks.stderr) | ||||||
|  | 	} | ||||||
|  | 	logsink.TextSinks = append(logsink.TextSinks, &sinks.file) | ||||||
|  | 
 | ||||||
|  | 	sinks.file.flushChan = make(chan logsink.Severity, 1) | ||||||
|  | 	go sinks.file.flushDaemon() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // stderrSink is a logsink.Text that writes log entries to stderr
 | ||||||
|  | // if they meet certain conditions.
 | ||||||
|  | type stderrSink struct { | ||||||
|  | 	mu sync.Mutex | ||||||
|  | 	w  io.Writer // if nil Emit uses os.Stderr directly
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Enabled implements logsink.Text.Enabled.  It returns true if any of the
 | ||||||
|  | // various stderr flags are enabled for logs of the given severity, if the log
 | ||||||
|  | // message is from the standard "log" package, or if google.Init has not yet run
 | ||||||
|  | // (and hence file logging is not yet initialized).
 | ||||||
|  | func (s *stderrSink) Enabled(m *logsink.Meta) bool { | ||||||
|  | 	return toStderr || alsoToStderr || m.Severity >= stderrThreshold.get() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Emit implements logsink.Text.Emit.
 | ||||||
|  | func (s *stderrSink) Emit(m *logsink.Meta, data []byte) (n int, err error) { | ||||||
|  | 	s.mu.Lock() | ||||||
|  | 	defer s.mu.Unlock() | ||||||
|  | 	w := s.w | ||||||
|  | 	if w == nil { | ||||||
|  | 		w = os.Stderr | ||||||
|  | 	} | ||||||
|  | 	dn, err := w.Write(data) | ||||||
|  | 	n += dn | ||||||
|  | 	return n, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // severityWriters is an array of flushSyncWriter with a value for each
 | ||||||
|  | // logsink.Severity.
 | ||||||
|  | type severityWriters [4]flushSyncWriter | ||||||
|  | 
 | ||||||
|  | // fileSink is a logsink.Text that prints to a set of Google log files.
 | ||||||
|  | type fileSink struct { | ||||||
|  | 	mu sync.Mutex | ||||||
|  | 	// file holds writer for each of the log types.
 | ||||||
|  | 	file      severityWriters | ||||||
|  | 	flushChan chan logsink.Severity | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Enabled implements logsink.Text.Enabled.  It returns true if google.Init
 | ||||||
|  | // has run and both --disable_log_to_disk and --logtostderr are false.
 | ||||||
|  | func (s *fileSink) Enabled(m *logsink.Meta) bool { | ||||||
|  | 	return !toStderr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Emit implements logsink.Text.Emit
 | ||||||
|  | func (s *fileSink) Emit(m *logsink.Meta, data []byte) (n int, err error) { | ||||||
|  | 	s.mu.Lock() | ||||||
|  | 	defer s.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	if err = s.createMissingFiles(m.Severity); err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	for sev := m.Severity; sev >= logsink.Info; sev-- { | ||||||
|  | 		if _, fErr := s.file[sev].Write(data); fErr != nil && err == nil { | ||||||
|  | 			err = fErr // Take the first error.
 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	n = len(data) | ||||||
|  | 	if int(m.Severity) > *logBufLevel { | ||||||
|  | 		select { | ||||||
|  | 		case s.flushChan <- m.Severity: | ||||||
|  | 		default: | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return n, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // syncBuffer joins a bufio.Writer to its underlying file, providing access to the
 | ||||||
|  | // file's Sync method and providing a wrapper for the Write method that provides log
 | ||||||
|  | // file rotation. There are conflicting methods, so the file cannot be embedded.
 | ||||||
|  | // s.mu is held for all its methods.
 | ||||||
|  | type syncBuffer struct { | ||||||
|  | 	sink *fileSink | ||||||
|  | 	*bufio.Writer | ||||||
|  | 	file   *os.File | ||||||
|  | 	names  []string | ||||||
|  | 	sev    logsink.Severity | ||||||
|  | 	nbytes uint64 // The number of bytes written to this file
 | ||||||
|  | 	madeAt time.Time | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sb *syncBuffer) Sync() error { | ||||||
|  | 	return sb.file.Sync() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sb *syncBuffer) Write(p []byte) (n int, err error) { | ||||||
|  | 	// Rotate the file if it is too large, but ensure we only do so,
 | ||||||
|  | 	// if rotate doesn't create a conflicting filename.
 | ||||||
|  | 	if sb.nbytes+uint64(len(p)) >= MaxSize { | ||||||
|  | 		now := timeNow() | ||||||
|  | 		if now.After(sb.madeAt.Add(1*time.Second)) || now.Second() != sb.madeAt.Second() { | ||||||
|  | 			if err := sb.rotateFile(now); err != nil { | ||||||
|  | 				return 0, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	n, err = sb.Writer.Write(p) | ||||||
|  | 	sb.nbytes += uint64(n) | ||||||
|  | 	return n, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (sb *syncBuffer) filenames() []string { | ||||||
|  | 	return sb.names | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const footer = "\nCONTINUED IN NEXT FILE\n" | ||||||
|  | 
 | ||||||
|  | // rotateFile closes the syncBuffer's file and starts a new one.
 | ||||||
|  | func (sb *syncBuffer) rotateFile(now time.Time) error { | ||||||
|  | 	var err error | ||||||
|  | 	pn := "<none>" | ||||||
|  | 	file, name, err := create(sb.sev.String(), now, "") | ||||||
|  | 	sb.madeAt = now | ||||||
|  | 
 | ||||||
|  | 	if sb.file != nil { | ||||||
|  | 		// The current log file becomes the previous log at the end of
 | ||||||
|  | 		// this block, so save its name for use in the header of the next
 | ||||||
|  | 		// file.
 | ||||||
|  | 		pn = sb.file.Name() | ||||||
|  | 		sb.Flush() | ||||||
|  | 		// If there's an existing file, write a footer with the name of
 | ||||||
|  | 		// the next file in the chain, followed by the constant string
 | ||||||
|  | 		// \nCONTINUED IN NEXT FILE\n to make continuation detection simple.
 | ||||||
|  | 		sb.file.Write([]byte("Next log: ")) | ||||||
|  | 		sb.file.Write([]byte(name)) | ||||||
|  | 		sb.file.Write([]byte(footer)) | ||||||
|  | 		sb.file.Close() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sb.file = file | ||||||
|  | 	sb.names = append(sb.names, name) | ||||||
|  | 	sb.nbytes = 0 | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sb.Writer = bufio.NewWriterSize(sb.file, bufferSize) | ||||||
|  | 
 | ||||||
|  | 	// Write header.
 | ||||||
|  | 	var buf bytes.Buffer | ||||||
|  | 	fmt.Fprintf(&buf, "Log file created at: %s\n", now.Format("2006/01/02 15:04:05")) | ||||||
|  | 	fmt.Fprintf(&buf, "Running on machine: %s\n", host) | ||||||
|  | 	fmt.Fprintf(&buf, "Binary: Built with %s %s for %s/%s\n", runtime.Compiler, runtime.Version(), runtime.GOOS, runtime.GOARCH) | ||||||
|  | 	fmt.Fprintf(&buf, "Previous log: %s\n", pn) | ||||||
|  | 	fmt.Fprintf(&buf, "Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg\n") | ||||||
|  | 	n, err := sb.file.Write(buf.Bytes()) | ||||||
|  | 	sb.nbytes += uint64(n) | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // bufferSize sizes the buffer associated with each log file. It's large
 | ||||||
|  | // so that log records can accumulate without the logging thread blocking
 | ||||||
|  | // on disk I/O. The flushDaemon will block instead.
 | ||||||
|  | const bufferSize = 256 * 1024 | ||||||
|  | 
 | ||||||
|  | // createMissingFiles creates all the log files for severity from infoLog up to
 | ||||||
|  | // upTo that have not already been created.
 | ||||||
|  | // s.mu is held.
 | ||||||
|  | func (s *fileSink) createMissingFiles(upTo logsink.Severity) error { | ||||||
|  | 	if s.file[upTo] != nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	now := time.Now() | ||||||
|  | 	// Files are created in increasing severity order, so we can be assured that
 | ||||||
|  | 	// if a high severity logfile exists, then so do all of lower severity.
 | ||||||
|  | 	for sev := logsink.Info; sev <= upTo; sev++ { | ||||||
|  | 		if s.file[sev] != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		sb := &syncBuffer{ | ||||||
|  | 			sink: s, | ||||||
|  | 			sev:  sev, | ||||||
|  | 		} | ||||||
|  | 		if err := sb.rotateFile(now); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		s.file[sev] = sb | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // flushDaemon periodically flushes the log file buffers.
 | ||||||
|  | func (s *fileSink) flushDaemon() { | ||||||
|  | 	tick := time.NewTicker(30 * time.Second) | ||||||
|  | 	defer tick.Stop() | ||||||
|  | 	for { | ||||||
|  | 		select { | ||||||
|  | 		case <-tick.C: | ||||||
|  | 			s.Flush() | ||||||
|  | 		case sev := <-s.flushChan: | ||||||
|  | 			s.flush(sev) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Flush flushes all pending log I/O.
 | ||||||
|  | func Flush() { | ||||||
|  | 	sinks.file.Flush() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Flush flushes all the logs and attempts to "sync" their data to disk.
 | ||||||
|  | func (s *fileSink) Flush() error { | ||||||
|  | 	return s.flush(logsink.Info) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // flush flushes all logs of severity threshold or greater.
 | ||||||
|  | func (s *fileSink) flush(threshold logsink.Severity) error { | ||||||
|  | 	var firstErr error | ||||||
|  | 	updateErr := func(err error) { | ||||||
|  | 		if err != nil && firstErr == nil { | ||||||
|  | 			firstErr = err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Remember where we flushed, so we can call sync without holding
 | ||||||
|  | 	// the lock.
 | ||||||
|  | 	var files []flushSyncWriter | ||||||
|  | 	func() { | ||||||
|  | 		s.mu.Lock() | ||||||
|  | 		defer s.mu.Unlock() | ||||||
|  | 		// Flush from fatal down, in case there's trouble flushing.
 | ||||||
|  | 		for sev := logsink.Fatal; sev >= threshold; sev-- { | ||||||
|  | 			if file := s.file[sev]; file != nil { | ||||||
|  | 				updateErr(file.Flush()) | ||||||
|  | 				files = append(files, file) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 
 | ||||||
|  | 	for _, file := range files { | ||||||
|  | 		updateErr(file.Sync()) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return firstErr | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Names returns the names of the log files holding the FATAL, ERROR,
 | ||||||
|  | // WARNING, or INFO logs. Returns ErrNoLog if the log for the given
 | ||||||
|  | // level doesn't exist (e.g. because no messages of that level have been
 | ||||||
|  | // written). This may return multiple names if the log type requested
 | ||||||
|  | // has rolled over.
 | ||||||
|  | func Names(s string) ([]string, error) { | ||||||
|  | 	severity, err := logsink.ParseSeverity(s) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	sinks.file.mu.Lock() | ||||||
|  | 	defer sinks.file.mu.Unlock() | ||||||
|  | 	f := sinks.file.file[severity] | ||||||
|  | 	if f == nil { | ||||||
|  | 		return nil, ErrNoLog | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return f.filenames(), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ | ||||||
| // Package cmp determines equality of values.
 | // Package cmp determines equality of values.
 | ||||||
| //
 | //
 | ||||||
| // This package is intended to be a more powerful and safer alternative to
 | // This package is intended to be a more powerful and safer alternative to
 | ||||||
| // reflect.DeepEqual for comparing whether two values are semantically equal.
 | // [reflect.DeepEqual] for comparing whether two values are semantically equal.
 | ||||||
| // It is intended to only be used in tests, as performance is not a goal and
 | // It is intended to only be used in tests, as performance is not a goal and
 | ||||||
| // it may panic if it cannot compare the values. Its propensity towards
 | // it may panic if it cannot compare the values. Its propensity towards
 | ||||||
| // panicking means that its unsuitable for production environments where a
 | // panicking means that its unsuitable for production environments where a
 | ||||||
|  | @ -13,21 +13,22 @@ | ||||||
| //
 | //
 | ||||||
| // The primary features of cmp are:
 | // The primary features of cmp are:
 | ||||||
| //
 | //
 | ||||||
| // • When the default behavior of equality does not suit the needs of the test,
 | //   - When the default behavior of equality does not suit the test's needs,
 | ||||||
| //     custom equality functions can override the equality operation.
 | //     custom equality functions can override the equality operation.
 | ||||||
| // For example, an equality function may report floats as equal so long as they
 | //     For example, an equality function may report floats as equal so long as
 | ||||||
| // are within some tolerance of each other.
 | //     they are within some tolerance of each other.
 | ||||||
| //
 | //
 | ||||||
| // • Types that have an Equal method may use that method to determine equality.
 | //   - Types with an Equal method (e.g., [time.Time.Equal]) may use that method
 | ||||||
| // This allows package authors to determine the equality operation for the types
 | //     to determine equality. This allows package authors to determine
 | ||||||
| // that they define.
 | //     the equality operation for the types that they define.
 | ||||||
| //
 | //
 | ||||||
| // • If no custom equality functions are used and no Equal method is defined,
 | //   - If no custom equality functions are used and no Equal method is defined,
 | ||||||
| // equality is determined by recursively comparing the primitive kinds on both
 | //     equality is determined by recursively comparing the primitive kinds on
 | ||||||
| // values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
 | //     both values, much like [reflect.DeepEqual]. Unlike [reflect.DeepEqual],
 | ||||||
| // fields are not compared by default; they result in panics unless suppressed
 | //     unexported fields are not compared by default; they result in panics
 | ||||||
| // by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly
 | //     unless suppressed by using an [Ignore] option
 | ||||||
| // compared using the Exporter option.
 | //     (see [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported])
 | ||||||
|  | //     or explicitly compared using the [Exporter] option.
 | ||||||
| package cmp | package cmp | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | @ -36,50 +37,52 @@ import ( | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/google/go-cmp/cmp/internal/diff" | 	"github.com/google/go-cmp/cmp/internal/diff" | ||||||
| 	"github.com/google/go-cmp/cmp/internal/flags" |  | ||||||
| 	"github.com/google/go-cmp/cmp/internal/function" | 	"github.com/google/go-cmp/cmp/internal/function" | ||||||
| 	"github.com/google/go-cmp/cmp/internal/value" | 	"github.com/google/go-cmp/cmp/internal/value" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // TODO(≥go1.18): Use any instead of interface{}.
 | ||||||
|  | 
 | ||||||
| // Equal reports whether x and y are equal by recursively applying the
 | // Equal reports whether x and y are equal by recursively applying the
 | ||||||
| // following rules in the given order to x and y and all of their sub-values:
 | // following rules in the given order to x and y and all of their sub-values:
 | ||||||
| //
 | //
 | ||||||
| // • Let S be the set of all Ignore, Transformer, and Comparer options that
 | //   - Let S be the set of all [Ignore], [Transformer], and [Comparer] options that
 | ||||||
| //     remain after applying all path filters, value filters, and type filters.
 | //     remain after applying all path filters, value filters, and type filters.
 | ||||||
| // If at least one Ignore exists in S, then the comparison is ignored.
 | //     If at least one [Ignore] exists in S, then the comparison is ignored.
 | ||||||
| // If the number of Transformer and Comparer options in S is greater than one,
 | //     If the number of [Transformer] and [Comparer] options in S is non-zero,
 | ||||||
| //     then Equal panics because it is ambiguous which option to use.
 | //     then Equal panics because it is ambiguous which option to use.
 | ||||||
| // If S contains a single Transformer, then use that to transform the current
 | //     If S contains a single [Transformer], then use that to transform
 | ||||||
| // values and recursively call Equal on the output values.
 | //     the current values and recursively call Equal on the output values.
 | ||||||
| // If S contains a single Comparer, then use that to compare the current values.
 | //     If S contains a single [Comparer], then use that to compare the current values.
 | ||||||
| //     Otherwise, evaluation proceeds to the next rule.
 | //     Otherwise, evaluation proceeds to the next rule.
 | ||||||
| //
 | //
 | ||||||
| // • If the values have an Equal method of the form "(T) Equal(T) bool" or
 | //   - If the values have an Equal method of the form "(T) Equal(T) bool" or
 | ||||||
| //     "(T) Equal(I) bool" where T is assignable to I, then use the result of
 | //     "(T) Equal(I) bool" where T is assignable to I, then use the result of
 | ||||||
| //     x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
 | //     x.Equal(y) even if x or y is nil. Otherwise, no such method exists and
 | ||||||
| //     evaluation proceeds to the next rule.
 | //     evaluation proceeds to the next rule.
 | ||||||
| //
 | //
 | ||||||
| // • Lastly, try to compare x and y based on their basic kinds.
 | //   - Lastly, try to compare x and y based on their basic kinds.
 | ||||||
| // Simple kinds like booleans, integers, floats, complex numbers, strings, and
 | //     Simple kinds like booleans, integers, floats, complex numbers, strings,
 | ||||||
| // channels are compared using the equivalent of the == operator in Go.
 | //     and channels are compared using the equivalent of the == operator in Go.
 | ||||||
| //     Functions are only equal if they are both nil, otherwise they are unequal.
 | //     Functions are only equal if they are both nil, otherwise they are unequal.
 | ||||||
| //
 | //
 | ||||||
| // Structs are equal if recursively calling Equal on all fields report equal.
 | // Structs are equal if recursively calling Equal on all fields report equal.
 | ||||||
| // If a struct contains unexported fields, Equal panics unless an Ignore option
 | // If a struct contains unexported fields, Equal panics unless an [Ignore] option
 | ||||||
| // (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option
 | // (e.g., [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]) ignores that field
 | ||||||
| // explicitly permits comparing the unexported field.
 | // or the [Exporter] option explicitly permits comparing the unexported field.
 | ||||||
| //
 | //
 | ||||||
| // Slices are equal if they are both nil or both non-nil, where recursively
 | // Slices are equal if they are both nil or both non-nil, where recursively
 | ||||||
| // calling Equal on all non-ignored slice or array elements report equal.
 | // calling Equal on all non-ignored slice or array elements report equal.
 | ||||||
| // Empty non-nil slices and nil slices are not equal; to equate empty slices,
 | // Empty non-nil slices and nil slices are not equal; to equate empty slices,
 | ||||||
| // consider using cmpopts.EquateEmpty.
 | // consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty].
 | ||||||
| //
 | //
 | ||||||
| // Maps are equal if they are both nil or both non-nil, where recursively
 | // Maps are equal if they are both nil or both non-nil, where recursively
 | ||||||
| // calling Equal on all non-ignored map entries report equal.
 | // calling Equal on all non-ignored map entries report equal.
 | ||||||
| // Map keys are equal according to the == operator.
 | // Map keys are equal according to the == operator.
 | ||||||
| // To use custom comparisons for map keys, consider using cmpopts.SortMaps.
 | // To use custom comparisons for map keys, consider using
 | ||||||
|  | // [github.com/google/go-cmp/cmp/cmpopts.SortMaps].
 | ||||||
| // Empty non-nil maps and nil maps are not equal; to equate empty maps,
 | // Empty non-nil maps and nil maps are not equal; to equate empty maps,
 | ||||||
| // consider using cmpopts.EquateEmpty.
 | // consider using [github.com/google/go-cmp/cmp/cmpopts.EquateEmpty].
 | ||||||
| //
 | //
 | ||||||
| // Pointers and interfaces are equal if they are both nil or both non-nil,
 | // Pointers and interfaces are equal if they are both nil or both non-nil,
 | ||||||
| // where they have the same underlying concrete type and recursively
 | // where they have the same underlying concrete type and recursively
 | ||||||
|  | @ -143,7 +146,7 @@ func rootStep(x, y interface{}) PathStep { | ||||||
| 	// so that they have the same parent type.
 | 	// so that they have the same parent type.
 | ||||||
| 	var t reflect.Type | 	var t reflect.Type | ||||||
| 	if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() { | 	if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() { | ||||||
| 		t = reflect.TypeOf((*interface{})(nil)).Elem() | 		t = anyType | ||||||
| 		if vx.IsValid() { | 		if vx.IsValid() { | ||||||
| 			vvx := reflect.New(t).Elem() | 			vvx := reflect.New(t).Elem() | ||||||
| 			vvx.Set(vx) | 			vvx.Set(vx) | ||||||
|  | @ -319,7 +322,6 @@ func (s *state) tryMethod(t reflect.Type, vx, vy reflect.Value) bool { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value { | func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value { | ||||||
| 	v = sanitizeValue(v, f.Type().In(0)) |  | ||||||
| 	if !s.dynChecker.Next() { | 	if !s.dynChecker.Next() { | ||||||
| 		return f.Call([]reflect.Value{v})[0] | 		return f.Call([]reflect.Value{v})[0] | ||||||
| 	} | 	} | ||||||
|  | @ -343,8 +345,6 @@ func (s *state) callTRFunc(f, v reflect.Value, step Transform) reflect.Value { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (s *state) callTTBFunc(f, x, y reflect.Value) bool { | func (s *state) callTTBFunc(f, x, y reflect.Value) bool { | ||||||
| 	x = sanitizeValue(x, f.Type().In(0)) |  | ||||||
| 	y = sanitizeValue(y, f.Type().In(1)) |  | ||||||
| 	if !s.dynChecker.Next() { | 	if !s.dynChecker.Next() { | ||||||
| 		return f.Call([]reflect.Value{x, y})[0].Bool() | 		return f.Call([]reflect.Value{x, y})[0].Bool() | ||||||
| 	} | 	} | ||||||
|  | @ -372,19 +372,6 @@ func detectRaces(c chan<- reflect.Value, f reflect.Value, vs ...reflect.Value) { | ||||||
| 	ret = f.Call(vs)[0] | 	ret = f.Call(vs)[0] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // sanitizeValue converts nil interfaces of type T to those of type R,
 |  | ||||||
| // assuming that T is assignable to R.
 |  | ||||||
| // Otherwise, it returns the input value as is.
 |  | ||||||
| func sanitizeValue(v reflect.Value, t reflect.Type) reflect.Value { |  | ||||||
| 	// TODO(≥go1.10): Workaround for reflect bug (https://golang.org/issue/22143).
 |  | ||||||
| 	if !flags.AtLeastGo110 { |  | ||||||
| 		if v.Kind() == reflect.Interface && v.IsNil() && v.Type() != t { |  | ||||||
| 			return reflect.New(t).Elem() |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return v |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) { | func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) { | ||||||
| 	var addr bool | 	var addr bool | ||||||
| 	var vax, vay reflect.Value // Addressable versions of vx and vy
 | 	var vax, vay reflect.Value // Addressable versions of vx and vy
 | ||||||
|  | @ -654,7 +641,9 @@ type dynChecker struct{ curr, next int } | ||||||
| // Next increments the state and reports whether a check should be performed.
 | // Next increments the state and reports whether a check should be performed.
 | ||||||
| //
 | //
 | ||||||
| // Checks occur every Nth function call, where N is a triangular number:
 | // Checks occur every Nth function call, where N is a triangular number:
 | ||||||
|  | //
 | ||||||
| //	0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
 | //	0 1 3 6 10 15 21 28 36 45 55 66 78 91 105 120 136 153 171 190 ...
 | ||||||
|  | //
 | ||||||
| // See https://en.wikipedia.org/wiki/Triangular_number
 | // See https://en.wikipedia.org/wiki/Triangular_number
 | ||||||
| //
 | //
 | ||||||
| // This sequence ensures that the cost of checks drops significantly as
 | // This sequence ensures that the cost of checks drops significantly as
 | ||||||
|  |  | ||||||
|  | @ -1,15 +0,0 @@ | ||||||
| // Copyright 2017, The Go Authors. All rights reserved.
 |  | ||||||
| // Use of this source code is governed by a BSD-style
 |  | ||||||
| // license that can be found in the LICENSE file.
 |  | ||||||
| 
 |  | ||||||
| // +build purego
 |  | ||||||
| 
 |  | ||||||
| package cmp |  | ||||||
| 
 |  | ||||||
| import "reflect" |  | ||||||
| 
 |  | ||||||
| const supportExporters = false |  | ||||||
| 
 |  | ||||||
| func retrieveUnexportedField(reflect.Value, reflect.StructField, bool) reflect.Value { |  | ||||||
| 	panic("no support for forcibly accessing unexported fields") |  | ||||||
| } |  | ||||||
|  | @ -1,35 +0,0 @@ | ||||||
| // Copyright 2017, The Go Authors. All rights reserved.
 |  | ||||||
| // Use of this source code is governed by a BSD-style
 |  | ||||||
| // license that can be found in the LICENSE file.
 |  | ||||||
| 
 |  | ||||||
| // +build !purego
 |  | ||||||
| 
 |  | ||||||
| package cmp |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"reflect" |  | ||||||
| 	"unsafe" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| const supportExporters = true |  | ||||||
| 
 |  | ||||||
| // retrieveUnexportedField uses unsafe to forcibly retrieve any field from
 |  | ||||||
| // a struct such that the value has read-write permissions.
 |  | ||||||
| //
 |  | ||||||
| // The parent struct, v, must be addressable, while f must be a StructField
 |  | ||||||
| // describing the field to retrieve. If addr is false,
 |  | ||||||
| // then the returned value will be shallowed copied to be non-addressable.
 |  | ||||||
| func retrieveUnexportedField(v reflect.Value, f reflect.StructField, addr bool) reflect.Value { |  | ||||||
| 	ve := reflect.NewAt(f.Type, unsafe.Pointer(uintptr(unsafe.Pointer(v.UnsafeAddr()))+f.Offset)).Elem() |  | ||||||
| 	if !addr { |  | ||||||
| 		// A field is addressable if and only if the struct is addressable.
 |  | ||||||
| 		// If the original parent value was not addressable, shallow copy the
 |  | ||||||
| 		// value to make it non-addressable to avoid leaking an implementation
 |  | ||||||
| 		// detail of how forcibly exporting a field works.
 |  | ||||||
| 		if ve.Kind() == reflect.Interface && ve.IsNil() { |  | ||||||
| 			return reflect.Zero(f.Type) |  | ||||||
| 		} |  | ||||||
| 		return reflect.ValueOf(ve.Interface()).Convert(f.Type) |  | ||||||
| 	} |  | ||||||
| 	return ve |  | ||||||
| } |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| // Use of this source code is governed by a BSD-style
 | // Use of this source code is governed by a BSD-style
 | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
|  | //go:build !cmp_debug
 | ||||||
| // +build !cmp_debug
 | // +build !cmp_debug
 | ||||||
| 
 | 
 | ||||||
| package diff | package diff | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| // Use of this source code is governed by a BSD-style
 | // Use of this source code is governed by a BSD-style
 | ||||||
| // license that can be found in the LICENSE file.
 | // license that can be found in the LICENSE file.
 | ||||||
| 
 | 
 | ||||||
|  | //go:build cmp_debug
 | ||||||
| // +build cmp_debug
 | // +build cmp_debug
 | ||||||
| 
 | 
 | ||||||
| package diff | package diff | ||||||
|  |  | ||||||
|  | @ -127,9 +127,9 @@ var randBool = rand.New(rand.NewSource(time.Now().Unix())).Intn(2) == 0 | ||||||
| // This function returns an edit-script, which is a sequence of operations
 | // This function returns an edit-script, which is a sequence of operations
 | ||||||
| // needed to convert one list into the other. The following invariants for
 | // needed to convert one list into the other. The following invariants for
 | ||||||
| // the edit-script are maintained:
 | // the edit-script are maintained:
 | ||||||
| //	• eq == (es.Dist()==0)
 | //   - eq == (es.Dist()==0)
 | ||||||
| //	• nx == es.LenX()
 | //   - nx == es.LenX()
 | ||||||
| //	• ny == es.LenY()
 | //   - ny == es.LenY()
 | ||||||
| //
 | //
 | ||||||
| // This algorithm is not guaranteed to be an optimal solution (i.e., one that
 | // This algorithm is not guaranteed to be an optimal solution (i.e., one that
 | ||||||
| // produces an edit-script with a minimal Levenshtein distance). This algorithm
 | // produces an edit-script with a minimal Levenshtein distance). This algorithm
 | ||||||
|  | @ -169,12 +169,13 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) { | ||||||
| 	// A diagonal edge is equivalent to a matching symbol between both X and Y.
 | 	// A diagonal edge is equivalent to a matching symbol between both X and Y.
 | ||||||
| 
 | 
 | ||||||
| 	// Invariants:
 | 	// Invariants:
 | ||||||
| 	//	• 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
 | 	//   - 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx
 | ||||||
| 	//	• 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
 | 	//   - 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// In general:
 | 	// In general:
 | ||||||
| 	//	• fwdFrontier.X < revFrontier.X
 | 	//   - fwdFrontier.X < revFrontier.X
 | ||||||
| 	//	• fwdFrontier.Y < revFrontier.Y
 | 	//   - fwdFrontier.Y < revFrontier.Y
 | ||||||
|  | 	//
 | ||||||
| 	// Unless, it is time for the algorithm to terminate.
 | 	// Unless, it is time for the algorithm to terminate.
 | ||||||
| 	fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)} | 	fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)} | ||||||
| 	revPath := path{-1, point{nx, ny}, make(EditScript, 0)} | 	revPath := path{-1, point{nx, ny}, make(EditScript, 0)} | ||||||
|  | @ -195,18 +196,20 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) { | ||||||
| 	// computing sub-optimal edit-scripts between two lists.
 | 	// computing sub-optimal edit-scripts between two lists.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// The algorithm is approximately as follows:
 | 	// The algorithm is approximately as follows:
 | ||||||
| 	//	• Searching for differences switches back-and-forth between
 | 	//   - Searching for differences switches back-and-forth between
 | ||||||
| 	//     a search that starts at the beginning (the top-left corner), and
 | 	//     a search that starts at the beginning (the top-left corner), and
 | ||||||
| 	//	a search that starts at the end (the bottom-right corner). The goal of
 | 	//     a search that starts at the end (the bottom-right corner).
 | ||||||
| 	//	the search is connect with the search from the opposite corner.
 | 	//     The goal of the search is connect with the search
 | ||||||
| 	//	• As we search, we build a path in a greedy manner, where the first
 | 	//     from the opposite corner.
 | ||||||
| 	//	match seen is added to the path (this is sub-optimal, but provides a
 | 	//   - As we search, we build a path in a greedy manner,
 | ||||||
| 	//	decent result in practice). When matches are found, we try the next pair
 | 	//     where the first match seen is added to the path (this is sub-optimal,
 | ||||||
| 	//	of symbols in the lists and follow all matches as far as possible.
 | 	//     but provides a decent result in practice). When matches are found,
 | ||||||
| 	//	• When searching for matches, we search along a diagonal going through
 | 	//     we try the next pair of symbols in the lists and follow all matches
 | ||||||
| 	//	through the "frontier" point. If no matches are found, we advance the
 | 	//     as far as possible.
 | ||||||
| 	//	frontier towards the opposite corner.
 | 	//   - When searching for matches, we search along a diagonal going through
 | ||||||
| 	//	• This algorithm terminates when either the X coordinates or the
 | 	//     through the "frontier" point. If no matches are found,
 | ||||||
|  | 	//     we advance the frontier towards the opposite corner.
 | ||||||
|  | 	//   - This algorithm terminates when either the X coordinates or the
 | ||||||
| 	//     Y coordinates of the forward and reverse frontier points ever intersect.
 | 	//     Y coordinates of the forward and reverse frontier points ever intersect.
 | ||||||
| 
 | 
 | ||||||
| 	// This algorithm is correct even if searching only in the forward direction
 | 	// This algorithm is correct even if searching only in the forward direction
 | ||||||
|  | @ -389,6 +392,7 @@ type point struct{ X, Y int } | ||||||
| func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy } | func (p *point) add(dx, dy int) { p.X += dx; p.Y += dy } | ||||||
| 
 | 
 | ||||||
| // zigzag maps a consecutive sequence of integers to a zig-zag sequence.
 | // zigzag maps a consecutive sequence of integers to a zig-zag sequence.
 | ||||||
|  | //
 | ||||||
| //	[0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
 | //	[0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...]
 | ||||||
| func zigzag(x int) int { | func zigzag(x int) int { | ||||||
| 	if x&1 != 0 { | 	if x&1 != 0 { | ||||||
|  |  | ||||||
|  | @ -1,10 +0,0 @@ | ||||||
| // Copyright 2019, The Go Authors. All rights reserved.
 |  | ||||||
| // Use of this source code is governed by a BSD-style
 |  | ||||||
| // license that can be found in the LICENSE file.
 |  | ||||||
| 
 |  | ||||||
| // +build !go1.10
 |  | ||||||
| 
 |  | ||||||
| package flags |  | ||||||
| 
 |  | ||||||
| // AtLeastGo110 reports whether the Go toolchain is at least Go 1.10.
 |  | ||||||
| const AtLeastGo110 = false |  | ||||||
|  | @ -1,10 +0,0 @@ | ||||||
| // Copyright 2019, The Go Authors. All rights reserved.
 |  | ||||||
| // Use of this source code is governed by a BSD-style
 |  | ||||||
| // license that can be found in the LICENSE file.
 |  | ||||||
| 
 |  | ||||||
| // +build go1.10
 |  | ||||||
| 
 |  | ||||||
| package flags |  | ||||||
| 
 |  | ||||||
| // AtLeastGo110 reports whether the Go toolchain is at least Go 1.10.
 |  | ||||||
| const AtLeastGo110 = true |  | ||||||
|  | @ -9,6 +9,8 @@ import ( | ||||||
| 	"strconv" | 	"strconv" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | var anyType = reflect.TypeOf((*interface{})(nil)).Elem() | ||||||
|  | 
 | ||||||
| // TypeString is nearly identical to reflect.Type.String,
 | // TypeString is nearly identical to reflect.Type.String,
 | ||||||
| // but has an additional option to specify that full type names be used.
 | // but has an additional option to specify that full type names be used.
 | ||||||
| func TypeString(t reflect.Type, qualified bool) string { | func TypeString(t reflect.Type, qualified bool) string { | ||||||
|  | @ -20,6 +22,11 @@ func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte | ||||||
| 	// of the same name and within the same package,
 | 	// of the same name and within the same package,
 | ||||||
| 	// but declared within the namespace of different functions.
 | 	// but declared within the namespace of different functions.
 | ||||||
| 
 | 
 | ||||||
|  | 	// Use the "any" alias instead of "interface{}" for better readability.
 | ||||||
|  | 	if t == anyType { | ||||||
|  | 		return append(b, "any"...) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	// Named type.
 | 	// Named type.
 | ||||||
| 	if t.Name() != "" { | 	if t.Name() != "" { | ||||||
| 		if qualified && t.PkgPath() != "" { | 		if qualified && t.PkgPath() != "" { | ||||||
|  |  | ||||||
|  | @ -1,33 +0,0 @@ | ||||||
| // Copyright 2018, The Go Authors. All rights reserved.
 |  | ||||||
| // Use of this source code is governed by a BSD-style
 |  | ||||||
| // license that can be found in the LICENSE file.
 |  | ||||||
| 
 |  | ||||||
| // +build purego
 |  | ||||||
| 
 |  | ||||||
| package value |  | ||||||
| 
 |  | ||||||
| import "reflect" |  | ||||||
| 
 |  | ||||||
| // Pointer is an opaque typed pointer and is guaranteed to be comparable.
 |  | ||||||
| type Pointer struct { |  | ||||||
| 	p uintptr |  | ||||||
| 	t reflect.Type |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // PointerOf returns a Pointer from v, which must be a
 |  | ||||||
| // reflect.Ptr, reflect.Slice, or reflect.Map.
 |  | ||||||
| func PointerOf(v reflect.Value) Pointer { |  | ||||||
| 	// NOTE: Storing a pointer as an uintptr is technically incorrect as it
 |  | ||||||
| 	// assumes that the GC implementation does not use a moving collector.
 |  | ||||||
| 	return Pointer{v.Pointer(), v.Type()} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // IsNil reports whether the pointer is nil.
 |  | ||||||
| func (p Pointer) IsNil() bool { |  | ||||||
| 	return p.p == 0 |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Uintptr returns the pointer as a uintptr.
 |  | ||||||
| func (p Pointer) Uintptr() uintptr { |  | ||||||
| 	return p.p |  | ||||||
| } |  | ||||||
|  | @ -1,36 +0,0 @@ | ||||||
| // Copyright 2018, The Go Authors. All rights reserved.
 |  | ||||||
| // Use of this source code is governed by a BSD-style
 |  | ||||||
| // license that can be found in the LICENSE file.
 |  | ||||||
| 
 |  | ||||||
| // +build !purego
 |  | ||||||
| 
 |  | ||||||
| package value |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"reflect" |  | ||||||
| 	"unsafe" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // Pointer is an opaque typed pointer and is guaranteed to be comparable.
 |  | ||||||
| type Pointer struct { |  | ||||||
| 	p unsafe.Pointer |  | ||||||
| 	t reflect.Type |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // PointerOf returns a Pointer from v, which must be a
 |  | ||||||
| // reflect.Ptr, reflect.Slice, or reflect.Map.
 |  | ||||||
| func PointerOf(v reflect.Value) Pointer { |  | ||||||
| 	// The proper representation of a pointer is unsafe.Pointer,
 |  | ||||||
| 	// which is necessary if the GC ever uses a moving collector.
 |  | ||||||
| 	return Pointer{unsafe.Pointer(v.Pointer()), v.Type()} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // IsNil reports whether the pointer is nil.
 |  | ||||||
| func (p Pointer) IsNil() bool { |  | ||||||
| 	return p.p == nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Uintptr returns the pointer as a uintptr.
 |  | ||||||
| func (p Pointer) Uintptr() uintptr { |  | ||||||
| 	return uintptr(p.p) |  | ||||||
| } |  | ||||||
|  | @ -1,48 +0,0 @@ | ||||||
| // Copyright 2017, The Go Authors. All rights reserved.
 |  | ||||||
| // Use of this source code is governed by a BSD-style
 |  | ||||||
| // license that can be found in the LICENSE file.
 |  | ||||||
| 
 |  | ||||||
| package value |  | ||||||
| 
 |  | ||||||
| import ( |  | ||||||
| 	"math" |  | ||||||
| 	"reflect" |  | ||||||
| ) |  | ||||||
| 
 |  | ||||||
| // IsZero reports whether v is the zero value.
 |  | ||||||
| // This does not rely on Interface and so can be used on unexported fields.
 |  | ||||||
| func IsZero(v reflect.Value) bool { |  | ||||||
| 	switch v.Kind() { |  | ||||||
| 	case reflect.Bool: |  | ||||||
| 		return v.Bool() == false |  | ||||||
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |  | ||||||
| 		return v.Int() == 0 |  | ||||||
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: |  | ||||||
| 		return v.Uint() == 0 |  | ||||||
| 	case reflect.Float32, reflect.Float64: |  | ||||||
| 		return math.Float64bits(v.Float()) == 0 |  | ||||||
| 	case reflect.Complex64, reflect.Complex128: |  | ||||||
| 		return math.Float64bits(real(v.Complex())) == 0 && math.Float64bits(imag(v.Complex())) == 0 |  | ||||||
| 	case reflect.String: |  | ||||||
| 		return v.String() == "" |  | ||||||
| 	case reflect.UnsafePointer: |  | ||||||
| 		return v.Pointer() == 0 |  | ||||||
| 	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: |  | ||||||
| 		return v.IsNil() |  | ||||||
| 	case reflect.Array: |  | ||||||
| 		for i := 0; i < v.Len(); i++ { |  | ||||||
| 			if !IsZero(v.Index(i)) { |  | ||||||
| 				return false |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return true |  | ||||||
| 	case reflect.Struct: |  | ||||||
| 		for i := 0; i < v.NumField(); i++ { |  | ||||||
| 			if !IsZero(v.Field(i)) { |  | ||||||
| 				return false |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  | @ -13,15 +13,15 @@ import ( | ||||||
| 	"github.com/google/go-cmp/cmp/internal/function" | 	"github.com/google/go-cmp/cmp/internal/function" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Option configures for specific behavior of Equal and Diff. In particular,
 | // Option configures for specific behavior of [Equal] and [Diff]. In particular,
 | ||||||
| // the fundamental Option functions (Ignore, Transformer, and Comparer),
 | // the fundamental Option functions ([Ignore], [Transformer], and [Comparer]),
 | ||||||
| // configure how equality is determined.
 | // configure how equality is determined.
 | ||||||
| //
 | //
 | ||||||
| // The fundamental options may be composed with filters (FilterPath and
 | // The fundamental options may be composed with filters ([FilterPath] and
 | ||||||
| // FilterValues) to control the scope over which they are applied.
 | // [FilterValues]) to control the scope over which they are applied.
 | ||||||
| //
 | //
 | ||||||
| // The cmp/cmpopts package provides helper functions for creating options that
 | // The [github.com/google/go-cmp/cmp/cmpopts] package provides helper functions
 | ||||||
| // may be used with Equal and Diff.
 | // for creating options that may be used with [Equal] and [Diff].
 | ||||||
| type Option interface { | type Option interface { | ||||||
| 	// filter applies all filters and returns the option that remains.
 | 	// filter applies all filters and returns the option that remains.
 | ||||||
| 	// Each option may only read s.curPath and call s.callTTBFunc.
 | 	// Each option may only read s.curPath and call s.callTTBFunc.
 | ||||||
|  | @ -33,6 +33,7 @@ type Option interface { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // applicableOption represents the following types:
 | // applicableOption represents the following types:
 | ||||||
|  | //
 | ||||||
| //	Fundamental: ignore | validator | *comparer | *transformer
 | //	Fundamental: ignore | validator | *comparer | *transformer
 | ||||||
| //	Grouping:    Options
 | //	Grouping:    Options
 | ||||||
| type applicableOption interface { | type applicableOption interface { | ||||||
|  | @ -43,6 +44,7 @@ type applicableOption interface { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // coreOption represents the following types:
 | // coreOption represents the following types:
 | ||||||
|  | //
 | ||||||
| //	Fundamental: ignore | validator | *comparer | *transformer
 | //	Fundamental: ignore | validator | *comparer | *transformer
 | ||||||
| //	Filters:     *pathFilter | *valuesFilter
 | //	Filters:     *pathFilter | *valuesFilter
 | ||||||
| type coreOption interface { | type coreOption interface { | ||||||
|  | @ -54,9 +56,9 @@ type core struct{} | ||||||
| 
 | 
 | ||||||
| func (core) isCore() {} | func (core) isCore() {} | ||||||
| 
 | 
 | ||||||
| // Options is a list of Option values that also satisfies the Option interface.
 | // Options is a list of [Option] values that also satisfies the [Option] interface.
 | ||||||
| // Helper comparison packages may return an Options value when packing multiple
 | // Helper comparison packages may return an Options value when packing multiple
 | ||||||
| // Option values into a single Option. When this package processes an Options,
 | // [Option] values into a single [Option]. When this package processes an Options,
 | ||||||
| // it will be implicitly expanded into a flat list.
 | // it will be implicitly expanded into a flat list.
 | ||||||
| //
 | //
 | ||||||
| // Applying a filter on an Options is equivalent to applying that same filter
 | // Applying a filter on an Options is equivalent to applying that same filter
 | ||||||
|  | @ -103,16 +105,16 @@ func (opts Options) String() string { | ||||||
| 	return fmt.Sprintf("Options{%s}", strings.Join(ss, ", ")) | 	return fmt.Sprintf("Options{%s}", strings.Join(ss, ", ")) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // FilterPath returns a new Option where opt is only evaluated if filter f
 | // FilterPath returns a new [Option] where opt is only evaluated if filter f
 | ||||||
| // returns true for the current Path in the value tree.
 | // returns true for the current [Path] in the value tree.
 | ||||||
| //
 | //
 | ||||||
| // This filter is called even if a slice element or map entry is missing and
 | // This filter is called even if a slice element or map entry is missing and
 | ||||||
| // provides an opportunity to ignore such cases. The filter function must be
 | // provides an opportunity to ignore such cases. The filter function must be
 | ||||||
| // symmetric such that the filter result is identical regardless of whether the
 | // symmetric such that the filter result is identical regardless of whether the
 | ||||||
| // missing value is from x or y.
 | // missing value is from x or y.
 | ||||||
| //
 | //
 | ||||||
| // The option passed in may be an Ignore, Transformer, Comparer, Options, or
 | // The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or
 | ||||||
| // a previously filtered Option.
 | // a previously filtered [Option].
 | ||||||
| func FilterPath(f func(Path) bool, opt Option) Option { | func FilterPath(f func(Path) bool, opt Option) Option { | ||||||
| 	if f == nil { | 	if f == nil { | ||||||
| 		panic("invalid path filter function") | 		panic("invalid path filter function") | ||||||
|  | @ -140,7 +142,7 @@ func (f pathFilter) String() string { | ||||||
| 	return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt) | 	return fmt.Sprintf("FilterPath(%s, %v)", function.NameOf(reflect.ValueOf(f.fnc)), f.opt) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // FilterValues returns a new Option where opt is only evaluated if filter f,
 | // FilterValues returns a new [Option] where opt is only evaluated if filter f,
 | ||||||
| // which is a function of the form "func(T, T) bool", returns true for the
 | // which is a function of the form "func(T, T) bool", returns true for the
 | ||||||
| // current pair of values being compared. If either value is invalid or
 | // current pair of values being compared. If either value is invalid or
 | ||||||
| // the type of the values is not assignable to T, then this filter implicitly
 | // the type of the values is not assignable to T, then this filter implicitly
 | ||||||
|  | @ -152,8 +154,8 @@ func (f pathFilter) String() string { | ||||||
| // If T is an interface, it is possible that f is called with two values with
 | // If T is an interface, it is possible that f is called with two values with
 | ||||||
| // different concrete types that both implement T.
 | // different concrete types that both implement T.
 | ||||||
| //
 | //
 | ||||||
| // The option passed in may be an Ignore, Transformer, Comparer, Options, or
 | // The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or
 | ||||||
| // a previously filtered Option.
 | // a previously filtered [Option].
 | ||||||
| func FilterValues(f interface{}, opt Option) Option { | func FilterValues(f interface{}, opt Option) Option { | ||||||
| 	v := reflect.ValueOf(f) | 	v := reflect.ValueOf(f) | ||||||
| 	if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() { | 	if !function.IsType(v.Type(), function.ValueFilter) || v.IsNil() { | ||||||
|  | @ -190,9 +192,9 @@ func (f valuesFilter) String() string { | ||||||
| 	return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt) | 	return fmt.Sprintf("FilterValues(%s, %v)", function.NameOf(f.fnc), f.opt) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Ignore is an Option that causes all comparisons to be ignored.
 | // Ignore is an [Option] that causes all comparisons to be ignored.
 | ||||||
| // This value is intended to be combined with FilterPath or FilterValues.
 | // This value is intended to be combined with [FilterPath] or [FilterValues].
 | ||||||
| // It is an error to pass an unfiltered Ignore option to Equal.
 | // It is an error to pass an unfiltered Ignore option to [Equal].
 | ||||||
| func Ignore() Option { return ignore{} } | func Ignore() Option { return ignore{} } | ||||||
| 
 | 
 | ||||||
| type ignore struct{ core } | type ignore struct{ core } | ||||||
|  | @ -232,6 +234,8 @@ func (validator) apply(s *state, vx, vy reflect.Value) { | ||||||
| 			name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType
 | 			name = fmt.Sprintf("%q.%v", t.PkgPath(), t.Name()) // e.g., "path/to/package".MyType
 | ||||||
| 			if _, ok := reflect.New(t).Interface().(error); ok { | 			if _, ok := reflect.New(t).Interface().(error); ok { | ||||||
| 				help = "consider using cmpopts.EquateErrors to compare error values" | 				help = "consider using cmpopts.EquateErrors to compare error values" | ||||||
|  | 			} else if t.Comparable() { | ||||||
|  | 				help = "consider using cmpopts.EquateComparable to compare comparable Go types" | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			// Unnamed type with unexported fields. Derive PkgPath from field.
 | 			// Unnamed type with unexported fields. Derive PkgPath from field.
 | ||||||
|  | @ -252,7 +256,7 @@ const identRx = `[_\p{L}][_\p{L}\p{N}]*` | ||||||
| 
 | 
 | ||||||
| var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) | var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) | ||||||
| 
 | 
 | ||||||
| // Transformer returns an Option that applies a transformation function that
 | // Transformer returns an [Option] that applies a transformation function that
 | ||||||
| // converts values of a certain type into that of another.
 | // converts values of a certain type into that of another.
 | ||||||
| //
 | //
 | ||||||
| // The transformer f must be a function "func(T) R" that converts values of
 | // The transformer f must be a function "func(T) R" that converts values of
 | ||||||
|  | @ -263,13 +267,14 @@ var identsRx = regexp.MustCompile(`^` + identRx + `(\.` + identRx + `)*$`) | ||||||
| // same transform to the output of itself (e.g., in the case where the
 | // same transform to the output of itself (e.g., in the case where the
 | ||||||
| // input and output types are the same), an implicit filter is added such that
 | // input and output types are the same), an implicit filter is added such that
 | ||||||
| // a transformer is applicable only if that exact transformer is not already
 | // a transformer is applicable only if that exact transformer is not already
 | ||||||
| // in the tail of the Path since the last non-Transform step.
 | // in the tail of the [Path] since the last non-[Transform] step.
 | ||||||
| // For situations where the implicit filter is still insufficient,
 | // For situations where the implicit filter is still insufficient,
 | ||||||
| // consider using cmpopts.AcyclicTransformer, which adds a filter
 | // consider using [github.com/google/go-cmp/cmp/cmpopts.AcyclicTransformer],
 | ||||||
| // to prevent the transformer from being recursively applied upon itself.
 | // which adds a filter to prevent the transformer from
 | ||||||
|  | // being recursively applied upon itself.
 | ||||||
| //
 | //
 | ||||||
| // The name is a user provided label that is used as the Transform.Name in the
 | // The name is a user provided label that is used as the [Transform.Name] in the
 | ||||||
| // transformation PathStep (and eventually shown in the Diff output).
 | // transformation [PathStep] (and eventually shown in the [Diff] output).
 | ||||||
| // The name must be a valid identifier or qualified identifier in Go syntax.
 | // The name must be a valid identifier or qualified identifier in Go syntax.
 | ||||||
| // If empty, an arbitrary name is used.
 | // If empty, an arbitrary name is used.
 | ||||||
| func Transformer(name string, f interface{}) Option { | func Transformer(name string, f interface{}) Option { | ||||||
|  | @ -327,7 +332,7 @@ func (tr transformer) String() string { | ||||||
| 	return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc)) | 	return fmt.Sprintf("Transformer(%s, %s)", tr.name, function.NameOf(tr.fnc)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Comparer returns an Option that determines whether two values are equal
 | // Comparer returns an [Option] that determines whether two values are equal
 | ||||||
| // to each other.
 | // to each other.
 | ||||||
| //
 | //
 | ||||||
| // The comparer f must be a function "func(T, T) bool" and is implicitly
 | // The comparer f must be a function "func(T, T) bool" and is implicitly
 | ||||||
|  | @ -336,9 +341,9 @@ func (tr transformer) String() string { | ||||||
| // both implement T.
 | // both implement T.
 | ||||||
| //
 | //
 | ||||||
| // The equality function must be:
 | // The equality function must be:
 | ||||||
| //	• Symmetric: equal(x, y) == equal(y, x)
 | //   - Symmetric: equal(x, y) == equal(y, x)
 | ||||||
| //	• Deterministic: equal(x, y) == equal(x, y)
 | //   - Deterministic: equal(x, y) == equal(x, y)
 | ||||||
| //	• Pure: equal(x, y) does not modify x or y
 | //   - Pure: equal(x, y) does not modify x or y
 | ||||||
| func Comparer(f interface{}) Option { | func Comparer(f interface{}) Option { | ||||||
| 	v := reflect.ValueOf(f) | 	v := reflect.ValueOf(f) | ||||||
| 	if !function.IsType(v.Type(), function.Equal) || v.IsNil() { | 	if !function.IsType(v.Type(), function.Equal) || v.IsNil() { | ||||||
|  | @ -375,35 +380,32 @@ func (cm comparer) String() string { | ||||||
| 	return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc)) | 	return fmt.Sprintf("Comparer(%s)", function.NameOf(cm.fnc)) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Exporter returns an Option that specifies whether Equal is allowed to
 | // Exporter returns an [Option] that specifies whether [Equal] is allowed to
 | ||||||
| // introspect into the unexported fields of certain struct types.
 | // introspect into the unexported fields of certain struct types.
 | ||||||
| //
 | //
 | ||||||
| // Users of this option must understand that comparing on unexported fields
 | // Users of this option must understand that comparing on unexported fields
 | ||||||
| // from external packages is not safe since changes in the internal
 | // from external packages is not safe since changes in the internal
 | ||||||
| // implementation of some external package may cause the result of Equal
 | // implementation of some external package may cause the result of [Equal]
 | ||||||
| // to unexpectedly change. However, it may be valid to use this option on types
 | // to unexpectedly change. However, it may be valid to use this option on types
 | ||||||
| // defined in an internal package where the semantic meaning of an unexported
 | // defined in an internal package where the semantic meaning of an unexported
 | ||||||
| // field is in the control of the user.
 | // field is in the control of the user.
 | ||||||
| //
 | //
 | ||||||
| // In many cases, a custom Comparer should be used instead that defines
 | // In many cases, a custom [Comparer] should be used instead that defines
 | ||||||
| // equality as a function of the public API of a type rather than the underlying
 | // equality as a function of the public API of a type rather than the underlying
 | ||||||
| // unexported implementation.
 | // unexported implementation.
 | ||||||
| //
 | //
 | ||||||
| // For example, the reflect.Type documentation defines equality to be determined
 | // For example, the [reflect.Type] documentation defines equality to be determined
 | ||||||
| // by the == operator on the interface (essentially performing a shallow pointer
 | // by the == operator on the interface (essentially performing a shallow pointer
 | ||||||
| // comparison) and most attempts to compare *regexp.Regexp types are interested
 | // comparison) and most attempts to compare *[regexp.Regexp] types are interested
 | ||||||
| // in only checking that the regular expression strings are equal.
 | // in only checking that the regular expression strings are equal.
 | ||||||
| // Both of these are accomplished using Comparers:
 | // Both of these are accomplished using [Comparer] options:
 | ||||||
| //
 | //
 | ||||||
| //	Comparer(func(x, y reflect.Type) bool { return x == y })
 | //	Comparer(func(x, y reflect.Type) bool { return x == y })
 | ||||||
| //	Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
 | //	Comparer(func(x, y *regexp.Regexp) bool { return x.String() == y.String() })
 | ||||||
| //
 | //
 | ||||||
| // In other cases, the cmpopts.IgnoreUnexported option can be used to ignore
 | // In other cases, the [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]
 | ||||||
| // all unexported fields on specified struct types.
 | // option can be used to ignore all unexported fields on specified struct types.
 | ||||||
| func Exporter(f func(reflect.Type) bool) Option { | func Exporter(f func(reflect.Type) bool) Option { | ||||||
| 	if !supportExporters { |  | ||||||
| 		panic("Exporter is not supported on purego builds") |  | ||||||
| 	} |  | ||||||
| 	return exporter(f) | 	return exporter(f) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -413,10 +415,10 @@ func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableO | ||||||
| 	panic("not implemented") | 	panic("not implemented") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // AllowUnexported returns an Options that allows Equal to forcibly introspect
 | // AllowUnexported returns an [Option] that allows [Equal] to forcibly introspect
 | ||||||
| // unexported fields of the specified struct types.
 | // unexported fields of the specified struct types.
 | ||||||
| //
 | //
 | ||||||
| // See Exporter for the proper use of this option.
 | // See [Exporter] for the proper use of this option.
 | ||||||
| func AllowUnexported(types ...interface{}) Option { | func AllowUnexported(types ...interface{}) Option { | ||||||
| 	m := make(map[reflect.Type]bool) | 	m := make(map[reflect.Type]bool) | ||||||
| 	for _, typ := range types { | 	for _, typ := range types { | ||||||
|  | @ -430,7 +432,7 @@ func AllowUnexported(types ...interface{}) Option { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Result represents the comparison result for a single node and
 | // Result represents the comparison result for a single node and
 | ||||||
| // is provided by cmp when calling Result (see Reporter).
 | // is provided by cmp when calling Report (see [Reporter]).
 | ||||||
| type Result struct { | type Result struct { | ||||||
| 	_     [0]func() // Make Result incomparable
 | 	_     [0]func() // Make Result incomparable
 | ||||||
| 	flags resultFlags | 	flags resultFlags | ||||||
|  | @ -443,7 +445,7 @@ func (r Result) Equal() bool { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ByIgnore reports whether the node is equal because it was ignored.
 | // ByIgnore reports whether the node is equal because it was ignored.
 | ||||||
| // This never reports true if Equal reports false.
 | // This never reports true if [Result.Equal] reports false.
 | ||||||
| func (r Result) ByIgnore() bool { | func (r Result) ByIgnore() bool { | ||||||
| 	return r.flags&reportByIgnore != 0 | 	return r.flags&reportByIgnore != 0 | ||||||
| } | } | ||||||
|  | @ -453,7 +455,7 @@ func (r Result) ByMethod() bool { | ||||||
| 	return r.flags&reportByMethod != 0 | 	return r.flags&reportByMethod != 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ByFunc reports whether a Comparer function determined equality.
 | // ByFunc reports whether a [Comparer] function determined equality.
 | ||||||
| func (r Result) ByFunc() bool { | func (r Result) ByFunc() bool { | ||||||
| 	return r.flags&reportByFunc != 0 | 	return r.flags&reportByFunc != 0 | ||||||
| } | } | ||||||
|  | @ -476,7 +478,7 @@ const ( | ||||||
| 	reportByCycle | 	reportByCycle | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Reporter is an Option that can be passed to Equal. When Equal traverses
 | // Reporter is an [Option] that can be passed to [Equal]. When [Equal] traverses
 | ||||||
| // the value trees, it calls PushStep as it descends into each node in the
 | // the value trees, it calls PushStep as it descends into each node in the
 | ||||||
| // tree and PopStep as it ascend out of the node. The leaves of the tree are
 | // tree and PopStep as it ascend out of the node. The leaves of the tree are
 | ||||||
| // either compared (determined to be equal or not equal) or ignored and reported
 | // either compared (determined to be equal or not equal) or ignored and reported
 | ||||||
|  |  | ||||||
|  | @ -14,9 +14,9 @@ import ( | ||||||
| 	"github.com/google/go-cmp/cmp/internal/value" | 	"github.com/google/go-cmp/cmp/internal/value" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Path is a list of PathSteps describing the sequence of operations to get
 | // Path is a list of [PathStep] describing the sequence of operations to get
 | ||||||
| // from some root type to the current position in the value tree.
 | // from some root type to the current position in the value tree.
 | ||||||
| // The first Path element is always an operation-less PathStep that exists
 | // The first Path element is always an operation-less [PathStep] that exists
 | ||||||
| // simply to identify the initial type.
 | // simply to identify the initial type.
 | ||||||
| //
 | //
 | ||||||
| // When traversing structs with embedded structs, the embedded struct will
 | // When traversing structs with embedded structs, the embedded struct will
 | ||||||
|  | @ -29,8 +29,13 @@ type Path []PathStep | ||||||
| // a value's tree structure. Users of this package never need to implement
 | // a value's tree structure. Users of this package never need to implement
 | ||||||
| // these types as values of this type will be returned by this package.
 | // these types as values of this type will be returned by this package.
 | ||||||
| //
 | //
 | ||||||
| // Implementations of this interface are
 | // Implementations of this interface:
 | ||||||
| // StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and Transform.
 | //   - [StructField]
 | ||||||
|  | //   - [SliceIndex]
 | ||||||
|  | //   - [MapIndex]
 | ||||||
|  | //   - [Indirect]
 | ||||||
|  | //   - [TypeAssertion]
 | ||||||
|  | //   - [Transform]
 | ||||||
| type PathStep interface { | type PathStep interface { | ||||||
| 	String() string | 	String() string | ||||||
| 
 | 
 | ||||||
|  | @ -41,12 +46,12 @@ type PathStep interface { | ||||||
| 	// The type of each valid value is guaranteed to be identical to Type.
 | 	// The type of each valid value is guaranteed to be identical to Type.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// In some cases, one or both may be invalid or have restrictions:
 | 	// In some cases, one or both may be invalid or have restrictions:
 | ||||||
| 	//	• For StructField, both are not interface-able if the current field
 | 	//   - For StructField, both are not interface-able if the current field
 | ||||||
| 	//     is unexported and the struct type is not explicitly permitted by
 | 	//     is unexported and the struct type is not explicitly permitted by
 | ||||||
| 	//     an Exporter to traverse unexported fields.
 | 	//     an Exporter to traverse unexported fields.
 | ||||||
| 	//	• For SliceIndex, one may be invalid if an element is missing from
 | 	//   - For SliceIndex, one may be invalid if an element is missing from
 | ||||||
| 	//     either the x or y slice.
 | 	//     either the x or y slice.
 | ||||||
| 	//	• For MapIndex, one may be invalid if an entry is missing from
 | 	//   - For MapIndex, one may be invalid if an entry is missing from
 | ||||||
| 	//     either the x or y map.
 | 	//     either the x or y map.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// The provided values must not be mutated.
 | 	// The provided values must not be mutated.
 | ||||||
|  | @ -70,8 +75,9 @@ func (pa *Path) pop() { | ||||||
| 	*pa = (*pa)[:len(*pa)-1] | 	*pa = (*pa)[:len(*pa)-1] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Last returns the last PathStep in the Path.
 | // Last returns the last [PathStep] in the Path.
 | ||||||
| // If the path is empty, this returns a non-nil PathStep that reports a nil Type.
 | // If the path is empty, this returns a non-nil [PathStep]
 | ||||||
|  | // that reports a nil [PathStep.Type].
 | ||||||
| func (pa Path) Last() PathStep { | func (pa Path) Last() PathStep { | ||||||
| 	return pa.Index(-1) | 	return pa.Index(-1) | ||||||
| } | } | ||||||
|  | @ -79,7 +85,8 @@ func (pa Path) Last() PathStep { | ||||||
| // Index returns the ith step in the Path and supports negative indexing.
 | // Index returns the ith step in the Path and supports negative indexing.
 | ||||||
| // A negative index starts counting from the tail of the Path such that -1
 | // A negative index starts counting from the tail of the Path such that -1
 | ||||||
| // refers to the last step, -2 refers to the second-to-last step, and so on.
 | // refers to the last step, -2 refers to the second-to-last step, and so on.
 | ||||||
| // If index is invalid, this returns a non-nil PathStep that reports a nil Type.
 | // If index is invalid, this returns a non-nil [PathStep]
 | ||||||
|  | // that reports a nil [PathStep.Type].
 | ||||||
| func (pa Path) Index(i int) PathStep { | func (pa Path) Index(i int) PathStep { | ||||||
| 	if i < 0 { | 	if i < 0 { | ||||||
| 		i = len(pa) + i | 		i = len(pa) + i | ||||||
|  | @ -94,6 +101,7 @@ func (pa Path) Index(i int) PathStep { | ||||||
| // The simplified path only contains struct field accesses.
 | // The simplified path only contains struct field accesses.
 | ||||||
| //
 | //
 | ||||||
| // For example:
 | // For example:
 | ||||||
|  | //
 | ||||||
| //	MyMap.MySlices.MyField
 | //	MyMap.MySlices.MyField
 | ||||||
| func (pa Path) String() string { | func (pa Path) String() string { | ||||||
| 	var ss []string | 	var ss []string | ||||||
|  | @ -108,6 +116,7 @@ func (pa Path) String() string { | ||||||
| // GoString returns the path to a specific node using Go syntax.
 | // GoString returns the path to a specific node using Go syntax.
 | ||||||
| //
 | //
 | ||||||
| // For example:
 | // For example:
 | ||||||
|  | //
 | ||||||
| //	(*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
 | //	(*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField
 | ||||||
| func (pa Path) GoString() string { | func (pa Path) GoString() string { | ||||||
| 	var ssPre, ssPost []string | 	var ssPre, ssPost []string | ||||||
|  | @ -159,14 +168,15 @@ func (ps pathStep) String() string { | ||||||
| 	if ps.typ == nil { | 	if ps.typ == nil { | ||||||
| 		return "<nil>" | 		return "<nil>" | ||||||
| 	} | 	} | ||||||
| 	s := ps.typ.String() | 	s := value.TypeString(ps.typ, false) | ||||||
| 	if s == "" || strings.ContainsAny(s, "{}\n") { | 	if s == "" || strings.ContainsAny(s, "{}\n") { | ||||||
| 		return "root" // Type too simple or complex to print
 | 		return "root" // Type too simple or complex to print
 | ||||||
| 	} | 	} | ||||||
| 	return fmt.Sprintf("{%s}", s) | 	return fmt.Sprintf("{%s}", s) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // StructField represents a struct field access on a field called Name.
 | // StructField is a [PathStep] that represents a struct field access
 | ||||||
|  | // on a field called [StructField.Name].
 | ||||||
| type StructField struct{ *structField } | type StructField struct{ *structField } | ||||||
| type structField struct { | type structField struct { | ||||||
| 	pathStep | 	pathStep | ||||||
|  | @ -178,7 +188,7 @@ type structField struct { | ||||||
| 	unexported bool | 	unexported bool | ||||||
| 	mayForce   bool                // Forcibly allow visibility
 | 	mayForce   bool                // Forcibly allow visibility
 | ||||||
| 	paddr      bool                // Was parent addressable?
 | 	paddr      bool                // Was parent addressable?
 | ||||||
| 	pvx, pvy   reflect.Value       // Parent values (always addressible)
 | 	pvx, pvy   reflect.Value       // Parent values (always addressable)
 | ||||||
| 	field      reflect.StructField // Field information
 | 	field      reflect.StructField // Field information
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -202,10 +212,11 @@ func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) } | ||||||
| func (sf StructField) Name() string { return sf.name } | func (sf StructField) Name() string { return sf.name } | ||||||
| 
 | 
 | ||||||
| // Index is the index of the field in the parent struct type.
 | // Index is the index of the field in the parent struct type.
 | ||||||
| // See reflect.Type.Field.
 | // See [reflect.Type.Field].
 | ||||||
| func (sf StructField) Index() int { return sf.idx } | func (sf StructField) Index() int { return sf.idx } | ||||||
| 
 | 
 | ||||||
| // SliceIndex is an index operation on a slice or array at some index Key.
 | // SliceIndex is a [PathStep] that represents an index operation on
 | ||||||
|  | // a slice or array at some index [SliceIndex.Key].
 | ||||||
| type SliceIndex struct{ *sliceIndex } | type SliceIndex struct{ *sliceIndex } | ||||||
| type sliceIndex struct { | type sliceIndex struct { | ||||||
| 	pathStep | 	pathStep | ||||||
|  | @ -245,12 +256,12 @@ func (si SliceIndex) Key() int { | ||||||
| // all of the indexes to be shifted. If an index is -1, then that
 | // all of the indexes to be shifted. If an index is -1, then that
 | ||||||
| // indicates that the element does not exist in the associated slice.
 | // indicates that the element does not exist in the associated slice.
 | ||||||
| //
 | //
 | ||||||
| // Key is guaranteed to return -1 if and only if the indexes returned
 | // [SliceIndex.Key] is guaranteed to return -1 if and only if the indexes
 | ||||||
| // by SplitKeys are not the same. SplitKeys will never return -1 for
 | // returned by SplitKeys are not the same. SplitKeys will never return -1 for
 | ||||||
| // both indexes.
 | // both indexes.
 | ||||||
| func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey } | func (si SliceIndex) SplitKeys() (ix, iy int) { return si.xkey, si.ykey } | ||||||
| 
 | 
 | ||||||
| // MapIndex is an index operation on a map at some index Key.
 | // MapIndex is a [PathStep] that represents an index operation on a map at some index Key.
 | ||||||
| type MapIndex struct{ *mapIndex } | type MapIndex struct{ *mapIndex } | ||||||
| type mapIndex struct { | type mapIndex struct { | ||||||
| 	pathStep | 	pathStep | ||||||
|  | @ -264,7 +275,7 @@ func (mi MapIndex) String() string                 { return fmt.Sprintf("[%#v]", | ||||||
| // Key is the value of the map key.
 | // Key is the value of the map key.
 | ||||||
| func (mi MapIndex) Key() reflect.Value { return mi.key } | func (mi MapIndex) Key() reflect.Value { return mi.key } | ||||||
| 
 | 
 | ||||||
| // Indirect represents pointer indirection on the parent type.
 | // Indirect is a [PathStep] that represents pointer indirection on the parent type.
 | ||||||
| type Indirect struct{ *indirect } | type Indirect struct{ *indirect } | ||||||
| type indirect struct { | type indirect struct { | ||||||
| 	pathStep | 	pathStep | ||||||
|  | @ -274,7 +285,7 @@ func (in Indirect) Type() reflect.Type             { return in.typ } | ||||||
| func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy } | func (in Indirect) Values() (vx, vy reflect.Value) { return in.vx, in.vy } | ||||||
| func (in Indirect) String() string                 { return "*" } | func (in Indirect) String() string                 { return "*" } | ||||||
| 
 | 
 | ||||||
| // TypeAssertion represents a type assertion on an interface.
 | // TypeAssertion is a [PathStep] that represents a type assertion on an interface.
 | ||||||
| type TypeAssertion struct{ *typeAssertion } | type TypeAssertion struct{ *typeAssertion } | ||||||
| type typeAssertion struct { | type typeAssertion struct { | ||||||
| 	pathStep | 	pathStep | ||||||
|  | @ -282,9 +293,10 @@ type typeAssertion struct { | ||||||
| 
 | 
 | ||||||
| func (ta TypeAssertion) Type() reflect.Type             { return ta.typ } | func (ta TypeAssertion) Type() reflect.Type             { return ta.typ } | ||||||
| func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy } | func (ta TypeAssertion) Values() (vx, vy reflect.Value) { return ta.vx, ta.vy } | ||||||
| func (ta TypeAssertion) String() string                 { return fmt.Sprintf(".(%v)", ta.typ) } | func (ta TypeAssertion) String() string                 { return fmt.Sprintf(".(%v)", value.TypeString(ta.typ, false)) } | ||||||
| 
 | 
 | ||||||
| // Transform is a transformation from the parent type to the current type.
 | // Transform is a [PathStep] that represents a transformation
 | ||||||
|  | // from the parent type to the current type.
 | ||||||
| type Transform struct{ *transform } | type Transform struct{ *transform } | ||||||
| type transform struct { | type transform struct { | ||||||
| 	pathStep | 	pathStep | ||||||
|  | @ -295,13 +307,13 @@ func (tf Transform) Type() reflect.Type             { return tf.typ } | ||||||
| func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy } | func (tf Transform) Values() (vx, vy reflect.Value) { return tf.vx, tf.vy } | ||||||
| func (tf Transform) String() string                 { return fmt.Sprintf("%s()", tf.trans.name) } | func (tf Transform) String() string                 { return fmt.Sprintf("%s()", tf.trans.name) } | ||||||
| 
 | 
 | ||||||
| // Name is the name of the Transformer.
 | // Name is the name of the [Transformer].
 | ||||||
| func (tf Transform) Name() string { return tf.trans.name } | func (tf Transform) Name() string { return tf.trans.name } | ||||||
| 
 | 
 | ||||||
| // Func is the function pointer to the transformer function.
 | // Func is the function pointer to the transformer function.
 | ||||||
| func (tf Transform) Func() reflect.Value { return tf.trans.fnc } | func (tf Transform) Func() reflect.Value { return tf.trans.fnc } | ||||||
| 
 | 
 | ||||||
| // Option returns the originally constructed Transformer option.
 | // Option returns the originally constructed [Transformer] option.
 | ||||||
| // The == operator can be used to detect the exact option used.
 | // The == operator can be used to detect the exact option used.
 | ||||||
| func (tf Transform) Option() Option { return tf.trans } | func (tf Transform) Option() Option { return tf.trans } | ||||||
| 
 | 
 | ||||||
|  | @ -315,7 +327,7 @@ func (tf Transform) Option() Option { return tf.trans } | ||||||
| // pops the address from the stack. Thus, when traversing into a pointer from
 | // pops the address from the stack. Thus, when traversing into a pointer from
 | ||||||
| // reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles
 | // reflect.Ptr, reflect.Slice element, or reflect.Map, we can detect cycles
 | ||||||
| // by checking whether the pointer has already been visited. The cycle detection
 | // by checking whether the pointer has already been visited. The cycle detection
 | ||||||
| // uses a seperate stack for the x and y values.
 | // uses a separate stack for the x and y values.
 | ||||||
| //
 | //
 | ||||||
| // If a cycle is detected we need to determine whether the two pointers
 | // If a cycle is detected we need to determine whether the two pointers
 | ||||||
| // should be considered equal. The definition of equality chosen by Equal
 | // should be considered equal. The definition of equality chosen by Equal
 | ||||||
|  |  | ||||||
|  | @ -7,8 +7,6 @@ package cmp | ||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 
 |  | ||||||
| 	"github.com/google/go-cmp/cmp/internal/value" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // numContextRecords is the number of surrounding equal records to print.
 | // numContextRecords is the number of surrounding equal records to print.
 | ||||||
|  | @ -116,7 +114,10 @@ func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// For leaf nodes, format the value based on the reflect.Values alone.
 | 	// For leaf nodes, format the value based on the reflect.Values alone.
 | ||||||
| 	if v.MaxDepth == 0 { | 	// As a special case, treat equal []byte as a leaf nodes.
 | ||||||
|  | 	isBytes := v.Type.Kind() == reflect.Slice && v.Type.Elem() == byteType | ||||||
|  | 	isEqualBytes := isBytes && v.NumDiff+v.NumIgnored+v.NumTransformed == 0 | ||||||
|  | 	if v.MaxDepth == 0 || isEqualBytes { | ||||||
| 		switch opts.DiffMode { | 		switch opts.DiffMode { | ||||||
| 		case diffUnknown, diffIdentical: | 		case diffUnknown, diffIdentical: | ||||||
| 			// Format Equal.
 | 			// Format Equal.
 | ||||||
|  | @ -245,11 +246,11 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, pt | ||||||
| 				var isZero bool | 				var isZero bool | ||||||
| 				switch opts.DiffMode { | 				switch opts.DiffMode { | ||||||
| 				case diffIdentical: | 				case diffIdentical: | ||||||
| 					isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY) | 					isZero = r.Value.ValueX.IsZero() || r.Value.ValueY.IsZero() | ||||||
| 				case diffRemoved: | 				case diffRemoved: | ||||||
| 					isZero = value.IsZero(r.Value.ValueX) | 					isZero = r.Value.ValueX.IsZero() | ||||||
| 				case diffInserted: | 				case diffInserted: | ||||||
| 					isZero = value.IsZero(r.Value.ValueY) | 					isZero = r.Value.ValueY.IsZero() | ||||||
| 				} | 				} | ||||||
| 				if isZero { | 				if isZero { | ||||||
| 					continue | 					continue | ||||||
|  |  | ||||||
|  | @ -16,6 +16,13 @@ import ( | ||||||
| 	"github.com/google/go-cmp/cmp/internal/value" | 	"github.com/google/go-cmp/cmp/internal/value" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | var ( | ||||||
|  | 	anyType    = reflect.TypeOf((*interface{})(nil)).Elem() | ||||||
|  | 	stringType = reflect.TypeOf((*string)(nil)).Elem() | ||||||
|  | 	bytesType  = reflect.TypeOf((*[]byte)(nil)).Elem() | ||||||
|  | 	byteType   = reflect.TypeOf((*byte)(nil)).Elem() | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| type formatValueOptions struct { | type formatValueOptions struct { | ||||||
| 	// AvoidStringer controls whether to avoid calling custom stringer
 | 	// AvoidStringer controls whether to avoid calling custom stringer
 | ||||||
| 	// methods like error.Error or fmt.Stringer.String.
 | 	// methods like error.Error or fmt.Stringer.String.
 | ||||||
|  | @ -184,7 +191,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, | ||||||
| 		} | 		} | ||||||
| 		for i := 0; i < v.NumField(); i++ { | 		for i := 0; i < v.NumField(); i++ { | ||||||
| 			vv := v.Field(i) | 			vv := v.Field(i) | ||||||
| 			if value.IsZero(vv) { | 			if vv.IsZero() { | ||||||
| 				continue // Elide fields with zero values
 | 				continue // Elide fields with zero values
 | ||||||
| 			} | 			} | ||||||
| 			if len(list) == maxLen { | 			if len(list) == maxLen { | ||||||
|  | @ -192,7 +199,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 			sf := t.Field(i) | 			sf := t.Field(i) | ||||||
| 			if supportExporters && !isExported(sf.Name) { | 			if !isExported(sf.Name) { | ||||||
| 				vv = retrieveUnexportedField(v, sf, true) | 				vv = retrieveUnexportedField(v, sf, true) | ||||||
| 			} | 			} | ||||||
| 			s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs) | 			s := opts.WithTypeMode(autoType).FormatValue(vv, t.Kind(), ptrs) | ||||||
|  | @ -205,12 +212,13 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Check whether this is a []byte of text data.
 | 		// Check whether this is a []byte of text data.
 | ||||||
| 		if t.Elem() == reflect.TypeOf(byte(0)) { | 		if t.Elem() == byteType { | ||||||
| 			b := v.Bytes() | 			b := v.Bytes() | ||||||
| 			isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) && unicode.IsSpace(r) } | 			isPrintSpace := func(r rune) bool { return unicode.IsPrint(r) || unicode.IsSpace(r) } | ||||||
| 			if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 { | 			if len(b) > 0 && utf8.Valid(b) && len(bytes.TrimFunc(b, isPrintSpace)) == 0 { | ||||||
| 				out = opts.formatString("", string(b)) | 				out = opts.formatString("", string(b)) | ||||||
| 				return opts.WithTypeMode(emitType).FormatType(t, out) | 				skipType = true | ||||||
|  | 				return opts.FormatType(t, out) | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -281,7 +289,12 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, | ||||||
| 		} | 		} | ||||||
| 		defer ptrs.Pop() | 		defer ptrs.Pop() | ||||||
| 
 | 
 | ||||||
|  | 		// Skip the name only if this is an unnamed pointer type.
 | ||||||
|  | 		// Otherwise taking the address of a value does not reproduce
 | ||||||
|  | 		// the named pointer type.
 | ||||||
|  | 		if v.Type().Name() == "" { | ||||||
| 			skipType = true // Let the underlying value print the type instead
 | 			skipType = true // Let the underlying value print the type instead
 | ||||||
|  | 		} | ||||||
| 		out = opts.FormatValue(v.Elem(), t.Kind(), ptrs) | 		out = opts.FormatValue(v.Elem(), t.Kind(), ptrs) | ||||||
| 		out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out) | 		out = wrapTrunkReference(ptrRef, opts.PrintAddresses, out) | ||||||
| 		out = &textWrap{Prefix: "&", Value: out} | 		out = &textWrap{Prefix: "&", Value: out} | ||||||
|  | @ -292,7 +305,6 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, | ||||||
| 		} | 		} | ||||||
| 		// Interfaces accept different concrete types,
 | 		// Interfaces accept different concrete types,
 | ||||||
| 		// so configure the underlying value to explicitly print the type.
 | 		// so configure the underlying value to explicitly print the type.
 | ||||||
| 		skipType = true // Print the concrete type instead
 |  | ||||||
| 		return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs) | 		return opts.WithTypeMode(emitType).FormatValue(v.Elem(), t.Kind(), ptrs) | ||||||
| 	default: | 	default: | ||||||
| 		panic(fmt.Sprintf("%v kind not handled", v.Kind())) | 		panic(fmt.Sprintf("%v kind not handled", v.Kind())) | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ package cmp | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"math" | ||||||
| 	"reflect" | 	"reflect" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | @ -79,7 +80,7 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Use specialized string diffing for longer slices or strings.
 | 	// Use specialized string diffing for longer slices or strings.
 | ||||||
| 	const minLength = 64 | 	const minLength = 32 | ||||||
| 	return vx.Len() >= minLength && vy.Len() >= minLength | 	return vx.Len() >= minLength && vy.Len() >= minLength | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -96,15 +97,16 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Auto-detect the type of the data.
 | 	// Auto-detect the type of the data.
 | ||||||
| 	var isLinedText, isText, isBinary bool |  | ||||||
| 	var sx, sy string | 	var sx, sy string | ||||||
|  | 	var ssx, ssy []string | ||||||
|  | 	var isString, isMostlyText, isPureLinedText, isBinary bool | ||||||
| 	switch { | 	switch { | ||||||
| 	case t.Kind() == reflect.String: | 	case t.Kind() == reflect.String: | ||||||
| 		sx, sy = vx.String(), vy.String() | 		sx, sy = vx.String(), vy.String() | ||||||
| 		isText = true // Initial estimate, verify later
 | 		isString = true | ||||||
| 	case t.Kind() == reflect.Slice && t.Elem() == reflect.TypeOf(byte(0)): | 	case t.Kind() == reflect.Slice && t.Elem() == byteType: | ||||||
| 		sx, sy = string(vx.Bytes()), string(vy.Bytes()) | 		sx, sy = string(vx.Bytes()), string(vy.Bytes()) | ||||||
| 		isBinary = true // Initial estimate, verify later
 | 		isString = true | ||||||
| 	case t.Kind() == reflect.Array: | 	case t.Kind() == reflect.Array: | ||||||
| 		// Arrays need to be addressable for slice operations to work.
 | 		// Arrays need to be addressable for slice operations to work.
 | ||||||
| 		vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem() | 		vx2, vy2 := reflect.New(t).Elem(), reflect.New(t).Elem() | ||||||
|  | @ -112,13 +114,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { | ||||||
| 		vy2.Set(vy) | 		vy2.Set(vy) | ||||||
| 		vx, vy = vx2, vy2 | 		vx, vy = vx2, vy2 | ||||||
| 	} | 	} | ||||||
| 	if isText || isBinary { | 	if isString { | ||||||
| 		var numLines, lastLineIdx, maxLineLen int | 		var numTotalRunes, numValidRunes, numLines, lastLineIdx, maxLineLen int | ||||||
| 		isBinary = !utf8.ValidString(sx) || !utf8.ValidString(sy) |  | ||||||
| 		for i, r := range sx + sy { | 		for i, r := range sx + sy { | ||||||
| 			if !(unicode.IsPrint(r) || unicode.IsSpace(r)) || r == utf8.RuneError { | 			numTotalRunes++ | ||||||
| 				isBinary = true | 			if (unicode.IsPrint(r) || unicode.IsSpace(r)) && r != utf8.RuneError { | ||||||
| 				break | 				numValidRunes++ | ||||||
| 			} | 			} | ||||||
| 			if r == '\n' { | 			if r == '\n' { | ||||||
| 				if maxLineLen < i-lastLineIdx { | 				if maxLineLen < i-lastLineIdx { | ||||||
|  | @ -128,8 +129,29 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { | ||||||
| 				numLines++ | 				numLines++ | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		isText = !isBinary | 		isPureText := numValidRunes == numTotalRunes | ||||||
| 		isLinedText = isText && numLines >= 4 && maxLineLen <= 1024 | 		isMostlyText = float64(numValidRunes) > math.Floor(0.90*float64(numTotalRunes)) | ||||||
|  | 		isPureLinedText = isPureText && numLines >= 4 && maxLineLen <= 1024 | ||||||
|  | 		isBinary = !isMostlyText | ||||||
|  | 
 | ||||||
|  | 		// Avoid diffing by lines if it produces a significantly more complex
 | ||||||
|  | 		// edit script than diffing by bytes.
 | ||||||
|  | 		if isPureLinedText { | ||||||
|  | 			ssx = strings.Split(sx, "\n") | ||||||
|  | 			ssy = strings.Split(sy, "\n") | ||||||
|  | 			esLines := diff.Difference(len(ssx), len(ssy), func(ix, iy int) diff.Result { | ||||||
|  | 				return diff.BoolResult(ssx[ix] == ssy[iy]) | ||||||
|  | 			}) | ||||||
|  | 			esBytes := diff.Difference(len(sx), len(sy), func(ix, iy int) diff.Result { | ||||||
|  | 				return diff.BoolResult(sx[ix] == sy[iy]) | ||||||
|  | 			}) | ||||||
|  | 			efficiencyLines := float64(esLines.Dist()) / float64(len(esLines)) | ||||||
|  | 			efficiencyBytes := float64(esBytes.Dist()) / float64(len(esBytes)) | ||||||
|  | 			quotedLength := len(strconv.Quote(sx + sy)) | ||||||
|  | 			unquotedLength := len(sx) + len(sy) | ||||||
|  | 			escapeExpansionRatio := float64(quotedLength) / float64(unquotedLength) | ||||||
|  | 			isPureLinedText = efficiencyLines < 4*efficiencyBytes || escapeExpansionRatio > 1.1 | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	// Format the string into printable records.
 | 	// Format the string into printable records.
 | ||||||
|  | @ -138,9 +160,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { | ||||||
| 	switch { | 	switch { | ||||||
| 	// If the text appears to be multi-lined text,
 | 	// If the text appears to be multi-lined text,
 | ||||||
| 	// then perform differencing across individual lines.
 | 	// then perform differencing across individual lines.
 | ||||||
| 	case isLinedText: | 	case isPureLinedText: | ||||||
| 		ssx := strings.Split(sx, "\n") |  | ||||||
| 		ssy := strings.Split(sy, "\n") |  | ||||||
| 		list = opts.formatDiffSlice( | 		list = opts.formatDiffSlice( | ||||||
| 			reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line", | 			reflect.ValueOf(ssx), reflect.ValueOf(ssy), 1, "line", | ||||||
| 			func(v reflect.Value, d diffMode) textRecord { | 			func(v reflect.Value, d diffMode) textRecord { | ||||||
|  | @ -154,12 +174,13 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { | ||||||
| 		// differences in a string literal. This format is more readable,
 | 		// differences in a string literal. This format is more readable,
 | ||||||
| 		// but has edge-cases where differences are visually indistinguishable.
 | 		// but has edge-cases where differences are visually indistinguishable.
 | ||||||
| 		// This format is avoided under the following conditions:
 | 		// This format is avoided under the following conditions:
 | ||||||
| 		//	• A line starts with `"""`
 | 		//   - A line starts with `"""`
 | ||||||
| 		//	• A line starts with "..."
 | 		//   - A line starts with "..."
 | ||||||
| 		//	• A line contains non-printable characters
 | 		//   - A line contains non-printable characters
 | ||||||
| 		//	• Adjacent different lines differ only by whitespace
 | 		//   - Adjacent different lines differ only by whitespace
 | ||||||
| 		//
 | 		//
 | ||||||
| 		// For example:
 | 		// For example:
 | ||||||
|  | 		//
 | ||||||
| 		//		"""
 | 		//		"""
 | ||||||
| 		//		... // 3 identical lines
 | 		//		... // 3 identical lines
 | ||||||
| 		//		foo
 | 		//		foo
 | ||||||
|  | @ -214,7 +235,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { | ||||||
| 			var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"} | 			var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"} | ||||||
| 			switch t.Kind() { | 			switch t.Kind() { | ||||||
| 			case reflect.String: | 			case reflect.String: | ||||||
| 				if t != reflect.TypeOf(string("")) { | 				if t != stringType { | ||||||
| 					out = opts.FormatType(t, out) | 					out = opts.FormatType(t, out) | ||||||
| 				} | 				} | ||||||
| 			case reflect.Slice: | 			case reflect.Slice: | ||||||
|  | @ -229,7 +250,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { | ||||||
| 	// If the text appears to be single-lined text,
 | 	// If the text appears to be single-lined text,
 | ||||||
| 	// then perform differencing in approximately fixed-sized chunks.
 | 	// then perform differencing in approximately fixed-sized chunks.
 | ||||||
| 	// The output is printed as quoted strings.
 | 	// The output is printed as quoted strings.
 | ||||||
| 	case isText: | 	case isMostlyText: | ||||||
| 		list = opts.formatDiffSlice( | 		list = opts.formatDiffSlice( | ||||||
| 			reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte", | 			reflect.ValueOf(sx), reflect.ValueOf(sy), 64, "byte", | ||||||
| 			func(v reflect.Value, d diffMode) textRecord { | 			func(v reflect.Value, d diffMode) textRecord { | ||||||
|  | @ -237,7 +258,6 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { | ||||||
| 				return textRecord{Diff: d, Value: textLine(s)} | 				return textRecord{Diff: d, Value: textLine(s)} | ||||||
| 			}, | 			}, | ||||||
| 		) | 		) | ||||||
| 		delim = "" |  | ||||||
| 
 | 
 | ||||||
| 	// If the text appears to be binary data,
 | 	// If the text appears to be binary data,
 | ||||||
| 	// then perform differencing in approximately fixed-sized chunks.
 | 	// then perform differencing in approximately fixed-sized chunks.
 | ||||||
|  | @ -299,7 +319,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { | ||||||
| 
 | 
 | ||||||
| 	// Wrap the output with appropriate type information.
 | 	// Wrap the output with appropriate type information.
 | ||||||
| 	var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"} | 	var out textNode = &textWrap{Prefix: "{", Value: list, Suffix: "}"} | ||||||
| 	if !isText { | 	if !isMostlyText { | ||||||
| 		// The "{...}" byte-sequence literal is not valid Go syntax for strings.
 | 		// The "{...}" byte-sequence literal is not valid Go syntax for strings.
 | ||||||
| 		// Emit the type for extra clarity (e.g. "string{...}").
 | 		// Emit the type for extra clarity (e.g. "string{...}").
 | ||||||
| 		if t.Kind() == reflect.String { | 		if t.Kind() == reflect.String { | ||||||
|  | @ -310,12 +330,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { | ||||||
| 	switch t.Kind() { | 	switch t.Kind() { | ||||||
| 	case reflect.String: | 	case reflect.String: | ||||||
| 		out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} | 		out = &textWrap{Prefix: "strings.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} | ||||||
| 		if t != reflect.TypeOf(string("")) { | 		if t != stringType { | ||||||
| 			out = opts.FormatType(t, out) | 			out = opts.FormatType(t, out) | ||||||
| 		} | 		} | ||||||
| 	case reflect.Slice: | 	case reflect.Slice: | ||||||
| 		out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} | 		out = &textWrap{Prefix: "bytes.Join(", Value: out, Suffix: fmt.Sprintf(", %q)", delim)} | ||||||
| 		if t != reflect.TypeOf([]byte(nil)) { | 		if t != bytesType { | ||||||
| 			out = opts.FormatType(t, out) | 			out = opts.FormatType(t, out) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -338,8 +358,11 @@ func (opts formatOptions) formatDiffSlice( | ||||||
| 	vx, vy reflect.Value, chunkSize int, name string, | 	vx, vy reflect.Value, chunkSize int, name string, | ||||||
| 	makeRec func(reflect.Value, diffMode) textRecord, | 	makeRec func(reflect.Value, diffMode) textRecord, | ||||||
| ) (list textList) { | ) (list textList) { | ||||||
| 	es := diff.Difference(vx.Len(), vy.Len(), func(ix int, iy int) diff.Result { | 	eq := func(ix, iy int) bool { | ||||||
| 		return diff.BoolResult(vx.Index(ix).Interface() == vy.Index(iy).Interface()) | 		return vx.Index(ix).Interface() == vy.Index(iy).Interface() | ||||||
|  | 	} | ||||||
|  | 	es := diff.Difference(vx.Len(), vy.Len(), func(ix, iy int) diff.Result { | ||||||
|  | 		return diff.BoolResult(eq(ix, iy)) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	appendChunks := func(v reflect.Value, d diffMode) int { | 	appendChunks := func(v reflect.Value, d diffMode) int { | ||||||
|  | @ -364,6 +387,7 @@ func (opts formatOptions) formatDiffSlice( | ||||||
| 
 | 
 | ||||||
| 	groups := coalesceAdjacentEdits(name, es) | 	groups := coalesceAdjacentEdits(name, es) | ||||||
| 	groups = coalesceInterveningIdentical(groups, chunkSize/4) | 	groups = coalesceInterveningIdentical(groups, chunkSize/4) | ||||||
|  | 	groups = cleanupSurroundingIdentical(groups, eq) | ||||||
| 	maxGroup := diffStats{Name: name} | 	maxGroup := diffStats{Name: name} | ||||||
| 	for i, ds := range groups { | 	for i, ds := range groups { | ||||||
| 		if maxLen >= 0 && numDiffs >= maxLen { | 		if maxLen >= 0 && numDiffs >= maxLen { | ||||||
|  | @ -416,25 +440,35 @@ func (opts formatOptions) formatDiffSlice( | ||||||
| 
 | 
 | ||||||
| // coalesceAdjacentEdits coalesces the list of edits into groups of adjacent
 | // coalesceAdjacentEdits coalesces the list of edits into groups of adjacent
 | ||||||
| // equal or unequal counts.
 | // equal or unequal counts.
 | ||||||
|  | //
 | ||||||
|  | // Example:
 | ||||||
|  | //
 | ||||||
|  | //	Input:  "..XXY...Y"
 | ||||||
|  | //	Output: [
 | ||||||
|  | //		{NumIdentical: 2},
 | ||||||
|  | //		{NumRemoved: 2, NumInserted 1},
 | ||||||
|  | //		{NumIdentical: 3},
 | ||||||
|  | //		{NumInserted: 1},
 | ||||||
|  | //	]
 | ||||||
| func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) { | func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) { | ||||||
| 	var prevCase int // Arbitrary index into which case last occurred
 | 	var prevMode byte | ||||||
| 	lastStats := func(i int) *diffStats { | 	lastStats := func(mode byte) *diffStats { | ||||||
| 		if prevCase != i { | 		if prevMode != mode { | ||||||
| 			groups = append(groups, diffStats{Name: name}) | 			groups = append(groups, diffStats{Name: name}) | ||||||
| 			prevCase = i | 			prevMode = mode | ||||||
| 		} | 		} | ||||||
| 		return &groups[len(groups)-1] | 		return &groups[len(groups)-1] | ||||||
| 	} | 	} | ||||||
| 	for _, e := range es { | 	for _, e := range es { | ||||||
| 		switch e { | 		switch e { | ||||||
| 		case diff.Identity: | 		case diff.Identity: | ||||||
| 			lastStats(1).NumIdentical++ | 			lastStats('=').NumIdentical++ | ||||||
| 		case diff.UniqueX: | 		case diff.UniqueX: | ||||||
| 			lastStats(2).NumRemoved++ | 			lastStats('!').NumRemoved++ | ||||||
| 		case diff.UniqueY: | 		case diff.UniqueY: | ||||||
| 			lastStats(2).NumInserted++ | 			lastStats('!').NumInserted++ | ||||||
| 		case diff.Modified: | 		case diff.Modified: | ||||||
| 			lastStats(2).NumModified++ | 			lastStats('!').NumModified++ | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return groups | 	return groups | ||||||
|  | @ -444,6 +478,34 @@ func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) | ||||||
| // equal groups into adjacent unequal groups that currently result in a
 | // equal groups into adjacent unequal groups that currently result in a
 | ||||||
| // dual inserted/removed printout. This acts as a high-pass filter to smooth
 | // dual inserted/removed printout. This acts as a high-pass filter to smooth
 | ||||||
| // out high-frequency changes within the windowSize.
 | // out high-frequency changes within the windowSize.
 | ||||||
|  | //
 | ||||||
|  | // Example:
 | ||||||
|  | //
 | ||||||
|  | //	WindowSize: 16,
 | ||||||
|  | //	Input: [
 | ||||||
|  | //		{NumIdentical: 61},              // group 0
 | ||||||
|  | //		{NumRemoved: 3, NumInserted: 1}, // group 1
 | ||||||
|  | //		{NumIdentical: 6},               // ├── coalesce
 | ||||||
|  | //		{NumInserted: 2},                // ├── coalesce
 | ||||||
|  | //		{NumIdentical: 1},               // ├── coalesce
 | ||||||
|  | //		{NumRemoved: 9},                 // └── coalesce
 | ||||||
|  | //		{NumIdentical: 64},              // group 2
 | ||||||
|  | //		{NumRemoved: 3, NumInserted: 1}, // group 3
 | ||||||
|  | //		{NumIdentical: 6},               // ├── coalesce
 | ||||||
|  | //		{NumInserted: 2},                // ├── coalesce
 | ||||||
|  | //		{NumIdentical: 1},               // ├── coalesce
 | ||||||
|  | //		{NumRemoved: 7},                 // ├── coalesce
 | ||||||
|  | //		{NumIdentical: 1},               // ├── coalesce
 | ||||||
|  | //		{NumRemoved: 2},                 // └── coalesce
 | ||||||
|  | //		{NumIdentical: 63},              // group 4
 | ||||||
|  | //	]
 | ||||||
|  | //	Output: [
 | ||||||
|  | //		{NumIdentical: 61},
 | ||||||
|  | //		{NumIdentical: 7, NumRemoved: 12, NumInserted: 3},
 | ||||||
|  | //		{NumIdentical: 64},
 | ||||||
|  | //		{NumIdentical: 8, NumRemoved: 12, NumInserted: 3},
 | ||||||
|  | //		{NumIdentical: 63},
 | ||||||
|  | //	]
 | ||||||
| func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats { | func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats { | ||||||
| 	groups, groupsOrig := groups[:0], groups | 	groups, groupsOrig := groups[:0], groups | ||||||
| 	for i, ds := range groupsOrig { | 	for i, ds := range groupsOrig { | ||||||
|  | @ -463,3 +525,90 @@ func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStat | ||||||
| 	} | 	} | ||||||
| 	return groups | 	return groups | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // cleanupSurroundingIdentical scans through all unequal groups, and
 | ||||||
|  | // moves any leading sequence of equal elements to the preceding equal group and
 | ||||||
|  | // moves and trailing sequence of equal elements to the succeeding equal group.
 | ||||||
|  | //
 | ||||||
|  | // This is necessary since coalesceInterveningIdentical may coalesce edit groups
 | ||||||
|  | // together such that leading/trailing spans of equal elements becomes possible.
 | ||||||
|  | // Note that this can occur even with an optimal diffing algorithm.
 | ||||||
|  | //
 | ||||||
|  | // Example:
 | ||||||
|  | //
 | ||||||
|  | //	Input: [
 | ||||||
|  | //		{NumIdentical: 61},
 | ||||||
|  | //		{NumIdentical: 1 , NumRemoved: 11, NumInserted: 2}, // assume 3 leading identical elements
 | ||||||
|  | //		{NumIdentical: 67},
 | ||||||
|  | //		{NumIdentical: 7, NumRemoved: 12, NumInserted: 3},  // assume 10 trailing identical elements
 | ||||||
|  | //		{NumIdentical: 54},
 | ||||||
|  | //	]
 | ||||||
|  | //	Output: [
 | ||||||
|  | //		{NumIdentical: 64}, // incremented by 3
 | ||||||
|  | //		{NumRemoved: 9},
 | ||||||
|  | //		{NumIdentical: 67},
 | ||||||
|  | //		{NumRemoved: 9},
 | ||||||
|  | //		{NumIdentical: 64}, // incremented by 10
 | ||||||
|  | //	]
 | ||||||
|  | func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []diffStats { | ||||||
|  | 	var ix, iy int // indexes into sequence x and y
 | ||||||
|  | 	for i, ds := range groups { | ||||||
|  | 		// Handle equal group.
 | ||||||
|  | 		if ds.NumDiff() == 0 { | ||||||
|  | 			ix += ds.NumIdentical | ||||||
|  | 			iy += ds.NumIdentical | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Handle unequal group.
 | ||||||
|  | 		nx := ds.NumIdentical + ds.NumRemoved + ds.NumModified | ||||||
|  | 		ny := ds.NumIdentical + ds.NumInserted + ds.NumModified | ||||||
|  | 		var numLeadingIdentical, numTrailingIdentical int | ||||||
|  | 		for j := 0; j < nx && j < ny && eq(ix+j, iy+j); j++ { | ||||||
|  | 			numLeadingIdentical++ | ||||||
|  | 		} | ||||||
|  | 		for j := 0; j < nx && j < ny && eq(ix+nx-1-j, iy+ny-1-j); j++ { | ||||||
|  | 			numTrailingIdentical++ | ||||||
|  | 		} | ||||||
|  | 		if numIdentical := numLeadingIdentical + numTrailingIdentical; numIdentical > 0 { | ||||||
|  | 			if numLeadingIdentical > 0 { | ||||||
|  | 				// Remove leading identical span from this group and
 | ||||||
|  | 				// insert it into the preceding group.
 | ||||||
|  | 				if i-1 >= 0 { | ||||||
|  | 					groups[i-1].NumIdentical += numLeadingIdentical | ||||||
|  | 				} else { | ||||||
|  | 					// No preceding group exists, so prepend a new group,
 | ||||||
|  | 					// but do so after we finish iterating over all groups.
 | ||||||
|  | 					defer func() { | ||||||
|  | 						groups = append([]diffStats{{Name: groups[0].Name, NumIdentical: numLeadingIdentical}}, groups...) | ||||||
|  | 					}() | ||||||
|  | 				} | ||||||
|  | 				// Increment indexes since the preceding group would have handled this.
 | ||||||
|  | 				ix += numLeadingIdentical | ||||||
|  | 				iy += numLeadingIdentical | ||||||
|  | 			} | ||||||
|  | 			if numTrailingIdentical > 0 { | ||||||
|  | 				// Remove trailing identical span from this group and
 | ||||||
|  | 				// insert it into the succeeding group.
 | ||||||
|  | 				if i+1 < len(groups) { | ||||||
|  | 					groups[i+1].NumIdentical += numTrailingIdentical | ||||||
|  | 				} else { | ||||||
|  | 					// No succeeding group exists, so append a new group,
 | ||||||
|  | 					// but do so after we finish iterating over all groups.
 | ||||||
|  | 					defer func() { | ||||||
|  | 						groups = append(groups, diffStats{Name: groups[len(groups)-1].Name, NumIdentical: numTrailingIdentical}) | ||||||
|  | 					}() | ||||||
|  | 				} | ||||||
|  | 				// Do not increment indexes since the succeeding group will handle this.
 | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Update this group since some identical elements were removed.
 | ||||||
|  | 			nx -= numIdentical | ||||||
|  | 			ny -= numIdentical | ||||||
|  | 			groups[i] = diffStats{Name: ds.Name, NumRemoved: nx, NumInserted: ny} | ||||||
|  | 		} | ||||||
|  | 		ix += nx | ||||||
|  | 		iy += ny | ||||||
|  | 	} | ||||||
|  | 	return groups | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -393,6 +393,7 @@ func (s diffStats) Append(ds diffStats) diffStats { | ||||||
| // String prints a humanly-readable summary of coalesced records.
 | // String prints a humanly-readable summary of coalesced records.
 | ||||||
| //
 | //
 | ||||||
| // Example:
 | // Example:
 | ||||||
|  | //
 | ||||||
| //	diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
 | //	diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields"
 | ||||||
| func (s diffStats) String() string { | func (s diffStats) String() string { | ||||||
| 	var ss []string | 	var ss []string | ||||||
|  |  | ||||||
|  | @ -14,9 +14,11 @@ github.com/go-logr/logr | ||||||
| ## explicit; go 1.15 | ## explicit; go 1.15 | ||||||
| github.com/gogo/protobuf/proto | github.com/gogo/protobuf/proto | ||||||
| github.com/gogo/protobuf/sortkeys | github.com/gogo/protobuf/sortkeys | ||||||
| # github.com/golang/glog v1.0.0 | # github.com/golang/glog v1.2.4 | ||||||
| ## explicit; go 1.11 | ## explicit; go 1.19 | ||||||
| github.com/golang/glog | github.com/golang/glog | ||||||
|  | github.com/golang/glog/internal/logsink | ||||||
|  | github.com/golang/glog/internal/stackdump | ||||||
| # github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da | # github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da | ||||||
| ## explicit | ## explicit | ||||||
| github.com/golang/groupcache/lru | github.com/golang/groupcache/lru | ||||||
|  | @ -27,8 +29,8 @@ github.com/golang/protobuf/ptypes | ||||||
| github.com/golang/protobuf/ptypes/any | github.com/golang/protobuf/ptypes/any | ||||||
| github.com/golang/protobuf/ptypes/duration | github.com/golang/protobuf/ptypes/duration | ||||||
| github.com/golang/protobuf/ptypes/timestamp | github.com/golang/protobuf/ptypes/timestamp | ||||||
| # github.com/google/go-cmp v0.5.5 | # github.com/google/go-cmp v0.6.0 | ||||||
| ## explicit; go 1.8 | ## explicit; go 1.13 | ||||||
| github.com/google/go-cmp/cmp | github.com/google/go-cmp/cmp | ||||||
| github.com/google/go-cmp/cmp/internal/diff | github.com/google/go-cmp/cmp/internal/diff | ||||||
| github.com/google/go-cmp/cmp/internal/flags | github.com/google/go-cmp/cmp/internal/flags | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue