diff --git a/ascii_over_tcp_client.go b/ascii_over_tcp_client.go index cc263e4..d45b008 100644 --- a/ascii_over_tcp_client.go +++ b/ascii_over_tcp_client.go @@ -55,7 +55,7 @@ func (mb *asciiTCPTransporter) Send(aduRequest []byte) (aduResponse []byte, err } // Send the request - mb.logf("modbus: send %q\n", aduRequest) + mb.Debug("modbus: send %q\n", aduRequest) if _, err = mb.conn.Write(aduRequest); err != nil { return } @@ -78,6 +78,6 @@ func (mb *asciiTCPTransporter) Send(aduRequest []byte) (aduResponse []byte, err } } aduResponse = data[:length] - mb.logf("modbus: recv %q\n", aduResponse) + mb.Debug("modbus: recv %q\n", aduResponse) return } diff --git a/asciiclient.go b/asciiclient.go index d860fb3..0deffea 100644 --- a/asciiclient.go +++ b/asciiclient.go @@ -181,7 +181,7 @@ func (mb *asciiSerialTransporter) Send(aduRequest []byte) (aduResponse []byte, e mb.startCloseTimer() // Send the request - mb.logf("modbus: send % x\n", aduRequest) + mb.Debug("modbus: send % x\n", aduRequest) if _, err = mb.port.Write(aduRequest); err != nil { return } @@ -204,7 +204,7 @@ func (mb *asciiSerialTransporter) Send(aduRequest []byte) (aduResponse []byte, e } } aduResponse = data[:length] - mb.logf("modbus: recv % x\n", aduResponse) + mb.Debug("modbus: recv % x\n", aduResponse) return } diff --git a/client.go b/client.go index 2535b72..6d4c383 100644 --- a/client.go +++ b/client.go @@ -9,11 +9,6 @@ import ( "fmt" ) -// logger is the interface to the required logging functions -type logger interface { - Printf(format string, v ...interface{}) -} - // ClientHandler is the interface that groups the Packager and Transporter methods. type ClientHandler interface { Packager diff --git a/cmd/modbus-cli/main.go b/cmd/modbus-cli/main.go index 39c69a8..2c9c141 100644 --- a/cmd/modbus-cli/main.go +++ b/cmd/modbus-cli/main.go @@ -6,11 +6,11 @@ import ( "flag" "fmt" "io" - "io/ioutil" - "log" + "log/slog" "math" "net/url" "os" + "strconv" "strings" "text/tabwriter" "time" @@ -64,9 +64,11 @@ func main() { return } - logger := log.New(os.Stdout, "", 0) + logger := slog.Default() if *register > math.MaxUint16 || *register < 0 { - logger.Fatalf("invalid register value: %d", *register) + intRegister := *register + logger.Error("invalid register value: " + strconv.Itoa(intRegister)) + os.Exit(-1) } startReg := uint16(*register) @@ -89,10 +91,12 @@ func main() { handler, err := newHandler(opt) if err != nil { - logger.Fatal(err) + logger.Error(err.Error()) + os.Exit(-1) } if err := handler.Connect(); err != nil { - log.Fatal(err) + logger.Error(err.Error()) + os.Exit(-1) } defer handler.Close() @@ -100,9 +104,10 @@ func main() { result, err := exec(client, eo, *writeParseOrder, *register, *fnCode, *writeValue, *eType, *quantity) if err != nil && strings.Contains(err.Error(), "crc") && *ignoreCRCError { - logger.Printf("ignoring crc error: %+v\n", err) + logger.Info("ignoring crc error: %+v\n", err) } else if err != nil { - logger.Fatal(err) + logger.Error(err.Error()) + os.Exit(-1) } var res string @@ -116,16 +121,19 @@ func main() { } if err != nil { - logger.Fatal(err) + logger.Error(err.Error()) + os.Exit(-1) } - logger.Println(res) + logger.Info(res) if *filename != "" { if err := resultToFile([]byte(res), *filename); err != nil { - logger.Fatal(err) + logger.Error(err.Error()) + os.Exit(-1) } - logger.Printf("%s successfully written\n", *filename) + fName := *filename + logger.Info(fName + " successfully written\n") } } @@ -230,7 +238,7 @@ func convertToBytes(eType string, order binary.ByteOrder, forcedOrder string, va } func resultToFile(r []byte, filename string) error { - return ioutil.WriteFile(filename, r, 0644) + return os.WriteFile(filename, r, 0644) } func resultToRawString(r []byte, startReg int) (string, error) { @@ -408,16 +416,12 @@ func resultToString(r []byte, order binary.ByteOrder, forcedOrder string, varTyp return "", fmt.Errorf("unsupported datatype: %s", varType) } -type logger interface { - Printf(format string, v ...interface{}) -} - type option struct { address string slaveID int timeout time.Duration - logger logger + logger *slog.Logger rtu struct { baudrate int diff --git a/go.mod b/go.mod index c0cf2a9..0e93002 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/grid-x/modbus go 1.21 require ( - github.com/google/go-cmp v0.5.6 - github.com/grid-x/serial v0.0.0-20191104121038-e24bc9bf6f08 - pgregory.net/rapid v0.4.7 + github.com/google/go-cmp v0.6.0 + github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa + pgregory.net/rapid v1.1.0 ) diff --git a/go.sum b/go.sum index 249a3ad..845532a 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,6 @@ -github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/grid-x/serial v0.0.0-20191104121038-e24bc9bf6f08 h1:syBxnRYnSPUDdkdo5U4sy2roxBPQDjNiw4od7xlsABQ= -github.com/grid-x/serial v0.0.0-20191104121038-e24bc9bf6f08/go.mod h1:kdOd86/VGFWRrtkNwf1MPk0u1gIjc4Y7R2j7nhwc7Rk= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g= -pgregory.net/rapid v0.4.7/go.mod h1:UYpPVyjFHzYBGHIxLFoupi8vwk6rXNzRY9OMvVxFIOU= +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/grid-x/serial v0.0.0-20211107191517-583c7356b3aa h1:Rsn6ARgNkXrsXJIzhkE4vQr5Gbx2LvtEMv4BJOK4LyU= +github.com/grid-x/serial v0.0.0-20211107191517-583c7356b3aa/go.mod h1:kdOd86/VGFWRrtkNwf1MPk0u1gIjc4Y7R2j7nhwc7Rk= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/rtu_over_tcp_client.go b/rtu_over_tcp_client.go index 1a88080..5b28691 100644 --- a/rtu_over_tcp_client.go +++ b/rtu_over_tcp_client.go @@ -57,7 +57,7 @@ func (mb *rtuTCPTransporter) Send(aduRequest []byte) (aduResponse []byte, err er } // Send the request - mb.logf("modbus: send % x\n", aduRequest) + mb.Debug("modbus: send % x\n", aduRequest) if _, err = mb.conn.Write(aduRequest); err != nil { return } @@ -95,6 +95,6 @@ func (mb *rtuTCPTransporter) Send(aduRequest []byte) (aduResponse []byte, err er return } aduResponse = data[:n] - mb.logf("modbus: recv % x\n", aduResponse) + mb.Debug("modbus: recv % x\n", aduResponse) return } diff --git a/rtu_over_udp_client.go b/rtu_over_udp_client.go index 6c5483e..2a6bb51 100644 --- a/rtu_over_udp_client.go +++ b/rtu_over_udp_client.go @@ -3,6 +3,7 @@ package modbus import ( "fmt" "io" + logger "log/slog" "net" "sync" ) @@ -45,7 +46,7 @@ type rtuUDPTransporter struct { // Connect string Address string // Transmission logger - Logger logger + Logger *logger.Logger // UDP connection mu sync.Mutex @@ -120,7 +121,7 @@ func (mb *rtuUDPTransporter) Send(aduRequest []byte) (aduResponse []byte, err er func (mb *rtuUDPTransporter) logf(format string, v ...interface{}) { if mb.Logger != nil { - mb.Logger.Printf(format, v...) + mb.Logger.Info(format, v...) } } diff --git a/rtuclient.go b/rtuclient.go index 5d8d065..b4cf837 100644 --- a/rtuclient.go +++ b/rtuclient.go @@ -261,7 +261,7 @@ func (mb *rtuSerialTransporter) Send(aduRequest []byte) (aduResponse []byte, err mb.startCloseTimer() // Send the request - mb.logf("modbus: send % x\n", aduRequest) + mb.Debug("modbus: send % x\n", aduRequest) if _, err = mb.port.Write(aduRequest); err != nil { return } @@ -271,7 +271,7 @@ func (mb *rtuSerialTransporter) Send(aduRequest []byte) (aduResponse []byte, err time.Sleep(mb.calculateDelay(len(aduRequest) + bytesToRead)) data, err := readIncrementally(aduRequest[0], aduRequest[1], mb.port, time.Now().Add(mb.Config.Timeout)) - mb.logf("modbus: recv % x\n", data[:]) + mb.Debug("modbus: recv % x\n", data[:]) aduResponse = data return } diff --git a/rtuclient_prop_test.go b/rtuclient_prop_test.go index 4da20a6..3cccf5b 100644 --- a/rtuclient_prop_test.go +++ b/rtuclient_prop_test.go @@ -10,12 +10,12 @@ import ( func TestRTUEncodeDecode(t *testing.T) { rapid.Check(t, func(t *rapid.T) { packager := &rtuPackager{ - SlaveID: rapid.Byte().Draw(t, "SlaveID").(byte), + SlaveID: rapid.Byte().Draw(t, "SlaveID"), } pdu := &ProtocolDataUnit{ - FunctionCode: rapid.Byte().Draw(t, "FunctionCode").(byte), - Data: rapid.SliceOf(rapid.Byte()).Draw(t, "Data").([]byte), + FunctionCode: rapid.Byte().Draw(t, "FunctionCode"), + Data: rapid.SliceOf(rapid.Byte()).Draw(t, "Data"), } raw, err := packager.Encode(pdu) diff --git a/serial.go b/serial.go index a910b95..178c831 100644 --- a/serial.go +++ b/serial.go @@ -5,8 +5,10 @@ package modbus import ( + "context" "fmt" "io" + "log/slog" "sync" "time" @@ -24,7 +26,7 @@ type serialPort struct { // Serial port configuration. serial.Config - Logger logger + Logger *slog.Logger IdleTimeout time.Duration mu sync.Mutex @@ -69,9 +71,39 @@ func (mb *serialPort) close() (err error) { return } -func (mb *serialPort) logf(format string, v ...interface{}) { +func (mb *serialPort) Debug(format string, v ...interface{}) { if mb.Logger != nil { - mb.Logger.Printf(format, v...) + mb.Logger.Debug(format, v...) + } +} + +func (mb *serialPort) Info(format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.Info(format, v...) + } +} + +func (mb *serialPort) Error(format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.Error(format, v...) + } +} + +func (mb *serialPort) DebugContext(ctx context.Context, format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.DebugContext(ctx, format, v...) + } +} + +func (mb *serialPort) InfoContext(ctx context.Context, format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.InfoContext(ctx, format, v...) + } +} + +func (mb *serialPort) ErrorContext(ctx context.Context, format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.ErrorContext(ctx, format, v...) } } @@ -96,7 +128,7 @@ func (mb *serialPort) closeIdle() { } if idle := time.Since(mb.lastActivity); idle >= mb.IdleTimeout { - mb.logf("modbus: closing connection due to idle timeout: %v", idle) + mb.Debug("modbus: closing connection due to idle timeout: %v", idle) mb.close() } } diff --git a/tcpclient.go b/tcpclient.go index 57b3ffd..dcb872f 100644 --- a/tcpclient.go +++ b/tcpclient.go @@ -5,9 +5,11 @@ package modbus import ( + "context" "encoding/binary" "fmt" "io" + "log/slog" "net" "sync" "sync/atomic" @@ -136,7 +138,7 @@ type tcpTransporter struct { // Silent period after successful connection ConnectDelay time.Duration // Transmission logger - Logger logger + Logger *slog.Logger // TCP connection mu sync.Mutex @@ -183,7 +185,7 @@ func (mb *tcpTransporter) Send(aduRequest []byte) (aduResponse []byte, err error return } // Send data - mb.logf("modbus: send % x", aduRequest) + mb.Debug("modbus: send % x", aduRequest) if _, err = mb.conn.Write(aduRequest); err != nil { return } @@ -201,7 +203,7 @@ func (mb *tcpTransporter) Send(aduRequest []byte) (aduResponse []byte, err error continue } - mb.logf("modbus: close connection and retry, because of %v", err) + mb.Debug("modbus: close connection and retry, because of %v", err) mb.close() time.Sleep(mb.LinkRecoveryTimeout) @@ -216,7 +218,7 @@ func (mb *tcpTransporter) readResponse(aduRequest []byte, data []byte, recoveryD if err == nil { err = verify(aduRequest, aduResponse) if err == nil { - mb.logf("modbus: recv % x\n", aduResponse) + mb.Debug("modbus: recv % x\n", aduResponse) return // everything is OK } } @@ -382,9 +384,39 @@ func (mb *tcpTransporter) flush(b []byte) (err error) { return } -func (mb *tcpTransporter) logf(format string, v ...interface{}) { +func (mb *tcpTransporter) Debug(format string, v ...interface{}) { if mb.Logger != nil { - mb.Logger.Printf(format, v...) + mb.Logger.Debug(format, v...) + } +} + +func (mb *tcpTransporter) Info(format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.Info(format, v...) + } +} + +func (mb *tcpTransporter) Error(format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.Error(format, v...) + } +} + +func (mb *tcpTransporter) DebugContext(ctx context.Context, format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.DebugContext(ctx, format, v...) + } +} + +func (mb *tcpTransporter) InfoContext(ctx context.Context, format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.InfoContext(ctx, format, v...) + } +} + +func (mb *tcpTransporter) ErrorContext(ctx context.Context, format string, v ...interface{}) { + if mb.Logger != nil { + mb.Logger.ErrorContext(ctx, format, v...) } } @@ -407,7 +439,7 @@ func (mb *tcpTransporter) closeIdle() { } if idle := time.Since(mb.lastActivity); idle >= mb.IdleTimeout { - mb.logf("modbus: closing connection due to idle timeout: %v", idle) + mb.Debug("modbus: closing connection due to idle timeout: %v", idle) mb.close() } } diff --git a/tcpclient_prop_test.go b/tcpclient_prop_test.go index 87f18e2..530dd88 100644 --- a/tcpclient_prop_test.go +++ b/tcpclient_prop_test.go @@ -10,13 +10,13 @@ import ( func TestTCPEncodeDecode(t *testing.T) { rapid.Check(t, func(t *rapid.T) { packager := &tcpPackager{ - transactionID: rapid.Uint32().Draw(t, "transactionID").(uint32), - SlaveID: rapid.Byte().Draw(t, "SlaveID").(byte), + transactionID: rapid.Uint32().Draw(t, "transactionID"), + SlaveID: rapid.Byte().Draw(t, "SlaveID"), } pdu := &ProtocolDataUnit{ - FunctionCode: rapid.Byte().Draw(t, "FunctionCode").(byte), - Data: rapid.SliceOf(rapid.Byte()).Draw(t, "Data").([]byte), + FunctionCode: rapid.Byte().Draw(t, "FunctionCode"), + Data: rapid.SliceOf(rapid.Byte()).Draw(t, "Data"), } raw, err := packager.Encode(pdu) diff --git a/test/asciiclient_test.go b/test/asciiclient_test.go index 45f4449..dbef4a2 100644 --- a/test/asciiclient_test.go +++ b/test/asciiclient_test.go @@ -5,7 +5,7 @@ package test import ( - "log" + "log/slog" "os" "testing" @@ -30,7 +30,7 @@ func TestASCIIClientAdvancedUsage(t *testing.T) { handler.Parity = "E" handler.StopBits = 1 handler.SlaveID = 12 - handler.Logger = log.New(os.Stdout, "ascii: ", log.LstdFlags) + handler.Logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)) err := handler.Connect() if err != nil { t.Fatal(err) diff --git a/test/rtu_over_tcp_client_test.go b/test/rtu_over_tcp_client_test.go index 3615406..402c38b 100644 --- a/test/rtu_over_tcp_client_test.go +++ b/test/rtu_over_tcp_client_test.go @@ -5,7 +5,7 @@ package test import ( - "log" + "log/slog" "os" "testing" "time" @@ -28,7 +28,7 @@ func TestRTUOverTCPClientAdvancedUsage(t *testing.T) { handler := modbus.NewRTUOverTCPClientHandler(rtuOverTCPDevice) handler.Timeout = 5 * time.Second handler.SlaveID = 1 - handler.Logger = log.New(os.Stdout, "rtu over tcp: ", log.LstdFlags) + handler.Logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)) handler.Connect() defer handler.Close() diff --git a/test/rtuclient_test.go b/test/rtuclient_test.go index 4722ee1..dddccb7 100644 --- a/test/rtuclient_test.go +++ b/test/rtuclient_test.go @@ -5,7 +5,7 @@ package test import ( - "log" + "log/slog" "os" "testing" @@ -30,7 +30,7 @@ func TestRTUClientAdvancedUsage(t *testing.T) { handler.Parity = "E" handler.StopBits = 1 handler.SlaveID = 11 - handler.Logger = log.New(os.Stdout, "rtu: ", log.LstdFlags) + handler.Logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)) err := handler.Connect() if err != nil { t.Fatal(err) diff --git a/test/tcpclient_test.go b/test/tcpclient_test.go index 632814a..0363675 100644 --- a/test/tcpclient_test.go +++ b/test/tcpclient_test.go @@ -5,7 +5,7 @@ package test import ( - "log" + "log/slog" "os" "testing" "time" @@ -26,7 +26,7 @@ func TestTCPClientAdvancedUsage(t *testing.T) { handler := modbus.NewTCPClientHandler(tcpDevice) handler.Timeout = 5 * time.Second handler.SlaveID = 1 - handler.Logger = log.New(os.Stdout, "tcp: ", log.LstdFlags) + handler.Logger = slog.New(slog.NewJSONHandler(os.Stdout, nil)) handler.Connect() defer handler.Close() diff --git a/vendor/github.com/google/go-cmp/cmp/compare.go b/vendor/github.com/google/go-cmp/cmp/compare.go index 86d0903..0f5b8a4 100644 --- a/vendor/github.com/google/go-cmp/cmp/compare.go +++ b/vendor/github.com/google/go-cmp/cmp/compare.go @@ -5,7 +5,7 @@ // Package cmp determines equality of values. // // 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 may panic if it cannot compare the values. Its propensity towards // panicking means that its unsuitable for production environments where a @@ -13,21 +13,22 @@ // // The primary features of cmp are: // -// • When the default behavior of equality does not suit the needs of the test, -// custom equality functions can override the equality operation. -// For example, an equality function may report floats as equal so long as they -// are within some tolerance of each other. +// - When the default behavior of equality does not suit the test's needs, +// custom equality functions can override the equality operation. +// For example, an equality function may report floats as equal so long as +// they are within some tolerance of each other. // -// • Types that have an Equal method may use that method to determine equality. -// This allows package authors to determine the equality operation for the types -// that they define. +// - Types with an Equal method (e.g., [time.Time.Equal]) may use that method +// to determine equality. This allows package authors to determine +// the equality operation for the types that they define. // -// • If no custom equality functions are used and no Equal method is defined, -// equality is determined by recursively comparing the primitive kinds on both -// values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported -// fields are not compared by default; they result in panics unless suppressed -// by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly -// compared using the Exporter option. +// - If no custom equality functions are used and no Equal method is defined, +// equality is determined by recursively comparing the primitive kinds on +// both values, much like [reflect.DeepEqual]. Unlike [reflect.DeepEqual], +// unexported fields are not compared by default; they result in panics +// unless suppressed by using an [Ignore] option +// (see [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]) +// or explicitly compared using the [Exporter] option. package cmp import ( @@ -36,50 +37,52 @@ import ( "strings" "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/value" ) +// TODO(≥go1.18): Use any instead of interface{}. + // 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: // -// • Let S be the set of all Ignore, Transformer, and Comparer options that -// 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 the number of Transformer and Comparer options in S is greater than one, -// then Equal panics because it is ambiguous which option to use. -// If S contains a single Transformer, then use that to transform 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. -// Otherwise, evaluation proceeds to the next rule. +// - Let S be the set of all [Ignore], [Transformer], and [Comparer] options that +// 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 the number of [Transformer] and [Comparer] options in S is non-zero, +// then Equal panics because it is ambiguous which option to use. +// If S contains a single [Transformer], then use that to transform +// 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. +// Otherwise, evaluation proceeds to the next rule. // -// • 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 -// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and -// evaluation proceeds to the next rule. +// - 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 +// x.Equal(y) even if x or y is nil. Otherwise, no such method exists and +// evaluation proceeds to the next rule. // -// • Lastly, try to compare x and y based on their basic kinds. -// Simple kinds like booleans, integers, floats, complex numbers, strings, 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. +// - Lastly, try to compare x and y based on their basic kinds. +// Simple kinds like booleans, integers, floats, complex numbers, strings, +// 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. // // Structs are equal if recursively calling Equal on all fields report equal. -// If a struct contains unexported fields, Equal panics unless an Ignore option -// (e.g., cmpopts.IgnoreUnexported) ignores that field or the Exporter option -// explicitly permits comparing the unexported field. +// If a struct contains unexported fields, Equal panics unless an [Ignore] option +// (e.g., [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported]) ignores that 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 // 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, -// 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 // calling Equal on all non-ignored map entries report equal. // 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, -// 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, // 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. var t reflect.Type if !vx.IsValid() || !vy.IsValid() || vx.Type() != vy.Type() { - t = reflect.TypeOf((*interface{})(nil)).Elem() + t = anyType if vx.IsValid() { vvx := reflect.New(t).Elem() 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 { - v = sanitizeValue(v, f.Type().In(0)) if !s.dynChecker.Next() { 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 { - x = sanitizeValue(x, f.Type().In(0)) - y = sanitizeValue(y, f.Type().In(1)) if !s.dynChecker.Next() { 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] } -// 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) { var addr bool 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. // // 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 ... +// // See https://en.wikipedia.org/wiki/Triangular_number // // This sequence ensures that the cost of checks drops significantly as diff --git a/vendor/github.com/google/go-cmp/cmp/export_unsafe.go b/vendor/github.com/google/go-cmp/cmp/export.go similarity index 95% rename from vendor/github.com/google/go-cmp/cmp/export_unsafe.go rename to vendor/github.com/google/go-cmp/cmp/export.go index 21eb548..29f82fe 100644 --- a/vendor/github.com/google/go-cmp/cmp/export_unsafe.go +++ b/vendor/github.com/google/go-cmp/cmp/export.go @@ -2,8 +2,6 @@ // 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 ( @@ -11,8 +9,6 @@ import ( "unsafe" ) -const supportExporters = true - // retrieveUnexportedField uses unsafe to forcibly retrieve any field from // a struct such that the value has read-write permissions. // diff --git a/vendor/github.com/google/go-cmp/cmp/export_panic.go b/vendor/github.com/google/go-cmp/cmp/export_panic.go deleted file mode 100644 index 5ff0b42..0000000 --- a/vendor/github.com/google/go-cmp/cmp/export_panic.go +++ /dev/null @@ -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") -} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go index 1daaaac..36062a6 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_disable.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build !cmp_debug // +build !cmp_debug package diff diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go index 4b91dbc..a3b97a1 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/debug_enable.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build cmp_debug // +build cmp_debug package diff diff --git a/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go index bc196b1..a248e54 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/diff/diff.go @@ -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 // needed to convert one list into the other. The following invariants for // the edit-script are maintained: -// • eq == (es.Dist()==0) -// • nx == es.LenX() -// • ny == es.LenY() +// - eq == (es.Dist()==0) +// - nx == es.LenX() +// - ny == es.LenY() // // 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 @@ -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. // Invariants: - // • 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx - // • 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny + // - 0 ≤ fwdPath.X ≤ (fwdFrontier.X, revFrontier.X) ≤ revPath.X ≤ nx + // - 0 ≤ fwdPath.Y ≤ (fwdFrontier.Y, revFrontier.Y) ≤ revPath.Y ≤ ny // // In general: - // • fwdFrontier.X < revFrontier.X - // • fwdFrontier.Y < revFrontier.Y + // - fwdFrontier.X < revFrontier.X + // - fwdFrontier.Y < revFrontier.Y + // // Unless, it is time for the algorithm to terminate. fwdPath := path{+1, point{0, 0}, make(EditScript, 0, (nx+ny)/2)} revPath := path{-1, point{nx, ny}, make(EditScript, 0)} @@ -195,19 +196,21 @@ func Difference(nx, ny int, f EqualFunc) (es EditScript) { // computing sub-optimal edit-scripts between two lists. // // The algorithm is approximately as follows: - // • 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 end (the bottom-right corner). The goal of - // the search is connect with the search from the opposite corner. - // • As we search, we build a path in a greedy manner, where the first - // match seen is added to the path (this is sub-optimal, but provides a - // decent result in practice). When matches are found, we try the next pair - // of symbols in the lists and follow all matches as far as possible. - // • When searching for matches, we search along a diagonal going through - // 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. + // - 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 end (the bottom-right corner). + // The goal of the search is connect with the search + // from the opposite corner. + // - As we search, we build a path in a greedy manner, + // where the first match seen is added to the path (this is sub-optimal, + // but provides a decent result in practice). When matches are found, + // we try the next pair of symbols in the lists and follow all matches + // as far as possible. + // - When searching for matches, we search along a diagonal going through + // 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. // This algorithm is correct even if searching only in the forward direction // or in the reverse direction. We do both because it is commonly observed @@ -389,6 +392,7 @@ type point struct{ X, Y int } 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. +// // [0 1 2 3 4 5 ...] => [0 -1 +1 -2 +2 ...] func zigzag(x int) int { if x&1 != 0 { diff --git a/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go b/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go deleted file mode 100644 index 82d1d7f..0000000 --- a/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_legacy.go +++ /dev/null @@ -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 diff --git a/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go b/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go deleted file mode 100644 index 8646f05..0000000 --- a/vendor/github.com/google/go-cmp/cmp/internal/flags/toolchain_recent.go +++ /dev/null @@ -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 diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/name.go b/vendor/github.com/google/go-cmp/cmp/internal/value/name.go index b6c12ce..7b498bb 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/name.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/name.go @@ -9,6 +9,8 @@ import ( "strconv" ) +var anyType = reflect.TypeOf((*interface{})(nil)).Elem() + // TypeString is nearly identical to reflect.Type.String, // but has an additional option to specify that full type names be used. 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, // 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. if t.Name() != "" { if qualified && t.PkgPath() != "" { diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go b/vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go similarity index 97% rename from vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go rename to vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go index a605953..e5dfff6 100644 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_unsafe.go +++ b/vendor/github.com/google/go-cmp/cmp/internal/value/pointer.go @@ -2,8 +2,6 @@ // 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 ( diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go b/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go deleted file mode 100644 index 44f4a5a..0000000 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/pointer_purego.go +++ /dev/null @@ -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 -} diff --git a/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go b/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go deleted file mode 100644 index 9147a29..0000000 --- a/vendor/github.com/google/go-cmp/cmp/internal/value/zero.go +++ /dev/null @@ -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 -} diff --git a/vendor/github.com/google/go-cmp/cmp/options.go b/vendor/github.com/google/go-cmp/cmp/options.go index e57b9eb..754496f 100644 --- a/vendor/github.com/google/go-cmp/cmp/options.go +++ b/vendor/github.com/google/go-cmp/cmp/options.go @@ -13,15 +13,15 @@ import ( "github.com/google/go-cmp/cmp/internal/function" ) -// Option configures for specific behavior of Equal and Diff. In particular, -// the fundamental Option functions (Ignore, Transformer, and Comparer), +// Option configures for specific behavior of [Equal] and [Diff]. In particular, +// the fundamental Option functions ([Ignore], [Transformer], and [Comparer]), // configure how equality is determined. // -// The fundamental options may be composed with filters (FilterPath and -// FilterValues) to control the scope over which they are applied. +// The fundamental options may be composed with filters ([FilterPath] and +// [FilterValues]) to control the scope over which they are applied. // -// The cmp/cmpopts package provides helper functions for creating options that -// may be used with Equal and Diff. +// The [github.com/google/go-cmp/cmp/cmpopts] package provides helper functions +// for creating options that may be used with [Equal] and [Diff]. type Option interface { // filter applies all filters and returns the option that remains. // Each option may only read s.curPath and call s.callTTBFunc. @@ -33,6 +33,7 @@ type Option interface { } // applicableOption represents the following types: +// // Fundamental: ignore | validator | *comparer | *transformer // Grouping: Options type applicableOption interface { @@ -43,6 +44,7 @@ type applicableOption interface { } // coreOption represents the following types: +// // Fundamental: ignore | validator | *comparer | *transformer // Filters: *pathFilter | *valuesFilter type coreOption interface { @@ -54,9 +56,9 @@ type core struct{} 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 -// 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. // // 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, ", ")) } -// FilterPath returns a new Option where opt is only evaluated if filter f -// returns true for the current Path in the value tree. +// FilterPath returns a new [Option] where opt is only evaluated if filter f +// 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 // provides an opportunity to ignore such cases. The filter function must be // symmetric such that the filter result is identical regardless of whether the // missing value is from x or y. // -// The option passed in may be an Ignore, Transformer, Comparer, Options, or -// a previously filtered Option. +// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or +// a previously filtered [Option]. func FilterPath(f func(Path) bool, opt Option) Option { if f == nil { 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) } -// 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 // 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 @@ -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 // different concrete types that both implement T. // -// The option passed in may be an Ignore, Transformer, Comparer, Options, or -// a previously filtered Option. +// The option passed in may be an [Ignore], [Transformer], [Comparer], [Options], or +// a previously filtered [Option]. func FilterValues(f interface{}, opt Option) Option { v := reflect.ValueOf(f) 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) } -// Ignore is an Option that causes all comparisons to be ignored. -// This value is intended to be combined with FilterPath or FilterValues. -// It is an error to pass an unfiltered Ignore option to Equal. +// Ignore is an [Option] that causes all comparisons to be ignored. +// This value is intended to be combined with [FilterPath] or [FilterValues]. +// It is an error to pass an unfiltered Ignore option to [Equal]. func Ignore() Option { return ignore{} } 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 if _, ok := reflect.New(t).Interface().(error); ok { help = "consider using cmpopts.EquateErrors to compare error values" + } else if t.Comparable() { + help = "consider using cmpopts.EquateComparable to compare comparable Go types" } } else { // 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 + `)*$`) -// 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. // // 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 // 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 -// 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, -// consider using cmpopts.AcyclicTransformer, which adds a filter -// to prevent the transformer from being recursively applied upon itself. +// consider using [github.com/google/go-cmp/cmp/cmpopts.AcyclicTransformer], +// 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 -// transformation PathStep (and eventually shown in the Diff output). +// 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). // The name must be a valid identifier or qualified identifier in Go syntax. // If empty, an arbitrary name is used. 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)) } -// 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. // // 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. // // The equality function must be: -// • Symmetric: equal(x, y) == equal(y, x) -// • Deterministic: equal(x, y) == equal(x, y) -// • Pure: equal(x, y) does not modify x or y +// - Symmetric: equal(x, y) == equal(y, x) +// - Deterministic: equal(x, y) == equal(x, y) +// - Pure: equal(x, y) does not modify x or y func Comparer(f interface{}) Option { v := reflect.ValueOf(f) 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)) } -// 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. // // Users of this option must understand that comparing on unexported fields // 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 // defined in an internal package where the semantic meaning of an unexported // 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 // 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 -// 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. -// 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 *regexp.Regexp) bool { return x.String() == y.String() }) // -// In other cases, the cmpopts.IgnoreUnexported option can be used to ignore -// all unexported fields on specified struct types. +// In other cases, the [github.com/google/go-cmp/cmp/cmpopts.IgnoreUnexported] +// option can be used to ignore all unexported fields on specified struct types. func Exporter(f func(reflect.Type) bool) Option { - if !supportExporters { - panic("Exporter is not supported on purego builds") - } return exporter(f) } @@ -413,10 +415,10 @@ func (exporter) filter(_ *state, _ reflect.Type, _, _ reflect.Value) applicableO 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. // -// See Exporter for the proper use of this option. +// See [Exporter] for the proper use of this option. func AllowUnexported(types ...interface{}) Option { m := make(map[reflect.Type]bool) for _, typ := range types { @@ -430,7 +432,7 @@ func AllowUnexported(types ...interface{}) Option { } // 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 { _ [0]func() // Make Result incomparable flags resultFlags @@ -443,7 +445,7 @@ func (r Result) Equal() bool { } // 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 { return r.flags&reportByIgnore != 0 } @@ -453,7 +455,7 @@ func (r Result) ByMethod() bool { 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 { return r.flags&reportByFunc != 0 } @@ -476,7 +478,7 @@ const ( 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 // 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 diff --git a/vendor/github.com/google/go-cmp/cmp/path.go b/vendor/github.com/google/go-cmp/cmp/path.go index f01eff3..c3c1456 100644 --- a/vendor/github.com/google/go-cmp/cmp/path.go +++ b/vendor/github.com/google/go-cmp/cmp/path.go @@ -14,9 +14,9 @@ import ( "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. -// 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. // // 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 // these types as values of this type will be returned by this package. // -// Implementations of this interface are -// StructField, SliceIndex, MapIndex, Indirect, TypeAssertion, and Transform. +// Implementations of this interface: +// - [StructField] +// - [SliceIndex] +// - [MapIndex] +// - [Indirect] +// - [TypeAssertion] +// - [Transform] type PathStep interface { String() string @@ -41,13 +46,13 @@ type PathStep interface { // 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: - // • For StructField, both are not interface-able if the current field - // is unexported and the struct type is not explicitly permitted by - // an Exporter to traverse unexported fields. - // • For SliceIndex, one may be invalid if an element is missing from - // either the x or y slice. - // • For MapIndex, one may be invalid if an entry is missing from - // either the x or y map. + // - For StructField, both are not interface-able if the current field + // is unexported and the struct type is not explicitly permitted by + // an Exporter to traverse unexported fields. + // - For SliceIndex, one may be invalid if an element is missing from + // either the x or y slice. + // - For MapIndex, one may be invalid if an entry is missing from + // either the x or y map. // // The provided values must not be mutated. Values() (vx, vy reflect.Value) @@ -70,8 +75,9 @@ func (pa *Path) pop() { *pa = (*pa)[:len(*pa)-1] } -// Last returns the last PathStep in the Path. -// If the path is empty, this returns a non-nil PathStep that reports a nil Type. +// Last returns the last [PathStep] in the Path. +// If the path is empty, this returns a non-nil [PathStep] +// that reports a nil [PathStep.Type]. func (pa Path) Last() PathStep { 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. // 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. -// 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 { if i < 0 { i = len(pa) + i @@ -94,6 +101,7 @@ func (pa Path) Index(i int) PathStep { // The simplified path only contains struct field accesses. // // For example: +// // MyMap.MySlices.MyField func (pa Path) String() string { var ss []string @@ -108,6 +116,7 @@ func (pa Path) String() string { // GoString returns the path to a specific node using Go syntax. // // For example: +// // (*root.MyMap["key"].(*mypkg.MyStruct).MySlices)[2][3].MyField func (pa Path) GoString() string { var ssPre, ssPost []string @@ -159,14 +168,15 @@ func (ps pathStep) String() string { if ps.typ == nil { return "" } - s := ps.typ.String() + s := value.TypeString(ps.typ, false) if s == "" || strings.ContainsAny(s, "{}\n") { return "root" // Type too simple or complex to print } 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 { pathStep @@ -178,7 +188,7 @@ type structField struct { unexported bool mayForce bool // Forcibly allow visibility 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 } @@ -202,10 +212,11 @@ func (sf StructField) String() string { return fmt.Sprintf(".%s", sf.name) } func (sf StructField) Name() string { return sf.name } // 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 } -// 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 { pathStep @@ -245,12 +256,12 @@ func (si SliceIndex) Key() int { // 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. // -// Key is guaranteed to return -1 if and only if the indexes returned -// by SplitKeys are not the same. SplitKeys will never return -1 for +// [SliceIndex.Key] is guaranteed to return -1 if and only if the indexes +// returned by SplitKeys are not the same. SplitKeys will never return -1 for // both indexes. 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 { pathStep @@ -264,7 +275,7 @@ func (mi MapIndex) String() string { return fmt.Sprintf("[%#v]", // Key is the value of the map 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 { 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) 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 { pathStep @@ -282,9 +293,10 @@ type typeAssertion struct { 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) 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 { 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) 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 is the function pointer to the transformer function. 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. func (tf Transform) Option() Option { return tf.trans } diff --git a/vendor/github.com/google/go-cmp/cmp/report_compare.go b/vendor/github.com/google/go-cmp/cmp/report_compare.go index 104bb30..2050bf6 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_compare.go +++ b/vendor/github.com/google/go-cmp/cmp/report_compare.go @@ -7,8 +7,6 @@ package cmp import ( "fmt" "reflect" - - "github.com/google/go-cmp/cmp/internal/value" ) // 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. - 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 { case diffUnknown, diffIdentical: // Format Equal. @@ -245,11 +246,11 @@ func (opts formatOptions) formatDiffList(recs []reportRecord, k reflect.Kind, pt var isZero bool switch opts.DiffMode { case diffIdentical: - isZero = value.IsZero(r.Value.ValueX) || value.IsZero(r.Value.ValueY) + isZero = r.Value.ValueX.IsZero() || r.Value.ValueY.IsZero() case diffRemoved: - isZero = value.IsZero(r.Value.ValueX) + isZero = r.Value.ValueX.IsZero() case diffInserted: - isZero = value.IsZero(r.Value.ValueY) + isZero = r.Value.ValueY.IsZero() } if isZero { continue diff --git a/vendor/github.com/google/go-cmp/cmp/report_reflect.go b/vendor/github.com/google/go-cmp/cmp/report_reflect.go index 33f0357..e39f422 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_reflect.go +++ b/vendor/github.com/google/go-cmp/cmp/report_reflect.go @@ -16,6 +16,13 @@ import ( "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 { // AvoidStringer controls whether to avoid calling custom stringer // 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++ { vv := v.Field(i) - if value.IsZero(vv) { + if vv.IsZero() { continue // Elide fields with zero values } if len(list) == maxLen { @@ -192,7 +199,7 @@ func (opts formatOptions) FormatValue(v reflect.Value, parentKind reflect.Kind, break } sf := t.Field(i) - if supportExporters && !isExported(sf.Name) { + if !isExported(sf.Name) { vv = retrieveUnexportedField(v, sf, true) } 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. - if t.Elem() == reflect.TypeOf(byte(0)) { + if t.Elem() == byteType { 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 { 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() - skipType = true // Let the underlying value print the type instead + // 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 + } out = opts.FormatValue(v.Elem(), t.Kind(), ptrs) out = wrapTrunkReference(ptrRef, opts.PrintAddresses, 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, // 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) default: panic(fmt.Sprintf("%v kind not handled", v.Kind())) diff --git a/vendor/github.com/google/go-cmp/cmp/report_slices.go b/vendor/github.com/google/go-cmp/cmp/report_slices.go index 2ad3bc8..23e444f 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_slices.go +++ b/vendor/github.com/google/go-cmp/cmp/report_slices.go @@ -80,7 +80,7 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { } // Use specialized string diffing for longer slices or strings. - const minLength = 64 + const minLength = 32 return vx.Len() >= minLength && vy.Len() >= minLength } @@ -104,7 +104,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { case t.Kind() == reflect.String: sx, sy = vx.String(), vy.String() 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()) isString = true case t.Kind() == reflect.Array: @@ -147,7 +147,10 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { }) efficiencyLines := float64(esLines.Dist()) / float64(len(esLines)) efficiencyBytes := float64(esBytes.Dist()) / float64(len(esBytes)) - isPureLinedText = efficiencyLines < 4*efficiencyBytes + quotedLength := len(strconv.Quote(sx + sy)) + unquotedLength := len(sx) + len(sy) + escapeExpansionRatio := float64(quotedLength) / float64(unquotedLength) + isPureLinedText = efficiencyLines < 4*efficiencyBytes || escapeExpansionRatio > 1.1 } } @@ -171,12 +174,13 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { // differences in a string literal. This format is more readable, // but has edge-cases where differences are visually indistinguishable. // This format is avoided under the following conditions: - // • A line starts with `"""` - // • A line starts with "..." - // • A line contains non-printable characters - // • Adjacent different lines differ only by whitespace + // - A line starts with `"""` + // - A line starts with "..." + // - A line contains non-printable characters + // - Adjacent different lines differ only by whitespace // // For example: + // // """ // ... // 3 identical lines // foo @@ -231,7 +235,7 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { var out textNode = &textWrap{Prefix: "(", Value: list2, Suffix: ")"} switch t.Kind() { case reflect.String: - if t != reflect.TypeOf(string("")) { + if t != stringType { out = opts.FormatType(t, out) } case reflect.Slice: @@ -326,12 +330,12 @@ func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { switch t.Kind() { case reflect.String: 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) } case reflect.Slice: 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) } } @@ -446,7 +450,6 @@ func (opts formatOptions) formatDiffSlice( // {NumIdentical: 3}, // {NumInserted: 1}, // ] -// func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) { var prevMode byte lastStats := func(mode byte) *diffStats { @@ -503,7 +506,6 @@ func coalesceAdjacentEdits(name string, es diff.EditScript) (groups []diffStats) // {NumIdentical: 8, NumRemoved: 12, NumInserted: 3}, // {NumIdentical: 63}, // ] -// func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStats { groups, groupsOrig := groups[:0], groups for i, ds := range groupsOrig { @@ -548,7 +550,6 @@ func coalesceInterveningIdentical(groups []diffStats, windowSize int) []diffStat // {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 { @@ -563,10 +564,10 @@ func cleanupSurroundingIdentical(groups []diffStats, eq func(i, j int) bool) []d nx := ds.NumIdentical + ds.NumRemoved + ds.NumModified ny := ds.NumIdentical + ds.NumInserted + ds.NumModified var numLeadingIdentical, numTrailingIdentical int - for i := 0; i < nx && i < ny && eq(ix+i, iy+i); i++ { + for j := 0; j < nx && j < ny && eq(ix+j, iy+j); j++ { numLeadingIdentical++ } - for i := 0; i < nx && i < ny && eq(ix+nx-1-i, iy+ny-1-i); i++ { + for j := 0; j < nx && j < ny && eq(ix+nx-1-j, iy+ny-1-j); j++ { numTrailingIdentical++ } if numIdentical := numLeadingIdentical + numTrailingIdentical; numIdentical > 0 { diff --git a/vendor/github.com/google/go-cmp/cmp/report_text.go b/vendor/github.com/google/go-cmp/cmp/report_text.go index 0fd46d7..388fcf5 100644 --- a/vendor/github.com/google/go-cmp/cmp/report_text.go +++ b/vendor/github.com/google/go-cmp/cmp/report_text.go @@ -393,6 +393,7 @@ func (s diffStats) Append(ds diffStats) diffStats { // String prints a humanly-readable summary of coalesced records. // // Example: +// // diffStats{Name: "Field", NumIgnored: 5}.String() => "5 ignored fields" func (s diffStats) String() string { var ss []string diff --git a/vendor/github.com/grid-x/serial/serial_bsd.go b/vendor/github.com/grid-x/serial/serial_bsd.go index 398870c..5e23da6 100644 --- a/vendor/github.com/grid-x/serial/serial_bsd.go +++ b/vendor/github.com/grid-x/serial/serial_bsd.go @@ -1,4 +1,4 @@ -// +build freebsd openbsd netbsd +// +build freebsd netbsd package serial diff --git a/vendor/github.com/grid-x/serial/serial_openbsd.go b/vendor/github.com/grid-x/serial/serial_openbsd.go new file mode 100644 index 0000000..01913aa --- /dev/null +++ b/vendor/github.com/grid-x/serial/serial_openbsd.go @@ -0,0 +1,90 @@ +package serial + +import ( + "fmt" + "syscall" + "unsafe" +) + +var baudRates = map[int]uint32{ + 50: syscall.B50, + 75: syscall.B75, + 110: syscall.B110, + 134: syscall.B134, + 150: syscall.B150, + 200: syscall.B200, + 300: syscall.B300, + 600: syscall.B600, + 1200: syscall.B1200, + 1800: syscall.B1800, + 2400: syscall.B2400, + 4800: syscall.B4800, + 9600: syscall.B9600, + 19200: syscall.B19200, + 38400: syscall.B38400, + 57600: syscall.B57600, + 115200: syscall.B115200, + 230400: syscall.B230400, +} + +var charSizes = map[int]uint32{ + 5: syscall.CS5, + 6: syscall.CS6, + 7: syscall.CS7, + 8: syscall.CS8, +} + +// syscallSelect is a wapper for syscall.Select that only returns error. +func syscallSelect(n int, r *syscall.FdSet, w *syscall.FdSet, e *syscall.FdSet, tv *syscall.Timeval) error { + return syscall.Select(n, r, w, e, tv) +} + +// tcsetattr sets terminal file descriptor parameters. +// See man tcsetattr(3). +func tcsetattr(fd int, termios *syscall.Termios) (err error) { + r, _, errno := syscall.Syscall(uintptr(syscall.SYS_IOCTL), + uintptr(fd), uintptr(syscall.TIOCSETA), uintptr(unsafe.Pointer(termios))) + if errno != 0 { + err = errno + return + } + if r != 0 { + err = fmt.Errorf("tcsetattr failed %v", r) + } + return +} + +// tcgetattr gets terminal file descriptor parameters. +// See man tcgetattr(3). +func tcgetattr(fd int, termios *syscall.Termios) (err error) { + r, _, errno := syscall.Syscall(uintptr(syscall.SYS_IOCTL), + uintptr(fd), uintptr(syscall.TIOCGETA), uintptr(unsafe.Pointer(termios))) + if errno != 0 { + err = errno + return + } + if r != 0 { + err = fmt.Errorf("tcgetattr failed %v", r) + return + } + return +} + +// fdget returns index and offset of fd in fds. +func fdget(fd int, fds *syscall.FdSet) (index, offset int) { + index = fd / (syscall.FD_SETSIZE / len(fds.Bits)) % len(fds.Bits) + offset = fd % (syscall.FD_SETSIZE / len(fds.Bits)) + return +} + +// fdset implements FD_SET macro. +func fdset(fd int, fds *syscall.FdSet) { + idx, pos := fdget(fd, fds) + fds.Bits[idx] = 1 << uint(pos) +} + +// fdisset implements FD_ISSET macro. +func fdisset(fd int, fds *syscall.FdSet) bool { + idx, pos := fdget(fd, fds) + return fds.Bits[idx]&(1<= 0, generated slices have minimum length of minLen. +// If maxLen >= 0, generated slices have maximum length of maxLen. SliceOfN panics if maxLen >= 0 +// and minLen > maxLen. +func SliceOfN[E any](elem *Generator[E], minLen int, maxLen int) *Generator[[]E] { assertValidRange(minLen, maxLen) - return newGenerator(&sliceGen{ - typ: reflect.SliceOf(elem.type_()), + return newGenerator[[]E](&sliceGen[E, struct{}]{ minLen: minLen, maxLen: maxLen, elem: elem, }) } -func SliceOfDistinct(elem *Generator, keyFn interface{}) *Generator { +// SliceOfDistinct is a shorthand for [SliceOfNDistinct](elem, -1, -1, keyFn). +func SliceOfDistinct[E any, K comparable](elem *Generator[E], keyFn func(E) K) *Generator[[]E] { return SliceOfNDistinct(elem, -1, -1, keyFn) } -func SliceOfNDistinct(elem *Generator, minLen int, maxLen int, keyFn interface{}) *Generator { +// SliceOfNDistinct creates a []E generator. Elements of each generated slice are distinct according to keyFn. +// If minLen >= 0, generated slices have minimum length of minLen. If maxLen >= 0, generated slices +// have maximum length of maxLen. SliceOfNDistinct panics if maxLen >= 0 and minLen > maxLen. +// [ID] helper can be used as keyFn to generate slices of distinct comparable elements. +func SliceOfNDistinct[E any, K comparable](elem *Generator[E], minLen int, maxLen int, keyFn func(E) K) *Generator[[]E] { assertValidRange(minLen, maxLen) - keyTyp := elem.type_() - if keyFn != nil { - t := reflect.TypeOf(keyFn) - assertCallable(t, elem.type_(), "keyFn") - keyTyp = t.Out(0) - } - assertf(keyTyp.Comparable(), "key type should be comparable (got %v)", keyTyp) - - return newGenerator(&sliceGen{ - typ: reflect.SliceOf(elem.type_()), + return newGenerator[[]E](&sliceGen[E, K]{ minLen: minLen, maxLen: maxLen, elem: elem, - keyTyp: keyTyp, - keyFn: reflect.ValueOf(keyFn), + keyFn: keyFn, }) } -type sliceGen struct { - typ reflect.Type +type sliceGen[E any, K comparable] struct { minLen int maxLen int - elem *Generator - keyTyp reflect.Type - keyFn reflect.Value + elem *Generator[E] + keyFn func(E) K } -func (g *sliceGen) String() string { - if g.keyTyp == nil { +func (g *sliceGen[E, K]) String() string { + if g.keyFn == nil { if g.minLen < 0 && g.maxLen < 0 { return fmt.Sprintf("SliceOf(%v)", g.elem) } else { return fmt.Sprintf("SliceOfN(%v, minLen=%v, maxLen=%v)", g.elem, g.minLen, g.maxLen) } } else { - key := "" - if g.keyFn.IsValid() { - key = fmt.Sprintf(", key=func(%v) %v", g.elem.type_(), g.keyTyp) - } - if g.minLen < 0 && g.maxLen < 0 { - return fmt.Sprintf("SliceOfDistinct(%v%v)", g.elem, key) + return fmt.Sprintf("SliceOfDistinct(%v, key=%T)", g.elem, g.keyFn) } else { - return fmt.Sprintf("SliceOfNDistinct(%v, minLen=%v, maxLen=%v%v)", g.elem, g.minLen, g.maxLen, key) + return fmt.Sprintf("SliceOfNDistinct(%v, minLen=%v, maxLen=%v, key=%T)", g.elem, g.minLen, g.maxLen, g.keyFn) } } } -func (g *sliceGen) type_() reflect.Type { - return g.typ -} - -func (g *sliceGen) value(t *T) value { - repeat := newRepeat(g.minLen, g.maxLen, -1) +func (g *sliceGen[E, K]) value(t *T) []E { + repeat := newRepeat(g.minLen, g.maxLen, -1, g.elem.String()) - var seen reflect.Value - if g.keyTyp != nil { - seen = reflect.MakeMapWithSize(reflect.MapOf(g.keyTyp, emptyStructType), repeat.avg()) + var seen map[K]struct{} + if g.keyFn != nil { + seen = make(map[K]struct{}, repeat.avg()) } - sl := reflect.MakeSlice(g.typ, 0, repeat.avg()) - for repeat.more(t.s, g.elem.String()) { - e := reflect.ValueOf(g.elem.value(t)) - if g.keyTyp == nil { - sl = reflect.Append(sl, e) + sl := make([]E, 0, repeat.avg()) + for repeat.more(t.s) { + e := g.elem.value(t) + if g.keyFn == nil { + sl = append(sl, e) } else { - k := e - if g.keyFn.IsValid() { - k = g.keyFn.Call([]reflect.Value{k})[0] - } - - if seen.MapIndex(k).IsValid() { + k := g.keyFn(e) + if _, ok := seen[k]; ok { repeat.reject() } else { - seen.SetMapIndex(k, emptyStructValue) - sl = reflect.Append(sl, e) + seen[k] = struct{}{} + sl = append(sl, e) } } } - return sl.Interface() + return sl } -func MapOf(key *Generator, val *Generator) *Generator { +// MapOf is a shorthand for [MapOfN](key, val, -1, -1). +func MapOf[K comparable, V any](key *Generator[K], val *Generator[V]) *Generator[map[K]V] { return MapOfN(key, val, -1, -1) } -func MapOfN(key *Generator, val *Generator, minLen int, maxLen int) *Generator { +// MapOfN creates a map[K]V generator. If minLen >= 0, generated maps have minimum length of minLen. +// If maxLen >= 0, generated maps have maximum length of maxLen. MapOfN panics if maxLen >= 0 +// and minLen > maxLen. +func MapOfN[K comparable, V any](key *Generator[K], val *Generator[V], minLen int, maxLen int) *Generator[map[K]V] { assertValidRange(minLen, maxLen) - assertf(key.type_().Comparable(), "key type should be comparable (got %v)", key.type_()) - return newGenerator(&mapGen{ - typ: reflect.MapOf(key.type_(), val.type_()), + return newGenerator[map[K]V](&mapGen[K, V]{ minLen: minLen, maxLen: maxLen, key: key, @@ -133,133 +120,75 @@ func MapOfN(key *Generator, val *Generator, minLen int, maxLen int) *Generator { }) } -func MapOfValues(val *Generator, keyFn interface{}) *Generator { +// MapOfValues is a shorthand for [MapOfNValues](val, -1, -1, keyFn). +func MapOfValues[K comparable, V any](val *Generator[V], keyFn func(V) K) *Generator[map[K]V] { return MapOfNValues(val, -1, -1, keyFn) } -func MapOfNValues(val *Generator, minLen int, maxLen int, keyFn interface{}) *Generator { +// MapOfNValues creates a map[K]V generator, where keys are generated by applying keyFn to values. +// If minLen >= 0, generated maps have minimum length of minLen. If maxLen >= 0, generated maps +// have maximum length of maxLen. MapOfNValues panics if maxLen >= 0 and minLen > maxLen. +func MapOfNValues[K comparable, V any](val *Generator[V], minLen int, maxLen int, keyFn func(V) K) *Generator[map[K]V] { assertValidRange(minLen, maxLen) - keyTyp := val.type_() - if keyFn != nil { - t := reflect.TypeOf(keyFn) - assertCallable(t, val.type_(), "keyFn") - keyTyp = t.Out(0) - } - assertf(keyTyp.Comparable(), "key type should be comparable (got %v)", keyTyp) - - return newGenerator(&mapGen{ - typ: reflect.MapOf(keyTyp, val.type_()), + return newGenerator[map[K]V](&mapGen[K, V]{ minLen: minLen, maxLen: maxLen, val: val, - keyTyp: keyTyp, - keyFn: reflect.ValueOf(keyFn), + keyFn: keyFn, }) } -type mapGen struct { - typ reflect.Type +type mapGen[K comparable, V any] struct { minLen int maxLen int - key *Generator - val *Generator - keyTyp reflect.Type - keyFn reflect.Value + key *Generator[K] + val *Generator[V] + keyFn func(V) K } -func (g *mapGen) String() string { - if g.keyTyp == nil { +func (g *mapGen[K, V]) String() string { + if g.key != nil { if g.minLen < 0 && g.maxLen < 0 { return fmt.Sprintf("MapOf(%v, %v)", g.key, g.val) } else { return fmt.Sprintf("MapOfN(%v, %v, minLen=%v, maxLen=%v)", g.key, g.val, g.minLen, g.maxLen) } } else { - key := "" - if g.keyFn.IsValid() { - key = fmt.Sprintf(", key=func(%v) %v", g.val.type_(), g.keyTyp) - } - if g.minLen < 0 && g.maxLen < 0 { - return fmt.Sprintf("MapOfValues(%v%v)", g.val, key) + return fmt.Sprintf("MapOfValues(%v, key=%T)", g.val, g.keyFn) } else { - return fmt.Sprintf("MapOfNValues(%v, minLen=%v, maxLen=%v%v)", g.val, g.minLen, g.maxLen, key) + return fmt.Sprintf("MapOfNValues(%v, minLen=%v, maxLen=%v, key=%T)", g.val, g.minLen, g.maxLen, g.keyFn) } } } -func (g *mapGen) type_() reflect.Type { - return g.typ -} - -func (g *mapGen) value(t *T) value { +func (g *mapGen[K, V]) value(t *T) map[K]V { label := g.val.String() if g.key != nil { label = g.key.String() + "," + label } - repeat := newRepeat(g.minLen, g.maxLen, -1) + repeat := newRepeat(g.minLen, g.maxLen, -1, label) - m := reflect.MakeMapWithSize(g.typ, repeat.avg()) - for repeat.more(t.s, label) { - var k, v reflect.Value - if g.keyTyp == nil { - k = reflect.ValueOf(g.key.value(t)) - v = reflect.ValueOf(g.val.value(t)) + m := make(map[K]V, repeat.avg()) + for repeat.more(t.s) { + var k K + var v V + if g.key != nil { + k = g.key.value(t) + v = g.val.value(t) } else { - v = reflect.ValueOf(g.val.value(t)) - k = v - if g.keyFn.IsValid() { - k = g.keyFn.Call([]reflect.Value{v})[0] - } + v = g.val.value(t) + k = g.keyFn(v) } - if m.MapIndex(k).IsValid() { + if _, ok := m[k]; ok { repeat.reject() } else { - m.SetMapIndex(k, v) - } - } - - return m.Interface() -} - -func ArrayOf(count int, elem *Generator) *Generator { - assertf(count >= 0 && count < 1024, "array element count should be in [0, 1024] (got %v)", count) - - return newGenerator(&arrayGen{ - typ: reflect.ArrayOf(count, elem.type_()), - count: count, - elem: elem, - }) -} - -type arrayGen struct { - typ reflect.Type - count int - elem *Generator -} - -func (g *arrayGen) String() string { - return fmt.Sprintf("ArrayOf(%v, %v)", g.count, g.elem) -} - -func (g *arrayGen) type_() reflect.Type { - return g.typ -} - -func (g *arrayGen) value(t *T) value { - a := reflect.Indirect(reflect.New(g.typ)) - - if g.count == 0 { - t.s.drawBits(0) - } else { - for i := 0; i < g.count; i++ { - e := reflect.ValueOf(g.elem.value(t)) - a.Index(i).Set(e) + m[k] = v } } - return a.Interface() + return m } diff --git a/vendor/pgregory.net/rapid/combinators.go b/vendor/pgregory.net/rapid/combinators.go index 991063d..b3b1808 100644 --- a/vendor/pgregory.net/rapid/combinators.go +++ b/vendor/pgregory.net/rapid/combinators.go @@ -8,48 +8,35 @@ package rapid import ( "fmt" - "reflect" + "math" "strings" ) const tryLabel = "try" -var ( - boolType = reflect.TypeOf(false) - tPtrType = reflect.TypeOf((*T)(nil)) - emptyInterfaceType = reflect.TypeOf([]interface{}{}).Elem() -) - -func Custom(fn interface{}) *Generator { - f := reflect.ValueOf(fn) - t := f.Type() - - assertCallable(t, tPtrType, "fn") - - return newGenerator(&customGen{ - typ: t.Out(0), - fn: f, +// Custom creates a generator which produces results of calling fn. In fn, values should be generated +// by calling other generators; it is invalid to return a value from fn without using any other generator. +// Custom is a primary way of creating user-defined generators. +func Custom[V any](fn func(*T) V) *Generator[V] { + return newGenerator[V](&customGen[V]{ + fn: fn, }) } -type customGen struct { - typ reflect.Type - fn reflect.Value +type customGen[V any] struct { + fn func(*T) V } -func (g *customGen) String() string { - return fmt.Sprintf("Custom(%v)", g.typ) +func (g *customGen[V]) String() string { + var v V + return fmt.Sprintf("Custom(%T)", v) } -func (g *customGen) type_() reflect.Type { - return g.typ -} - -func (g *customGen) value(t *T) value { +func (g *customGen[V]) value(t *T) V { return find(g.maybeValue, t, small) } -func (g *customGen) maybeValue(t *T) value { +func (g *customGen[V]) maybeValue(t *T) (V, bool) { t = newT(t.tb, t.s, flags.debug, nil) defer func() { @@ -60,57 +47,69 @@ func (g *customGen) maybeValue(t *T) value { } }() - return call(g.fn, reflect.ValueOf(t)) + return g.fn(t), true } -func filter(g *Generator, fn interface{}) *Generator { - f := reflect.ValueOf(fn) - t := f.Type() +// Deferred creates a generator which defers calling fn until attempting to produce a value. This allows +// to define recursive generators. +func Deferred[V any](fn func() *Generator[V]) *Generator[V] { + return newGenerator[V](&deferredGen[V]{ + fn: fn, + }) +} - assertCallable(t, g.type_(), "fn") - assertf(t.Out(0) == boolType, "fn should return bool, not %v", t.Out(0)) +type deferredGen[V any] struct { + g *Generator[V] + fn func() *Generator[V] +} - return newGenerator(&filteredGen{ - g: g, - fn: func(v value) bool { - return call(f, reflect.ValueOf(v)).(bool) - }, - }) +func (g *deferredGen[V]) String() string { + var v V + return fmt.Sprintf("Deferred(%T)", v) } -type filteredGen struct { - g *Generator - fn func(value) bool +func (g *deferredGen[V]) value(t *T) V { + if g.g == nil { + g.g = g.fn() + } + return g.g.value(t) } -func (g *filteredGen) String() string { - return fmt.Sprintf("%v.Filter(...)", g.g) +func filter[V any](g *Generator[V], fn func(V) bool) *Generator[V] { + return newGenerator[V](&filteredGen[V]{ + g: g, + fn: fn, + }) } -func (g *filteredGen) type_() reflect.Type { - return g.g.type_() +type filteredGen[V any] struct { + g *Generator[V] + fn func(V) bool } -func (g *filteredGen) value(t *T) value { +func (g *filteredGen[V]) String() string { + return fmt.Sprintf("%v.Filter(...)", g.g) +} + +func (g *filteredGen[V]) value(t *T) V { return find(g.maybeValue, t, small) } -func (g *filteredGen) maybeValue(t *T) value { +func (g *filteredGen[V]) maybeValue(t *T) (V, bool) { v := g.g.value(t) if g.fn(v) { - return v + return v, true } else { - return nil + var zero V + return zero, false } } -func find(gen func(*T) value, t *T, tries int) value { +func find[V any](gen func(*T) (V, bool), t *T, tries int) V { for n := 0; n < tries; n++ { i := t.s.beginGroup(tryLabel, false) - v := gen(t) - ok := v != nil + v, ok := gen(t) t.s.endGroup(i, !ok) - if ok { return v } @@ -119,103 +118,110 @@ func find(gen func(*T) value, t *T, tries int) value { panic(invalidData(fmt.Sprintf("failed to find suitable value in %d tries", tries))) } -func map_(g *Generator, fn interface{}) *Generator { - f := reflect.ValueOf(fn) - t := f.Type() - - assertCallable(t, g.type_(), "fn") - - return newGenerator(&mappedGen{ - typ: t.Out(0), - g: g, - fn: f, +// Map creates a generator producing fn(u) for each u produced by g. +func Map[U any, V any](g *Generator[U], fn func(U) V) *Generator[V] { + return newGenerator[V](&mappedGen[U, V]{ + g: g, + fn: fn, }) } -type mappedGen struct { - typ reflect.Type - g *Generator - fn reflect.Value -} - -func (g *mappedGen) String() string { - return fmt.Sprintf("%v.Map(func(...) %v)", g.g, g.typ) +type mappedGen[U any, V any] struct { + g *Generator[U] + fn func(U) V } -func (g *mappedGen) type_() reflect.Type { - return g.typ +func (g *mappedGen[U, V]) String() string { + return fmt.Sprintf("Map(%v, %T)", g.g, g.fn) } -func (g *mappedGen) value(t *T) value { - v := reflect.ValueOf(g.g.value(t)) - return call(g.fn, v) +func (g *mappedGen[U, V]) value(t *T) V { + return g.fn(g.g.value(t)) } -func Just(val interface{}) *Generator { - return SampledFrom([]interface{}{val}) +// Just creates a generator which always produces the given value. +// Just(val) is a shorthand for [SampledFrom]([]V{val}). +func Just[V any](val V) *Generator[V] { + return SampledFrom([]V{val}) } -func SampledFrom(slice interface{}) *Generator { - v := reflect.ValueOf(slice) - t := v.Type() +// SampledFrom creates a generator which produces values from the given slice. +// SampledFrom panics if slice is empty. +func SampledFrom[S ~[]E, E any](slice S) *Generator[E] { + assertf(len(slice) > 0, "slice should not be empty") - assertf(t.Kind() == reflect.Slice, "argument should be a slice, not %v", t.Kind()) - assertf(v.Len() > 0, "slice should not be empty") - - return newGenerator(&sampledGen{ - typ: t.Elem(), - slice: v, - n: v.Len(), + return newGenerator[E](&sampledGen[E]{ + slice: slice, }) } -type sampledGen struct { - typ reflect.Type - slice reflect.Value - n int +type sampledGen[E any] struct { + slice []E } -func (g *sampledGen) String() string { - if g.n == 1 { - return fmt.Sprintf("Just(%v)", g.slice.Index(0).Interface()) +func (g *sampledGen[E]) String() string { + if len(g.slice) == 1 { + return fmt.Sprintf("Just(%v)", g.slice[0]) } else { - return fmt.Sprintf("SampledFrom(%v %v)", g.n, g.typ) + return fmt.Sprintf("SampledFrom(%v %T)", len(g.slice), g.slice[0]) } } -func (g *sampledGen) type_() reflect.Type { - return g.typ +func (g *sampledGen[E]) value(t *T) E { + i := genIndex(t.s, len(g.slice), true) + + return g.slice[i] } -func (g *sampledGen) value(t *T) value { - i := genIndex(t.s, g.n, true) +// Permutation creates a generator which produces permutations of the given slice. +func Permutation[S ~[]E, E any](slice S) *Generator[S] { + return newGenerator[S](&permGen[S, E]{ + slice: slice, + }) +} - return g.slice.Index(i).Interface() +type permGen[S ~[]E, E any] struct { + slice S } -func OneOf(gens ...*Generator) *Generator { - assertf(len(gens) > 0, "at least one generator should be specified") +func (g *permGen[S, E]) String() string { + var zero E + return fmt.Sprintf("Permutation(%v %T)", len(g.slice), zero) +} - typ := gens[0].type_() - for _, g := range gens { - if g.type_() != gens[0].type_() { - typ = emptyInterfaceType - break - } +func (g *permGen[S, E]) value(t *T) S { + s := append(S(nil), g.slice...) + n := len(s) + m := n - 1 + if m < 0 { + m = 0 + } + + // shrink-friendly variant of Fisher–Yates shuffle: shrinks to lower number of smaller distance swaps + repeat := newRepeat(0, m, math.MaxInt, "permute") + for i := 0; repeat.more(t.s); i++ { + j, _, _ := genUintRange(t.s, uint64(i), uint64(n-1), false) + s[i], s[j] = s[j], s[i] } - return newGenerator(&oneOfGen{ - typ: typ, + return s +} + +// OneOf creates a generator which produces each value by selecting one of gens and producing a value from it. +// OneOf panics if gens is empty. +func OneOf[V any](gens ...*Generator[V]) *Generator[V] { + assertf(len(gens) > 0, "at least one generator should be specified") + + return newGenerator[V](&oneOfGen[V]{ gens: gens, }) } -type oneOfGen struct { - typ reflect.Type - gens []*Generator +type oneOfGen[V any] struct { + gens []*Generator[V] } -func (g *oneOfGen) String() string { +func (g *oneOfGen[V]) String() string { strs := make([]string, len(g.gens)) for i, g := range g.gens { strs[i] = g.String() @@ -224,49 +230,57 @@ func (g *oneOfGen) String() string { return fmt.Sprintf("OneOf(%v)", strings.Join(strs, ", ")) } -func (g *oneOfGen) type_() reflect.Type { - return g.typ -} - -func (g *oneOfGen) value(t *T) value { +func (g *oneOfGen[V]) value(t *T) V { i := genIndex(t.s, len(g.gens), true) return g.gens[i].value(t) } -func Ptr(elem *Generator, allowNil bool) *Generator { - return newGenerator(&ptrGen{ - typ: reflect.PtrTo(elem.type_()), +// Ptr creates a *E generator. If allowNil is true, Ptr can return nil pointers. +func Ptr[E any](elem *Generator[E], allowNil bool) *Generator[*E] { + return newGenerator[*E](&ptrGen[E]{ elem: elem, allowNil: allowNil, }) } -type ptrGen struct { - typ reflect.Type - elem *Generator +type ptrGen[E any] struct { + elem *Generator[E] allowNil bool } -func (g *ptrGen) String() string { +func (g *ptrGen[E]) String() string { return fmt.Sprintf("Ptr(%v, allowNil=%v)", g.elem, g.allowNil) } -func (g *ptrGen) type_() reflect.Type { - return g.typ -} - -func (g *ptrGen) value(t *T) value { +func (g *ptrGen[E]) value(t *T) *E { pNonNil := float64(1) if g.allowNil { pNonNil = 0.5 } if flipBiasedCoin(t.s, pNonNil) { - p := reflect.New(g.elem.type_()) - p.Elem().Set(reflect.ValueOf(g.elem.value(t))) - return p.Interface() + e := g.elem.value(t) + return &e } else { - return reflect.Zero(g.typ).Interface() + return nil } } + +func asAny[V any](g *Generator[V]) *Generator[any] { + return newGenerator[any](&asAnyGen[V]{ + gen: g, + }) +} + +type asAnyGen[V any] struct { + gen *Generator[V] +} + +func (g *asAnyGen[V]) String() string { + return fmt.Sprintf("%v.AsAny()", g.gen) +} + +func (g *asAnyGen[V]) value(t *T) any { + return g.gen.value(t) +} diff --git a/vendor/pgregory.net/rapid/data.go b/vendor/pgregory.net/rapid/data.go index 3791d39..d68b6de 100644 --- a/vendor/pgregory.net/rapid/data.go +++ b/vendor/pgregory.net/rapid/data.go @@ -7,14 +7,11 @@ package rapid import ( + "hash/maphash" "math" "math/bits" - "sync/atomic" - "time" ) -var seedCounter uint32 - type bitStream interface { drawBits(n int) uint64 beginGroup(label string, standalone bool) int @@ -26,7 +23,7 @@ func baseSeed() uint64 { return flags.seed } - return uint64(time.Now().UnixNano())<<32 + uint64(atomic.AddUint32(&seedCounter, 1)) + return new(maphash.Hash).Sum64() } type randomBitStream struct { @@ -125,7 +122,8 @@ func (rec *recordedBits) beginGroup(label string, standalone bool) int { } func (rec *recordedBits) endGroup(i int, discard bool) { - assertf((!rec.persist && rec.dataLen != i) || (rec.persist && len(rec.data) != rec.groups[i].begin), "group did not use any data from bitstream") + assertf(discard || (!rec.persist && rec.dataLen > i) || (rec.persist && len(rec.data) > rec.groups[i].begin), + "group did not use any data from bitstream; this is likely a result of Custom generator not calling any of the built-in generators") if !rec.persist { return diff --git a/vendor/pgregory.net/rapid/doc.go b/vendor/pgregory.net/rapid/doc.go index 6b32ec1..7d5d751 100644 --- a/vendor/pgregory.net/rapid/doc.go +++ b/vendor/pgregory.net/rapid/doc.go @@ -7,35 +7,51 @@ /* Package rapid implements utilities for property-based testing. -Rapid checks that properties you define hold for a large number +[Check] verifies that properties you define hold for a large number of automatically generated test cases. If a failure is found, rapid fails the current test and presents an automatically minimized version of the failing test case. -Here is what a trivial test using rapid looks like: - - package rapid_test - - import ( - "net" - "testing" - - "pgregory.net/rapid" - ) - - func TestParseValidIPv4(t *testing.T) { - const ipv4re = `(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])` + - `\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])` + - `\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])` + - `\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])` - - rapid.Check(t, func(t *rapid.T) { - addr := rapid.StringMatching(ipv4re).Draw(t, "addr").(string) - ip := net.ParseIP(addr) - if ip == nil || ip.String() != addr { - t.Fatalf("parsed %q into %v", addr, ip) - } - }) - } +[T.Repeat] is used to construct state machine (sometimes called "stateful" +or "model-based") tests. + +# Generators + +Primitives: + - [Bool] + - [Rune], [RuneFrom] + - [Byte], [ByteMin], [ByteMax], [ByteRange] + - [Int], [IntMin], [IntMax], [IntRange] + - [Int8], [Int8Min], [Int8Max], [Int8Range] + - [Int16], [Int16Min], [Int16Max], [Int16Range] + - [Int32], [Int32Min], [Int32Max], [Int32Range] + - [Int64], [Int64Min], [Int64Max], [Int64Range] + - [Uint], [UintMin], [UintMax], [UintRange] + - [Uint8], [Uint8Min], [Uint8Max], [Uint8Range] + - [Uint16], [Uint16Min], [Uint16Max], [Uint16Range] + - [Uint32], [Uint32Min], [Uint32Max], [Uint32Range] + - [Uint64], [Uint64Min], [Uint64Max], [Uint64Range] + - [Uintptr], [UintptrMin], [UintptrMax], [UintptrRange] + - [Float32], [Float32Min], [Float32Max], [Float32Range] + - [Float64], [Float64Min], [Float64Max], [Float64Range] + +Collections: + - [String], [StringMatching], [StringOf], [StringOfN], [StringN] + - [SliceOfBytesMatching] + - [SliceOf], [SliceOfN], [SliceOfDistinct], [SliceOfNDistinct] + - [Permutation] + - [MapOf], [MapOfN], [MapOfValues], [MapOfNValues] + +User-defined types: + - [Custom] + - [Make] + +Other: + - [Map], + - [Generator.Filter] + - [SampledFrom], [Just] + - [OneOf] + - [Deferred] + - [Ptr] */ package rapid diff --git a/vendor/pgregory.net/rapid/engine.go b/vendor/pgregory.net/rapid/engine.go index aced4e7..a2ea5c3 100644 --- a/vendor/pgregory.net/rapid/engine.go +++ b/vendor/pgregory.net/rapid/engine.go @@ -8,11 +8,12 @@ package rapid import ( "bytes" + "encoding/binary" "flag" "fmt" "log" "os" - "reflect" + "path/filepath" "regexp" "runtime" "strings" @@ -26,6 +27,9 @@ const ( invalidChecksMult = 10 exampleMaxTries = 1000 + maxTestTimeout = 24 * time.Hour + shrinkStepBound = 10 * time.Second // can be improved by taking average checkOnce runtime into account + tracebackLen = 32 tracebackStop = "pgregory.net/rapid.checkOnce" runtimePrefix = "runtime." @@ -34,12 +38,9 @@ const ( var ( flags cmdline - emptyStructType = reflect.TypeOf(struct{}{}) - emptyStructValue = reflect.ValueOf(struct{}{}) - tracebackBlacklist = map[string]bool{ - "pgregory.net/rapid.(*customGen).maybeValue.func1": true, - "pgregory.net/rapid.runAction.func1": true, + "pgregory.net/rapid.(*customGen[...]).maybeValue.func1": true, + "pgregory.net/rapid.runAction.func1": true, } ) @@ -58,7 +59,7 @@ type cmdline struct { func init() { flag.IntVar(&flags.checks, "rapid.checks", 100, "rapid: number of checks to perform") - flag.IntVar(&flags.steps, "rapid.steps", 100, "rapid: number of state machine steps to perform") + flag.IntVar(&flags.steps, "rapid.steps", 30, "rapid: average number of Repeat actions to execute") flag.StringVar(&flags.failfile, "rapid.failfile", "", "rapid: fail file to use to reproduce test failure") flag.BoolVar(&flags.nofailfile, "rapid.nofailfile", false, "rapid: do not write fail files on test failures") flag.Uint64Var(&flags.seed, "rapid.seed", 0, "rapid: PRNG seed to start with (0 to use a random one)") @@ -75,75 +76,148 @@ func assert(ok bool) { } } -func assertf(ok bool, format string, args ...interface{}) { +func assertf(ok bool, format string, args ...any) { if !ok { panic(fmt.Sprintf(format, args...)) } } func assertValidRange(min int, max int) { - assertf(max < 0 || min <= max, fmt.Sprintf("invalid range [%d, %d]", min, max)) + if max >= 0 && min > max { + panic(fmt.Sprintf("invalid range [%d, %d]", min, max)) + } +} + +func checkDeadline(tb tb) time.Time { + t, ok := tb.(*testing.T) + if !ok { + return time.Now().Add(maxTestTimeout) + } + d, ok := t.Deadline() + if !ok { + return time.Now().Add(maxTestTimeout) + } + return d +} + +func shrinkDeadline(deadline time.Time) time.Time { + d := time.Now().Add(flags.shrinkTime) + max := deadline.Add(-shrinkStepBound) // account for the fact that shrink deadline is checked before the step + if d.After(max) { + d = max + } + return d } // Check fails the current test if rapid can find a test case which falsifies prop. // // Property is falsified in case of a panic or a call to -// (*T).Fatalf, (*T).Fatal, (*T).Errorf, (*T).Error, (*T).FailNow or (*T).Fail. -func Check(t *testing.T, prop func(*T)) { +// [*T.Fatalf], [*T.Fatal], [*T.Errorf], [*T.Error], [*T.FailNow] or [*T.Fail]. +func Check(t TB, prop func(*T)) { t.Helper() - checkTB(t, prop) + checkTB(t, checkDeadline(t), prop) } // MakeCheck is a convenience function for defining subtests suitable for -// (*testing.T).Run. It allows you to write this: +// [*testing.T.Run]. It allows you to write this: // -// t.Run("subtest name", rapid.MakeCheck(func(t *rapid.T) { -// // test code -// })) +// t.Run("subtest name", rapid.MakeCheck(func(t *rapid.T) { +// // test code +// })) // // instead of this: // -// t.Run("subtest name", func(t *testing.T) { -// rapid.Check(t, func(t *rapid.T) { -// // test code -// }) -// }) -// +// t.Run("subtest name", func(t *testing.T) { +// rapid.Check(t, func(t *rapid.T) { +// // test code +// }) +// }) func MakeCheck(prop func(*T)) func(*testing.T) { return func(t *testing.T) { t.Helper() - checkTB(t, prop) + checkTB(t, checkDeadline(t), prop) + } +} + +// MakeFuzz creates a fuzz target for [*testing.F.Fuzz]: +// +// func FuzzFoo(f *testing.F) { +// f.Fuzz(rapid.MakeFuzz(func(t *rapid.T) { +// // test code +// })) +// } +func MakeFuzz(prop func(*T)) func(*testing.T, []byte) { + return func(t *testing.T, input []byte) { + t.Helper() + checkFuzz(t, prop, input) + } +} + +func checkFuzz(tb tb, prop func(*T), input []byte) { + tb.Helper() + + var buf []uint64 + for len(input) > 0 { + var tmp [8]byte + n := copy(tmp[:], input) + buf = append(buf, binary.LittleEndian.Uint64(tmp[:])) + input = input[n:] + } + + t := newT(tb, newBufBitStream(buf, false), true, nil) + err := checkOnce(t, prop) + + switch { + case err == nil: + // do nothing + case err.isInvalidData(): + tb.SkipNow() + case err.isStopTest(): + tb.Fatalf("[rapid] failed: %v", err) + default: + tb.Fatalf("[rapid] panic: %v\nTraceback:\n%v", err, traceback(err)) } } -func checkTB(tb tb, prop func(*T)) { +func checkTB(tb tb, deadline time.Time, prop func(*T)) { tb.Helper() + checks := flags.checks + if testing.Short() { + checks /= 5 + } + start := time.Now() - valid, invalid, seed, buf, err1, err2 := doCheck(tb, flags.failfile, flags.checks, baseSeed(), prop) + valid, invalid, earlyExit, seed, failfile, buf, err1, err2 := doCheck(tb, deadline, checks, baseSeed(), flags.failfile, true, prop) dt := time.Since(start) if err1 == nil && err2 == nil { - if valid == flags.checks { + if valid == checks || (earlyExit && valid > 0) { tb.Logf("[rapid] OK, passed %v tests (%v)", valid, dt) } else { tb.Errorf("[rapid] only generated %v valid tests from %v total (%v)", valid, valid+invalid, dt) } } else { - repr := fmt.Sprintf("-rapid.seed=%d", seed) - if flags.failfile != "" && seed == 0 { - repr = fmt.Sprintf("-rapid.failfile=%q", flags.failfile) - } else if !flags.nofailfile { - failfile := failFileName(tb.Name()) + if failfile == "" && !flags.nofailfile { + _, failfile = failFileName(tb.Name()) out := captureTestOutput(tb, prop, buf) err := saveFailFile(failfile, rapidVersion, out, seed, buf) - if err == nil { - repr = fmt.Sprintf("-rapid.failfile=%q (or -rapid.seed=%d)", failfile, seed) - } else { + if err != nil { tb.Logf("[rapid] %v", err) + failfile = "" } } + var repr string + switch { + case failfile != "" && seed != 0: + repr = fmt.Sprintf("-rapid.failfile=%q (or -rapid.seed=%d)", failfile, seed) + case failfile != "": + repr = fmt.Sprintf("-rapid.failfile=%q", failfile) + case seed != 0: + repr = fmt.Sprintf("-rapid.seed=%d", seed) + } + name := regexp.QuoteMeta(tb.Name()) if traceback(err1) == traceback(err2) { if err2.isStopTest() { @@ -163,21 +237,29 @@ func checkTB(tb tb, prop func(*T)) { } } -func doCheck(tb tb, failfile string, checks int, seed uint64, prop func(*T)) (int, int, uint64, []uint64, *testError, *testError) { +func doCheck(tb tb, deadline time.Time, checks int, seed uint64, failfile string, globFailFiles bool, prop func(*T)) (int, int, bool, uint64, string, []uint64, *testError, *testError) { tb.Helper() assertf(!tb.Failed(), "check function called with *testing.T which has already failed") + var failfiles []string if failfile != "" { + failfiles = []string{failfile} + } + if globFailFiles { + matches, _ := filepath.Glob(failFilePattern(tb.Name())) + failfiles = append(failfiles, matches...) + } + for _, failfile := range failfiles { buf, err1, err2 := checkFailFile(tb, failfile, prop) if err1 != nil || err2 != nil { - return 0, 0, 0, buf, err1, err2 + return 0, 0, false, 0, failfile, buf, err1, err2 } } - seed, valid, invalid, err1 := findBug(tb, checks, seed, prop) + valid, invalid, earlyExit, seed, err1 := findBug(tb, deadline, checks, seed, prop) if err1 == nil { - return valid, invalid, 0, nil, nil, nil + return valid, invalid, earlyExit, 0, "", nil, nil, nil } s := newRandomBitStream(seed, true) @@ -185,13 +267,13 @@ func doCheck(tb tb, failfile string, checks int, seed uint64, prop func(*T)) (in t.Logf("[rapid] trying to reproduce the failure") err2 := checkOnce(t, prop) if !sameError(err1, err2) { - return valid, invalid, seed, s.data, err1, err2 + return valid, invalid, false, seed, "", s.data, err1, err2 } t.Logf("[rapid] trying to minimize the failing test case") - buf, err3 := shrink(tb, s.recordedBits, err2, prop) + buf, err3 := shrink(tb, shrinkDeadline(deadline), s.recordedBits, err2, prop) - return valid, invalid, seed, buf, err2, err3 + return valid, invalid, false, seed, "", buf, err2, err3 } func checkFailFile(tb tb, failfile string, prop func(*T)) ([]uint64, *testError, *testError) { @@ -226,7 +308,7 @@ func checkFailFile(tb tb, failfile string, prop func(*T)) ([]uint64, *testError, return buf, err1, err2 } -func findBug(tb tb, checks int, seed uint64, prop func(*T)) (uint64, int, int, *testError) { +func findBug(tb tb, deadline time.Time, checks int, seed uint64, prop func(*T)) (int, int, bool, uint64, *testError) { tb.Helper() var ( @@ -236,39 +318,49 @@ func findBug(tb tb, checks int, seed uint64, prop func(*T)) (uint64, int, int, * invalid = 0 ) + var total time.Duration for valid < checks && invalid < checks*invalidChecksMult { - seed += uint64(valid) + uint64(invalid) + iter := valid + invalid + if iter > 0 && time.Until(deadline) < total/time.Duration(iter)*5 { + if t.shouldLog() { + t.Logf("[rapid] early exit after test #%v (%v)", iter, total) + } + return valid, invalid, true, 0, nil + } + + seed += uint64(iter) r.init(seed) - var start time.Time + start := time.Now() if t.shouldLog() { - t.Logf("[rapid] test #%v start (seed %v)", valid+invalid+1, seed) - start = time.Now() + t.Logf("[rapid] test #%v start (seed %v)", iter+1, seed) } err := checkOnce(t, prop) + dt := time.Since(start) + total += dt if err == nil { if t.shouldLog() { - t.Logf("[rapid] test #%v OK (%v)", valid+invalid+1, time.Since(start)) + t.Logf("[rapid] test #%v OK (%v)", iter+1, dt) } valid++ } else if err.isInvalidData() { if t.shouldLog() { - t.Logf("[rapid] test #%v invalid (%v)", valid+invalid+1, time.Since(start)) + t.Logf("[rapid] test #%v invalid (%v)", iter+1, dt) } invalid++ } else { if t.shouldLog() { - t.Logf("[rapid] test #%v failed: %v", valid+invalid+1, err) + t.Logf("[rapid] test #%v failed: %v", iter+1, err) } - return seed, valid, invalid, err + return valid, invalid, false, seed, err } } - return 0, valid, invalid, nil + return valid, invalid, false, 0, nil } func checkOnce(t *T, prop func(*T)) (err *testError) { - if t.tbLog && t.tb != nil { + if t.tbLog { t.tb.Helper() } defer func() { err = panicToError(recover(), 3) }() @@ -281,7 +373,7 @@ func checkOnce(t *T, prop func(*T)) (err *testError) { func captureTestOutput(tb tb, prop func(*T), buf []uint64) []byte { var b bytes.Buffer - l := log.New(&b, fmt.Sprintf("%s ", tb.Name()), log.Ldate|log.Ltime) // TODO: enable log.Lmsgprefix once all supported versions of Go have it + l := log.New(&b, fmt.Sprintf("[%v] ", tb.Name()), log.Lmsgprefix|log.Ldate|log.Ltime|log.Lmicroseconds) _ = checkOnce(newT(tb, newBufBitStream(buf, false), false, l), prop) return b.Bytes() } @@ -290,11 +382,11 @@ type invalidData string type stopTest string type testError struct { - data interface{} + data any traceback string } -func panicToError(p interface{}, skip int) *testError { +func panicToError(p any, skip int) *testError { if p == nil { return nil } @@ -365,32 +457,64 @@ func traceback(err *testError) string { return err.traceback } -type tb interface { +// TB is a common interface between [*testing.T], [*testing.B] and [*T]. +type TB interface { Helper() Name() string - Logf(format string, args ...interface{}) - Log(args ...interface{}) - Errorf(format string, args ...interface{}) - Error(args ...interface{}) - Fatalf(format string, args ...interface{}) - Fatal(args ...interface{}) + Logf(format string, args ...any) + Log(args ...any) + Skipf(format string, args ...any) + Skip(args ...any) + SkipNow() + Errorf(format string, args ...any) + Error(args ...any) + Fatalf(format string, args ...any) + Fatal(args ...any) FailNow() Fail() Failed() bool } +type tb TB // tb is a private copy of TB, made to avoid T having public fields + +type nilTB struct{} + +func (nilTB) Helper() {} +func (nilTB) Name() string { return "" } +func (nilTB) Logf(string, ...any) {} +func (nilTB) Log(...any) {} +func (nilTB) Skipf(string, ...any) { panic("call to TB.Skipf() outside a test") } +func (nilTB) Skip(...any) { panic("call to TB.Skip() outside a test") } +func (nilTB) SkipNow() { panic("call to TB.SkipNow() outside a test") } +func (nilTB) Errorf(string, ...any) { panic("call to TB.Errorf() outside a test") } +func (nilTB) Error(...any) { panic("call to TB.Error() outside a test") } +func (nilTB) Fatalf(string, ...any) { panic("call to TB.Fatalf() outside a test") } +func (nilTB) Fatal(...any) { panic("call to TB.Fatal() outside a test") } +func (nilTB) FailNow() { panic("call to TB.FailNow() outside a test") } +func (nilTB) Fail() { panic("call to TB.Fail() outside a test") } +func (nilTB) Failed() bool { panic("call to TB.Failed() outside a test") } + +// T is similar to [testing.T], but with extra bookkeeping for property-based tests. +// +// For tests to be reproducible, they should generally run in a single goroutine. +// If concurrency is unavoidable, methods on *T, such as [*testing.T.Helper] and [*T.Errorf], +// are safe for concurrent calls, but *Generator.Draw from a given *T is not. type T struct { tb // unnamed to force re-export of (*T).Helper() tbLog bool rawLog *log.Logger s bitStream draws int - refDraws []value + refDraws []any mu sync.RWMutex failed stopTest } -func newT(tb tb, s bitStream, tbLog bool, rawLog *log.Logger, refDraws ...value) *T { +func newT(tb tb, s bitStream, tbLog bool, rawLog *log.Logger, refDraws ...any) *T { + if tb == nil { + tb = nilTB{} + } + t := &T{ tb: tb, tbLog: tbLog, @@ -405,119 +529,93 @@ func newT(tb tb, s bitStream, tbLog bool, rawLog *log.Logger, refDraws ...value) testName = tb.Name() } - t.rawLog = log.New(os.Stdout, fmt.Sprintf("[%v] ", testName), 0) + t.rawLog = log.New(os.Stdout, fmt.Sprintf("[%v] ", testName), log.Lmsgprefix|log.Ldate|log.Ltime|log.Lmicroseconds) } return t } -func (t *T) draw(g *Generator, label string) value { - v := g.value(t) - - if len(t.refDraws) > 0 { - ref := t.refDraws[t.draws] - if !reflect.DeepEqual(v, ref) { - t.tb.Fatalf("draw %v differs: %#v vs expected %#v", t.draws, v, ref) - } - } - - if t.tbLog || t.rawLog != nil { - if label == "" { - label = fmt.Sprintf("#%v", t.draws) - } - - if t.tbLog && t.tb != nil { - t.tb.Helper() - } - t.Logf("[rapid] draw %v: %#v", label, v) - } - - t.draws++ - - return v -} - func (t *T) shouldLog() bool { - return t.rawLog != nil || (t.tbLog && t.tb != nil) + return t.rawLog != nil || t.tbLog } -func (t *T) Logf(format string, args ...interface{}) { +func (t *T) Logf(format string, args ...any) { if t.rawLog != nil { t.rawLog.Printf(format, args...) - } else if t.tbLog && t.tb != nil { + } else if t.tbLog { t.tb.Helper() t.tb.Logf(format, args...) } } -func (t *T) Log(args ...interface{}) { +func (t *T) Log(args ...any) { if t.rawLog != nil { t.rawLog.Print(args...) - } else if t.tbLog && t.tb != nil { + } else if t.tbLog { t.tb.Helper() t.tb.Log(args...) } } -// Skipf is equivalent to Logf followed by SkipNow. -func (t *T) Skipf(format string, args ...interface{}) { - if t.tbLog && t.tb != nil { +// Skipf is equivalent to [T.Logf] followed by [T.SkipNow]. +func (t *T) Skipf(format string, args ...any) { + if t.tbLog { t.tb.Helper() } t.Logf(format, args...) t.skip(fmt.Sprintf(format, args...)) } -// Skip is equivalent to Log followed by SkipNow. -func (t *T) Skip(args ...interface{}) { - if t.tbLog && t.tb != nil { +// Skip is equivalent to [T.Log] followed by [T.SkipNow]. +func (t *T) Skip(args ...any) { + if t.tbLog { t.tb.Helper() } t.Log(args...) t.skip(fmt.Sprint(args...)) } -// SkipNow marks the current test case as invalid (except state machine -// tests, where it marks current action as non-applicable instead). +// SkipNow marks the current test case as invalid (except in [T.Repeat] +// actions, where it marks current action as non-applicable instead). // If too many test cases are skipped, rapid will mark the test as failing // due to inability to generate enough valid test cases. // -// Prefer Filter to SkipNow, and prefer generators that always produce +// Prefer *Generator.Filter to SkipNow, and prefer generators that always produce // valid test cases to Filter. func (t *T) SkipNow() { t.skip("(*T).SkipNow() called") } -// Errorf is equivalent to Logf followed by Fail. -func (t *T) Errorf(format string, args ...interface{}) { - if t.tbLog && t.tb != nil { +// Errorf is equivalent to [T.Logf] followed by [T.Fail]. +func (t *T) Errorf(format string, args ...any) { + if t.tbLog { t.tb.Helper() } t.Logf(format, args...) t.fail(false, fmt.Sprintf(format, args...)) } -// Error is equivalent to Log followed by Fail. -func (t *T) Error(args ...interface{}) { - if t.tbLog && t.tb != nil { +// Error is equivalent to [T.Log] followed by [T.Fail]. +func (t *T) Error(args ...any) { + if t.tbLog { t.tb.Helper() } t.Log(args...) t.fail(false, fmt.Sprint(args...)) } -// Fatalf is equivalent to Logf followed by FailNow. -func (t *T) Fatalf(format string, args ...interface{}) { - if t.tbLog && t.tb != nil { +// Fatalf is equivalent to [T.Logf] followed by [T.FailNow]. +func (t *T) Fatalf(format string, args ...any) { + if t.tbLog { t.tb.Helper() } t.Logf(format, args...) t.fail(true, fmt.Sprintf(format, args...)) } -// Fatal is equivalent to Log followed by FailNow. -func (t *T) Fatal(args ...interface{}) { - if t.tbLog && t.tb != nil { +// Fatal is equivalent to [T.Log] followed by [T.FailNow]. +func (t *T) Fatal(args ...any) { + if t.tbLog { t.tb.Helper() } t.Log(args...) @@ -561,21 +659,3 @@ func (t *T) failOnError() { panic(t.failed) } } - -func assertCallable(fn reflect.Type, t reflect.Type, name string) { - assertf(fn.Kind() == reflect.Func, "%v should be a function, not %v", name, fn.Kind()) - assertf(fn.NumIn() == 1, "%v should have 1 parameter, not %v", name, fn.NumIn()) - assertf(fn.NumOut() == 1, "%v should have 1 output parameter, not %v", name, fn.NumOut()) - assertf(t.AssignableTo(fn.In(0)), "parameter #0 (%v) of %v should be assignable from %v", fn.In(0), name, t) -} - -func call(fn reflect.Value, arg reflect.Value) value { - r := fn.Call([]reflect.Value{arg}) - - if len(r) == 0 { - return nil - } else { - assert(len(r) == 1) - return r[0].Interface() - } -} diff --git a/vendor/pgregory.net/rapid/floats.go b/vendor/pgregory.net/rapid/floats.go index cf90fe8..aeaa974 100644 --- a/vendor/pgregory.net/rapid/floats.go +++ b/vendor/pgregory.net/rapid/floats.go @@ -10,7 +10,6 @@ import ( "fmt" "math" "math/bits" - "reflect" ) const ( @@ -24,77 +23,80 @@ const ( floatSignifLabel = "floatsignif" ) -var ( - float32Type = reflect.TypeOf(float32(0)) - float64Type = reflect.TypeOf(float64(0)) -) - -func Float32() *Generator { +// Float32 is a shorthand for [Float32Range](-[math.MaxFloat32], [math.MaxFloat32]). +func Float32() *Generator[float32] { return Float32Range(-math.MaxFloat32, math.MaxFloat32) } -func Float32Min(min float32) *Generator { +// Float32Min is a shorthand for [Float32Range](min, [math.MaxFloat32]). +func Float32Min(min float32) *Generator[float32] { return Float32Range(min, math.MaxFloat32) } -func Float32Max(max float32) *Generator { +// Float32Max is a shorthand for [Float32Range](-[math.MaxFloat32], max). +func Float32Max(max float32) *Generator[float32] { return Float32Range(-math.MaxFloat32, max) } -func Float32Range(min float32, max float32) *Generator { +// Float32Range creates a generator of 32-bit floating-point numbers in range [min, max]. +// Both min and max can be infinite. +func Float32Range(min float32, max float32) *Generator[float32] { assertf(min == min, "min should not be a NaN") assertf(max == max, "max should not be a NaN") assertf(min <= max, "invalid range [%v, %v]", min, max) - return newGenerator(&floatGen{ - typ: float32Type, - min: float64(min), - max: float64(max), - minVal: -math.MaxFloat32, - maxVal: math.MaxFloat32, + return newGenerator[float32](&float32Gen{ + floatGen{ + min: float64(min), + max: float64(max), + minVal: -math.MaxFloat32, + maxVal: math.MaxFloat32, + }, }) } -func Float64() *Generator { +// Float64 is a shorthand for [Float64Range](-[math.MaxFloat64], [math.MaxFloat64]). +func Float64() *Generator[float64] { return Float64Range(-math.MaxFloat64, math.MaxFloat64) } -func Float64Min(min float64) *Generator { +// Float64Min is a shorthand for [Float64Range](min, [math.MaxFloat64]). +func Float64Min(min float64) *Generator[float64] { return Float64Range(min, math.MaxFloat64) } -func Float64Max(max float64) *Generator { +// Float64Max is a shorthand for [Float64Range](-[math.MaxFloat64], max). +func Float64Max(max float64) *Generator[float64] { return Float64Range(-math.MaxFloat64, max) } -func Float64Range(min float64, max float64) *Generator { +// Float64Range creates a generator of 64-bit floating-point numbers in range [min, max]. +// Both min and max can be infinite. +func Float64Range(min float64, max float64) *Generator[float64] { assertf(min == min, "min should not be a NaN") assertf(max == max, "max should not be a NaN") assertf(min <= max, "invalid range [%v, %v]", min, max) - return newGenerator(&floatGen{ - typ: float64Type, - min: min, - max: max, - minVal: -math.MaxFloat64, - maxVal: math.MaxFloat64, + return newGenerator[float64](&float64Gen{ + floatGen{ + min: min, + max: max, + minVal: -math.MaxFloat64, + maxVal: math.MaxFloat64, + }, }) } type floatGen struct { - typ reflect.Type min float64 max float64 minVal float64 maxVal float64 } +type float32Gen struct{ floatGen } +type float64Gen struct{ floatGen } -func (g *floatGen) String() string { - kind := "Float64" - if g.typ == float32Type { - kind = "Float32" - } - +func (g *floatGen) stringImpl(kind string) string { if g.min != g.minVal && g.max != g.maxVal { return fmt.Sprintf("%sRange(%g, %g)", kind, g.min, g.max) } else if g.min != g.minVal { @@ -105,17 +107,18 @@ func (g *floatGen) String() string { return fmt.Sprintf("%s()", kind) } - -func (g *floatGen) type_() reflect.Type { - return g.typ +func (g *float32Gen) String() string { + return g.stringImpl("Float32") +} +func (g *float64Gen) String() string { + return g.stringImpl("Float64") } -func (g *floatGen) value(t *T) value { - if g.typ == float32Type { - return float32FromParts(genFloatRange(t.s, g.min, g.max, float32SignifBits)) - } else { - return float64FromParts(genFloatRange(t.s, g.min, g.max, float64SignifBits)) - } +func (g *float32Gen) value(t *T) float32 { + return float32FromParts(genFloatRange(t.s, g.min, g.max, float32SignifBits)) +} +func (g *float64Gen) value(t *T) float64 { + return float64FromParts(genFloatRange(t.s, g.min, g.max, float64SignifBits)) } func ufloatFracBits(e int32, signifBits uint) uint { diff --git a/vendor/pgregory.net/rapid/generator.go b/vendor/pgregory.net/rapid/generator.go index 72a9716..4dc424d 100644 --- a/vendor/pgregory.net/rapid/generator.go +++ b/vendor/pgregory.net/rapid/generator.go @@ -7,33 +7,30 @@ package rapid import ( + "fmt" "reflect" "sync" ) -type value interface{} - -type generatorImpl interface { +type generatorImpl[V any] interface { String() string - type_() reflect.Type - value(t *T) value + value(t *T) V } -type Generator struct { - impl generatorImpl - typ reflect.Type +// Generator describes a generator of values of type V. +type Generator[V any] struct { + impl generatorImpl[V] strOnce sync.Once str string } -func newGenerator(impl generatorImpl) *Generator { - return &Generator{ +func newGenerator[V any](impl generatorImpl[V]) *Generator[V] { + return &Generator[V]{ impl: impl, - typ: impl.type_(), } } -func (g *Generator) String() string { +func (g *Generator[V]) String() string { g.strOnce.Do(func() { g.str = g.impl.String() }) @@ -41,31 +38,47 @@ func (g *Generator) String() string { return g.str } -func (g *Generator) type_() reflect.Type { - return g.typ -} - -func (g *Generator) Draw(t *T, label string) interface{} { - if t.tbLog && t.tb != nil { +// Draw produces a value from the generator. +func (g *Generator[V]) Draw(t *T, label string) V { + if t.tbLog { t.tb.Helper() } - return t.draw(g, label) + + v := g.value(t) + + if len(t.refDraws) > 0 { + ref := t.refDraws[t.draws] + if !reflect.DeepEqual(v, ref) { + t.tb.Fatalf("draw %v differs: %#v vs expected %#v", t.draws, v, ref) + } + } + + if t.tbLog || t.rawLog != nil { + if label == "" { + label = fmt.Sprintf("#%v", t.draws) + } + + if t.tbLog { + t.tb.Helper() + } + t.Logf("[rapid] draw %v: %#v", label, v) + } + + t.draws++ + + return v } -func (g *Generator) value(t *T) value { +func (g *Generator[V]) value(t *T) V { i := t.s.beginGroup(g.str, true) - v := g.impl.value(t) - u := reflect.TypeOf(v) - assertf(v != nil, "%v has generated a nil value", g) - assertf(u.AssignableTo(g.typ), "%v has generated a value of type %v which is not assignable to %v", g, u, g.typ) - t.s.endGroup(i, false) - return v } -func (g *Generator) Example(seed ...int) interface{} { +// Example produces an example value from the generator. If seed is provided, value is produced deterministically +// based on seed. Example should only be used for examples; always use *Generator.Draw in property-based tests. +func (g *Generator[V]) Example(seed ...int) V { s := baseSeed() if len(seed) > 0 { s = uint64(seed[0]) @@ -77,26 +90,29 @@ func (g *Generator) Example(seed ...int) interface{} { return v } -func (g *Generator) Filter(fn interface{}) *Generator { +// Filter creates a generator producing only values from g for which fn returns true. +func (g *Generator[V]) Filter(fn func(V) bool) *Generator[V] { return filter(g, fn) } -func (g *Generator) Map(fn interface{}) *Generator { - return map_(g, fn) +// AsAny creates a generator producing values from g converted to any. +func (g *Generator[V]) AsAny() *Generator[any] { + return asAny(g) } -func example(g *Generator, t *T) (value, int, error) { +func example[V any](g *Generator[V], t *T) (V, int, error) { for i := 1; ; i++ { r, err := recoverValue(g, t) if err == nil { return r, i, nil } else if i == exampleMaxTries { - return nil, i, err + var zero V + return zero, i, err } } } -func recoverValue(g *Generator, t *T) (v value, err *testError) { +func recoverValue[V any](g *Generator[V], t *T) (v V, err *testError) { defer func() { err = panicToError(recover(), 3) }() return g.value(t), nil diff --git a/vendor/pgregory.net/rapid/integers.go b/vendor/pgregory.net/rapid/integers.go index 7a3b3f7..62d4e7f 100644 --- a/vendor/pgregory.net/rapid/integers.go +++ b/vendor/pgregory.net/rapid/integers.go @@ -9,7 +9,6 @@ package rapid import ( "fmt" "math" - "reflect" ) const ( @@ -30,44 +29,32 @@ const ( uintSize = 32 << (^uint(0) >> 32 & 1) intSize = uintSize - minInt = -1 << (intSize - 1) - maxInt = 1<<(intSize-1) - 1 - maxUint = 1< +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +package rapid + +import ( + "fmt" + "reflect" +) + +// Make creates a generator of values of type V, using reflection to infer the required structure. +func Make[V any]() *Generator[V] { + var zero V + gen := newMakeGen(reflect.TypeOf(zero)) + return newGenerator[V](&makeGen[V]{ + gen: gen, + }) +} + +type makeGen[V any] struct { + gen *Generator[any] +} + +func (g *makeGen[V]) String() string { + var zero V + return fmt.Sprintf("Make[%T]()", zero) +} + +func (g *makeGen[V]) value(t *T) V { + return g.gen.value(t).(V) +} + +func newMakeGen(typ reflect.Type) *Generator[any] { + gen, mayNeedCast := newMakeKindGen(typ) + if !mayNeedCast || typ.String() == typ.Kind().String() { + return gen // fast path with less reflect + } + return newGenerator[any](&castGen{gen, typ}) +} + +type castGen struct { + gen *Generator[any] + typ reflect.Type +} + +func (g *castGen) String() string { + return fmt.Sprintf("cast(%v, %v)", g.gen, g.typ.Name()) +} + +func (g *castGen) value(t *T) any { + v := g.gen.value(t) + return reflect.ValueOf(v).Convert(g.typ).Interface() +} + +func newMakeKindGen(typ reflect.Type) (gen *Generator[any], mayNeedCast bool) { + switch typ.Kind() { + case reflect.Bool: + return Bool().AsAny(), true + case reflect.Int: + return Int().AsAny(), true + case reflect.Int8: + return Int8().AsAny(), true + case reflect.Int16: + return Int16().AsAny(), true + case reflect.Int32: + return Int32().AsAny(), true + case reflect.Int64: + return Int64().AsAny(), true + case reflect.Uint: + return Uint().AsAny(), true + case reflect.Uint8: + return Uint8().AsAny(), true + case reflect.Uint16: + return Uint16().AsAny(), true + case reflect.Uint32: + return Uint32().AsAny(), true + case reflect.Uint64: + return Uint64().AsAny(), true + case reflect.Uintptr: + return Uintptr().AsAny(), true + case reflect.Float32: + return Float32().AsAny(), true + case reflect.Float64: + return Float64().AsAny(), true + case reflect.Array: + return genAnyArray(typ), false + case reflect.Map: + return genAnyMap(typ), false + case reflect.Pointer: + return Deferred(func() *Generator[any] { return genAnyPointer(typ) }), false + case reflect.Slice: + return genAnySlice(typ), false + case reflect.String: + return String().AsAny(), true + case reflect.Struct: + return genAnyStruct(typ), false + default: + panic(fmt.Sprintf("unsupported type kind for Make: %v", typ.Kind())) + } +} + +func genAnyPointer(typ reflect.Type) *Generator[any] { + elem := typ.Elem() + elemGen := newMakeGen(elem) + const pNonNil = 0.5 + + return Custom[any](func(t *T) any { + if flipBiasedCoin(t.s, pNonNil) { + val := elemGen.value(t) + ptr := reflect.New(elem) + ptr.Elem().Set(reflect.ValueOf(val)) + return ptr.Interface() + } else { + return reflect.Zero(typ).Interface() + } + }) +} + +func genAnyArray(typ reflect.Type) *Generator[any] { + count := typ.Len() + elemGen := newMakeGen(typ.Elem()) + + return Custom[any](func(t *T) any { + a := reflect.Indirect(reflect.New(typ)) + if count == 0 { + t.s.drawBits(0) + } else { + for i := 0; i < count; i++ { + e := reflect.ValueOf(elemGen.value(t)) + a.Index(i).Set(e) + } + } + return a.Interface() + }) +} + +func genAnySlice(typ reflect.Type) *Generator[any] { + elemGen := newMakeGen(typ.Elem()) + + return Custom[any](func(t *T) any { + repeat := newRepeat(-1, -1, -1, elemGen.String()) + sl := reflect.MakeSlice(typ, 0, repeat.avg()) + for repeat.more(t.s) { + e := reflect.ValueOf(elemGen.value(t)) + sl = reflect.Append(sl, e) + } + return sl.Interface() + }) +} + +func genAnyMap(typ reflect.Type) *Generator[any] { + keyGen := newMakeGen(typ.Key()) + valGen := newMakeGen(typ.Elem()) + + return Custom[any](func(t *T) any { + label := keyGen.String() + "," + valGen.String() + repeat := newRepeat(-1, -1, -1, label) + m := reflect.MakeMapWithSize(typ, repeat.avg()) + for repeat.more(t.s) { + k := reflect.ValueOf(keyGen.value(t)) + v := reflect.ValueOf(valGen.value(t)) + if m.MapIndex(k).IsValid() { + repeat.reject() + } else { + m.SetMapIndex(k, v) + } + } + return m.Interface() + }) +} + +func genAnyStruct(typ reflect.Type) *Generator[any] { + numFields := typ.NumField() + fieldGens := make([]*Generator[any], numFields) + for i := 0; i < numFields; i++ { + fieldGens[i] = newMakeGen(typ.Field(i).Type) + } + + return Custom[any](func(t *T) any { + s := reflect.Indirect(reflect.New(typ)) + if numFields == 0 { + t.s.drawBits(0) + } else { + for i := 0; i < numFields; i++ { + f := reflect.ValueOf(fieldGens[i].value(t)) + s.Field(i).Set(f) + } + } + return s.Interface() + }) +} diff --git a/vendor/pgregory.net/rapid/persist.go b/vendor/pgregory.net/rapid/persist.go index c0dece7..155ebfe 100644 --- a/vendor/pgregory.net/rapid/persist.go +++ b/vendor/pgregory.net/rapid/persist.go @@ -9,7 +9,6 @@ package rapid import ( "bufio" "fmt" - "io/ioutil" "os" "path/filepath" "strconv" @@ -19,7 +18,7 @@ import ( ) const ( - rapidVersion = "v0.4.6" + rapidVersion = "v0.4.8" persistDirMode = 0775 failfileTmpPattern = ".rapid-failfile-tmp-*" @@ -37,9 +36,17 @@ func kindaSafeFilename(f string) string { return s.String() } -func failFileName(testName string) string { +func failFileName(testName string) (string, string) { ts := time.Now().Format("20060102150405") - return fmt.Sprintf("%s-%s-%d.fail", kindaSafeFilename(testName), ts, os.Getpid()) + fileName := fmt.Sprintf("%s-%s-%d.fail", kindaSafeFilename(testName), ts, os.Getpid()) + dirName := filepath.Join("testdata", "rapid", kindaSafeFilename(testName)) + return dirName, filepath.Join(dirName, fileName) +} + +func failFilePattern(testName string) string { + fileName := fmt.Sprintf("%s-*.fail", kindaSafeFilename(testName)) + dirName := filepath.Join("testdata", "rapid", kindaSafeFilename(testName)) + return filepath.Join(dirName, fileName) } func saveFailFile(filename string, version string, output []byte, seed uint64, buf []uint64) error { @@ -49,7 +56,7 @@ func saveFailFile(filename string, version string, output []byte, seed uint64, b return fmt.Errorf("failed to create directory for fail file %q: %w", filename, err) } - f, err := ioutil.TempFile(dir, failfileTmpPattern) + f, err := os.CreateTemp(dir, failfileTmpPattern) if err != nil { return fmt.Errorf("failed to create temporary file for fail file %q: %w", filename, err) } diff --git a/vendor/pgregory.net/rapid/shrink.go b/vendor/pgregory.net/rapid/shrink.go index 6112156..52a1589 100644 --- a/vendor/pgregory.net/rapid/shrink.go +++ b/vendor/pgregory.net/rapid/shrink.go @@ -31,7 +31,7 @@ const ( labelSortGroups = "sort_groups" ) -func shrink(tb tb, rec recordedBits, err *testError, prop func(*T)) ([]uint64, *testError) { +func shrink(tb tb, deadline time.Time, rec recordedBits, err *testError, prop func(*T)) ([]uint64, *testError) { rec.prune() s := &shrinker{ @@ -44,7 +44,7 @@ func shrink(tb tb, rec recordedBits, err *testError, prop func(*T)) ([]uint64, * cache: map[string]struct{}{}, } - buf, err := s.shrink() + buf, err := s.shrink(deadline) if flags.debugvis { name := fmt.Sprintf("vis-%v.html", strings.Replace(tb.Name(), "/", "_", -1)) @@ -75,14 +75,14 @@ type shrinker struct { hits int } -func (s *shrinker) debugf(verbose_ bool, format string, args ...interface{}) { +func (s *shrinker) debugf(verbose_ bool, format string, args ...any) { if flags.debug && (!verbose_ || flags.verbose) { s.tb.Helper() s.tb.Logf("[shrink] "+format, args...) } } -func (s *shrinker) shrink() (buf []uint64, err *testError) { +func (s *shrinker) shrink(deadline time.Time) (buf []uint64, err *testError) { defer func() { if r := recover(); r != nil { buf, err = s.rec.data, r.(*testError) @@ -90,7 +90,6 @@ func (s *shrinker) shrink() (buf []uint64, err *testError) { }() i := 0 - deadline := time.Now().Add(flags.shrinkTime) for shrinks := -1; s.shrinks > shrinks && time.Now().Before(deadline); i++ { shrinks = s.shrinks @@ -246,7 +245,7 @@ func (s *shrinker) removeGroupSpans(deadline time.Time) { } } -func (s *shrinker) accept(buf []uint64, label string, format string, args ...interface{}) bool { +func (s *shrinker) accept(buf []uint64, label string, format string, args ...any) bool { if compareData(buf, s.rec.data) >= 0 { return false } diff --git a/vendor/pgregory.net/rapid/statemachine.go b/vendor/pgregory.net/rapid/statemachine.go index 527d88f..40069d2 100644 --- a/vendor/pgregory.net/rapid/statemachine.go +++ b/vendor/pgregory.net/rapid/statemachine.go @@ -9,128 +9,101 @@ package rapid import ( "reflect" "sort" + "testing" ) const ( - actionLabel = "action" - validActionTries = 100 // hack, but probably good enough for now - - initMethodName = "Init" + actionLabel = "action" + validActionTries = 100 // hack, but probably good enough for now checkMethodName = "Check" - cleanupMethodName = "Cleanup" - - noValidActionsMsg = "can't find a valid action" + noValidActionsMsg = "can't find a valid (non-skipped) action" ) -type StateMachine interface { - // Check is ran after every action and should contain invariant checks. - // - // Other public methods are treated as follows: - // - Init(t *rapid.T), if present, is ran at the beginning of each test case - // to initialize the state machine instance; - // - Cleanup(), if present, is called at the end of each test case; - // - All other public methods should have a form ActionName(t *rapid.T) - // and are used as possible actions. At least one action has to be specified. - // - Check(*T) -} - -// Run is a convenience function for defining "state machine" tests, -// to be run by Check or MakeCheck. -// -// State machine test is a pattern for testing stateful systems that looks -// like this: +// Repeat executes a random sequence of actions (often called a "state machine" test). +// actions[""], if set, is executed before/after every other action invocation +// and should only contain invariant checking code. // -// m := new(StateMachineType) -// m.Init(t) // optional -// defer m.Cleanup() // optional -// m.Check(t) -// for { -// m.RandomAction(t) -// m.Check(t) -// } -// -// Run synthesizes such test from the type of m, which must be a pointer. -// Note that for each test case, new state machine instance is created -// via reflection; any data inside m is ignored. -func Run(m StateMachine) func(*T) { - typ := reflect.TypeOf(m) +// For complex state machines, it can be more convenient to specify actions as +// methods of a special state machine type. In this case, [StateMachineActions] +// can be used to create an actions map from state machine methods using reflection. +func (t *T) Repeat(actions map[string]func(*T)) { + t.Helper() - return func(t *T) { - t.Helper() + check := func(*T) {} + actionKeys := make([]string, 0, len(actions)) + for key, action := range actions { + if key != "" { + actionKeys = append(actionKeys, key) + } else { + check = action + } + } + if len(actionKeys) == 0 { + return + } + sort.Strings(actionKeys) - repeat := newRepeat(0, flags.steps, maxInt) + steps := flags.steps + if testing.Short() { + steps /= 2 + } - sm := newStateMachine(typ) - if sm.init != nil { - sm.init(t) - t.failOnError() - } - if sm.cleanup != nil { - defer sm.cleanup() - } + repeat := newRepeat(-1, -1, float64(steps), "Repeat") + sm := stateMachine{ + check: check, + actionKeys: SampledFrom(actionKeys), + actions: actions, + } - sm.check(t) - t.failOnError() - for repeat.more(t.s, typ.String()) { - ok := sm.executeAction(t) - if ok { - sm.check(t) - t.failOnError() - } else { - repeat.reject() - } + sm.check(t) + t.failOnError() + for repeat.more(t.s) { + ok := sm.executeAction(t) + if ok { + sm.check(t) + t.failOnError() + } else { + repeat.reject() } } } -type stateMachine struct { - init func(*T) - cleanup func() - check func(*T) - actionKeys *Generator - actions map[string]func(*T) +type StateMachine interface { + // Check is ran after every action and should contain invariant checks. + // + // All other public methods should have a form ActionName(t *rapid.T) + // and are used as possible actions. At least one action has to be specified. + Check(*T) } -func newStateMachine(typ reflect.Type) *stateMachine { - assertf(typ.Kind() == reflect.Ptr, "state machine type should be a pointer, not %v", typ.Kind()) - +// StateMachineActions creates an actions map for [*T.Repeat] +// from methods of a [StateMachine] type instance using reflection. +func StateMachineActions(sm StateMachine) map[string]func(*T) { var ( - v = reflect.New(typ.Elem()) - n = typ.NumMethod() - init func(*T) - cleanup func() - actionKeys []string - actions = map[string]func(*T){} + v = reflect.ValueOf(sm) + t = v.Type() + n = t.NumMethod() ) + actions := make(map[string]func(*T), n) for i := 0; i < n; i++ { - name := typ.Method(i).Name + name := t.Method(i).Name m, ok := v.Method(i).Interface().(func(*T)) - if ok { - if name == initMethodName { - init = m - } else if name != checkMethodName { - actionKeys = append(actionKeys, name) - actions[name] = m - } - } else if name == cleanupMethodName { - m, ok := v.Method(i).Interface().(func()) - assertf(ok, "method %v should have type func(), not %v", cleanupMethodName, v.Method(i).Type()) - cleanup = m + if ok && name != checkMethodName { + actions[name] = m } } - assertf(len(actions) > 0, "state machine of type %v has no actions specified", typ) - sort.Strings(actionKeys) + assertf(len(actions) > 0, "state machine of type %v has no actions specified", t) + actions[""] = sm.Check - return &stateMachine{ - init: init, - cleanup: cleanup, - check: v.Interface().(StateMachine).Check, - actionKeys: SampledFrom(actionKeys), - actions: actions, - } + return actions +} + +type stateMachine struct { + check func(*T) + actionKeys *Generator[string] + actions map[string]func(*T) } func (sm *stateMachine) executeAction(t *T) bool { @@ -138,7 +111,7 @@ func (sm *stateMachine) executeAction(t *T) bool { for n := 0; n < validActionTries; n++ { i := t.s.beginGroup(actionLabel, false) - action := sm.actions[sm.actionKeys.Draw(t, "action").(string)] + action := sm.actions[sm.actionKeys.Draw(t, "action")] invalid, skipped := runAction(t, action) t.s.endGroup(i, false) diff --git a/vendor/pgregory.net/rapid/strings.go b/vendor/pgregory.net/rapid/strings.go index 68da1c3..7948e04 100644 --- a/vendor/pgregory.net/rapid/strings.go +++ b/vendor/pgregory.net/rapid/strings.go @@ -9,7 +9,7 @@ package rapid import ( "bytes" "fmt" - "reflect" + "math" "regexp" "regexp/syntax" "strings" @@ -19,9 +19,6 @@ import ( ) var ( - stringType = reflect.TypeOf("") - byteSliceType = reflect.TypeOf([]byte(nil)) - defaultRunes = []rune{ 'A', 'a', '?', '~', '!', '@', '#', '$', '%', '^', '&', '*', '_', '-', '+', '=', @@ -73,15 +70,18 @@ type compiledRegexp struct { re *regexp.Regexp } -func Rune() *Generator { +// Rune creates a rune generator. Rune is equivalent to [RuneFrom] with default set of runes and tables. +func Rune() *Generator[rune] { return runesFrom(true, defaultRunes, defaultTables...) } -func RuneFrom(runes []rune, tables ...*unicode.RangeTable) *Generator { +// RuneFrom creates a rune generator from provided runes and tables. +// RuneFrom panics if both runes and tables are empty. RuneFrom panics if tables contain an empty table. +func RuneFrom(runes []rune, tables ...*unicode.RangeTable) *Generator[rune] { return runesFrom(false, runes, tables...) } -func runesFrom(default_ bool, runes []rune, tables ...*unicode.RangeTable) *Generator { +func runesFrom(default_ bool, runes []rune, tables ...*unicode.RangeTable) *Generator[rune] { if len(tables) == 0 { assertf(len(runes) > 0, "at least one rune should be specified") } @@ -103,7 +103,7 @@ func runesFrom(default_ bool, runes []rune, tables ...*unicode.RangeTable) *Gene assertf(len(tables_[i]) > 0, "empty *unicode.RangeTable %v", i) } - return newGenerator(&runeGen{ + return newGenerator[rune](&runeGen{ die: newLoadedDie(weights), runes: runes, tables: tables_, @@ -126,11 +126,7 @@ func (g *runeGen) String() string { } } -func (g *runeGen) type_() reflect.Type { - return int32Type -} - -func (g *runeGen) value(t *T) value { +func (g *runeGen) value(t *T) rune { n := g.die.roll(t.s) runes := g.runes @@ -143,106 +139,112 @@ func (g *runeGen) value(t *T) value { return runes[genIndex(t.s, len(runes), true)] } -func String() *Generator { +// String is a shorthand for [StringOf]([Rune]()). +func String() *Generator[string] { return StringOf(anyRuneGen) } -func StringN(minRunes int, maxRunes int, maxLen int) *Generator { +// StringN is a shorthand for [StringOfN]([Rune](), minRunes, maxRunes, maxLen). +func StringN(minRunes int, maxRunes int, maxLen int) *Generator[string] { return StringOfN(anyRuneGen, minRunes, maxRunes, maxLen) } -func StringOf(elem *Generator) *Generator { +// StringOf is a shorthand for [StringOfN](elem, -1, -1, -1). +func StringOf(elem *Generator[rune]) *Generator[string] { return StringOfN(elem, -1, -1, -1) } -func StringOfN(elem *Generator, minElems int, maxElems int, maxLen int) *Generator { - assertValidRange(minElems, maxElems) - assertf(elem.type_() == int32Type || elem.type_() == uint8Type, "element generator should generate runes or bytes, not %v", elem.type_()) - assertf(maxLen < 0 || maxLen >= maxElems, "maximum length (%v) should not be less than maximum number of elements (%v)", maxLen, maxElems) +// StringOfN creates a UTF-8 string generator. +// If minRunes >= 0, generated strings have minimum minRunes runes. +// If maxRunes >= 0, generated strings have maximum maxRunes runes. +// If maxLen >= 0, generates strings have maximum length of maxLen. +// StringOfN panics if maxRunes >= 0 and minRunes > maxRunes. +// StringOfN panics if maxLen >= 0 and maxLen < maxRunes. +func StringOfN(elem *Generator[rune], minRunes int, maxRunes int, maxLen int) *Generator[string] { + assertValidRange(minRunes, maxRunes) + assertf(maxLen < 0 || maxLen >= maxRunes, "maximum length (%v) should not be less than maximum number of runes (%v)", maxLen, maxRunes) - return newGenerator(&stringGen{ + return newGenerator[string](&stringGen{ elem: elem, - minElems: minElems, - maxElems: maxElems, + minRunes: minRunes, + maxRunes: maxRunes, maxLen: maxLen, }) } type stringGen struct { - elem *Generator - minElems int - maxElems int + elem *Generator[rune] + minRunes int + maxRunes int maxLen int } func (g *stringGen) String() string { if g.elem == anyRuneGen { - if g.minElems < 0 && g.maxElems < 0 && g.maxLen < 0 { + if g.minRunes < 0 && g.maxRunes < 0 && g.maxLen < 0 { return "String()" } else { - return fmt.Sprintf("StringN(minRunes=%v, maxRunes=%v, maxLen=%v)", g.minElems, g.maxElems, g.maxLen) + return fmt.Sprintf("StringN(minRunes=%v, maxRunes=%v, maxLen=%v)", g.minRunes, g.maxRunes, g.maxLen) } } else { - if g.minElems < 0 && g.maxElems < 0 && g.maxLen < 0 { + if g.minRunes < 0 && g.maxRunes < 0 && g.maxLen < 0 { return fmt.Sprintf("StringOf(%v)", g.elem) } else { - return fmt.Sprintf("StringOfN(%v, minElems=%v, maxElems=%v, maxLen=%v)", g.elem, g.minElems, g.maxElems, g.maxLen) + return fmt.Sprintf("StringOfN(%v, minRunes=%v, maxRunes=%v, maxLen=%v)", g.elem, g.minRunes, g.maxRunes, g.maxLen) } } } -func (g *stringGen) type_() reflect.Type { - return stringType -} - -func (g *stringGen) value(t *T) value { - repeat := newRepeat(g.minElems, g.maxElems, -1) +func (g *stringGen) value(t *T) string { + repeat := newRepeat(g.minRunes, g.maxRunes, -1, g.elem.String()) var b strings.Builder b.Grow(repeat.avg()) - if g.elem.type_() == int32Type { - maxLen := g.maxLen - if maxLen < 0 { - maxLen = maxInt - } + maxLen := g.maxLen + if maxLen < 0 { + maxLen = math.MaxInt + } - for repeat.more(t.s, g.elem.String()) { - r := g.elem.value(t).(rune) - n := utf8.RuneLen(r) + for repeat.more(t.s) { + r := g.elem.value(t) + n := utf8.RuneLen(r) - if n < 0 || b.Len()+n > maxLen { - repeat.reject() - } else { - b.WriteRune(r) - } - } - } else { - for repeat.more(t.s, g.elem.String()) { - b.WriteByte(g.elem.value(t).(byte)) + if n < 0 || b.Len()+n > maxLen { + repeat.reject() + } else { + b.WriteRune(r) } } return b.String() } -func StringMatching(expr string) *Generator { - return matching(expr, true) -} +// StringMatching creates a UTF-8 string generator matching the provided [syntax.Perl] regular expression. +func StringMatching(expr string) *Generator[string] { + compiled, err := compileRegexp(expr) + assertf(err == nil, "%v", err) -func SliceOfBytesMatching(expr string) *Generator { - return matching(expr, false) + return newGenerator[string](®expStringGen{ + regexpGen{ + expr: expr, + syn: compiled.syn, + re: compiled.re, + }, + }) } -func matching(expr string, str bool) *Generator { +// SliceOfBytesMatching creates a UTF-8 byte slice generator matching the provided [syntax.Perl] regular expression. +func SliceOfBytesMatching(expr string) *Generator[[]byte] { compiled, err := compileRegexp(expr) assertf(err == nil, "%v", err) - return newGenerator(®expGen{ - str: str, - expr: expr, - syn: compiled.syn, - re: compiled.re, + return newGenerator[[]byte](®expSliceGen{ + regexpGen{ + expr: expr, + syn: compiled.syn, + re: compiled.re, + }, }) } @@ -251,58 +253,49 @@ type runeWriter interface { } type regexpGen struct { - str bool expr string syn *syntax.Regexp re *regexp.Regexp } +type regexpStringGen struct{ regexpGen } +type regexpSliceGen struct{ regexpGen } -func (g *regexpGen) String() string { - if g.str { - return fmt.Sprintf("StringMatching(%q)", g.expr) - } else { - return fmt.Sprintf("SliceOfBytesMatching(%q)", g.expr) - } +func (g *regexpStringGen) String() string { + return fmt.Sprintf("StringMatching(%q)", g.expr) } - -func (g *regexpGen) type_() reflect.Type { - if g.str { - return stringType - } else { - return byteSliceType - } +func (g *regexpSliceGen) String() string { + return fmt.Sprintf("SliceOfBytesMatching(%q)", g.expr) } -func (g *regexpGen) maybeString(t *T) value { +func (g *regexpStringGen) maybeString(t *T) (string, bool) { b := &strings.Builder{} g.build(b, g.syn, t) v := b.String() if g.re.MatchString(v) { - return v + return v, true } else { - return nil + return "", false } } -func (g *regexpGen) maybeSlice(t *T) value { +func (g *regexpSliceGen) maybeSlice(t *T) ([]byte, bool) { b := &bytes.Buffer{} g.build(b, g.syn, t) v := b.Bytes() if g.re.Match(v) { - return v + return v, true } else { - return nil + return nil, false } } -func (g *regexpGen) value(t *T) value { - if g.str { - return find(g.maybeString, t, small) - } else { - return find(g.maybeSlice, t, small) - } +func (g *regexpStringGen) value(t *T) string { + return find(g.maybeString, t, small) +} +func (g *regexpSliceGen) value(t *T) []byte { + return find(g.maybeSlice, t, small) } func (g *regexpGen) build(w runeWriter, re *syntax.Regexp, t *T) { @@ -326,7 +319,7 @@ func (g *regexpGen) build(w runeWriter, re *syntax.Regexp, t *T) { case syntax.OpAnyCharNotNL: sub = anyRuneGenNoNL } - r := sub.value(t).(rune) + r := sub.value(t) _, _ = w.WriteRune(maybeFoldCase(t.s, r, re.Flags)) case syntax.OpBeginLine, syntax.OpEndLine, syntax.OpBeginText, syntax.OpEndText, @@ -344,8 +337,8 @@ func (g *regexpGen) build(w runeWriter, re *syntax.Regexp, t *T) { case syntax.OpQuest: min, max = 0, 1 } - repeat := newRepeat(min, max, -1) - for repeat.more(t.s, regexpName(re.Sub[0])) { + repeat := newRepeat(min, max, -1, regexpName(re.Sub[0])) + for repeat.more(t.s) { g.build(w, re.Sub[0], t) } case syntax.OpConcat: @@ -375,13 +368,21 @@ func maybeFoldCase(s bitStream, r rune, flags syntax.Flags) rune { return r } -func expandRangeTable(t *unicode.RangeTable, key interface{}) []rune { +func expandRangeTable(t *unicode.RangeTable, key any) []rune { cached, ok := expandedTables.Load(key) if ok { return cached.([]rune) } - var ret []rune + n := 0 + for _, r := range t.R16 { + n += int(r.Hi-r.Lo)/int(r.Stride) + 1 + } + for _, r := range t.R32 { + n += int(r.Hi-r.Lo)/int(r.Stride) + 1 + } + + ret := make([]rune, 0, n) for _, r := range t.R16 { for i := uint32(r.Lo); i <= uint32(r.Hi); i += uint32(r.Stride) { ret = append(ret, rune(i)) @@ -431,14 +432,16 @@ func regexpName(re *syntax.Regexp) string { return s } -func charClassGen(re *syntax.Regexp) *Generator { +func charClassGen(re *syntax.Regexp) *Generator[rune] { cached, ok := charClassGens.Load(regexpName(re)) if ok { - return cached.(*Generator) + return cached.(*Generator[rune]) } - t := &unicode.RangeTable{} + t := &unicode.RangeTable{R32: make([]unicode.Range32, 0, len(re.Rune)/2)} for i := 0; i < len(re.Rune); i += 2 { + // not a valid unicode.Range32, since it requires that Lo and Hi must always be >= 1<<16 + // however, we don't really care, since the only use of these ranges is as input to expandRangeTable t.R32 = append(t.R32, unicode.Range32{ Lo: uint32(re.Rune[i]), Hi: uint32(re.Rune[i+1]), @@ -446,7 +449,7 @@ func charClassGen(re *syntax.Regexp) *Generator { }) } - g := newGenerator(&runeGen{ + g := newGenerator[rune](&runeGen{ die: newLoadedDie([]int{1}), tables: [][]rune{expandRangeTable(t, regexpName(re))}, }) diff --git a/vendor/pgregory.net/rapid/utils.go b/vendor/pgregory.net/rapid/utils.go index 3499aaf..0c54314 100644 --- a/vendor/pgregory.net/rapid/utils.go +++ b/vendor/pgregory.net/rapid/utils.go @@ -24,7 +24,7 @@ func bitmask64(n uint) uint64 { } func genFloat01(s bitStream) float64 { - return float64(s.drawBits(53)) / (1 << 53) + return float64(s.drawBits(53)) * 0x1.0p-53 } func genGeom(s bitStream, p float64) uint64 { @@ -207,14 +207,15 @@ type repeat struct { rejected bool rejections int forceStop bool + label string } -func newRepeat(minCount int, maxCount int, avgCount float64) *repeat { +func newRepeat(minCount int, maxCount int, avgCount float64, label string) *repeat { if minCount < 0 { minCount = 0 } if maxCount < 0 { - maxCount = maxInt + maxCount = math.MaxInt } if avgCount < 0 { avgCount = float64(minCount) + math.Min(math.Max(float64(minCount), small), (float64(maxCount)-float64(minCount))/2) @@ -226,6 +227,7 @@ func newRepeat(minCount int, maxCount int, avgCount float64) *repeat { avgCount: avgCount, pContinue: 1 - 1/(1+avgCount-float64(minCount)), // TODO was no -minCount intentional? group: -1, + label: label + repeatLabel, } } @@ -233,12 +235,12 @@ func (r *repeat) avg() int { return int(math.Ceil(r.avgCount)) } -func (r *repeat) more(s bitStream, label string) bool { +func (r *repeat) more(s bitStream) bool { if r.group >= 0 { s.endGroup(r.group, r.rejected) } - r.group = s.beginGroup(label+repeatLabel, true) + r.group = s.beginGroup(r.label, true) r.rejected = false pCont := r.pContinue