2021-09-28 12:42:01 +02:00
|
|
|
package netxlite
|
2021-09-07 17:09:30 +02:00
|
|
|
|
|
|
|
import (
|
2021-09-08 17:42:36 +02:00
|
|
|
"encoding/json"
|
2021-09-07 17:09:30 +02:00
|
|
|
"errors"
|
2021-11-06 17:49:58 +01:00
|
|
|
"fmt"
|
2021-09-07 17:09:30 +02:00
|
|
|
"io"
|
|
|
|
"testing"
|
2021-09-08 21:19:51 +02:00
|
|
|
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/atomicx"
|
2021-09-07 17:09:30 +02:00
|
|
|
)
|
|
|
|
|
2021-09-07 20:39:32 +02:00
|
|
|
func TestErrWrapper(t *testing.T) {
|
|
|
|
t.Run("Error", func(t *testing.T) {
|
|
|
|
err := &ErrWrapper{Failure: FailureDNSNXDOMAINError}
|
|
|
|
if err.Error() != FailureDNSNXDOMAINError {
|
|
|
|
t.Fatal("invalid return value")
|
|
|
|
}
|
|
|
|
})
|
2021-09-07 17:09:30 +02:00
|
|
|
|
2021-09-07 20:39:32 +02:00
|
|
|
t.Run("Unwrap", func(t *testing.T) {
|
|
|
|
err := &ErrWrapper{
|
|
|
|
Failure: FailureEOFError,
|
|
|
|
WrappedErr: io.EOF,
|
|
|
|
}
|
|
|
|
if !errors.Is(err, io.EOF) {
|
|
|
|
t.Fatal("cannot unwrap error")
|
|
|
|
}
|
|
|
|
})
|
2021-09-08 17:42:36 +02:00
|
|
|
|
|
|
|
t.Run("MarshalJSON", func(t *testing.T) {
|
|
|
|
wrappedErr := &ErrWrapper{
|
|
|
|
Failure: FailureEOFError,
|
|
|
|
WrappedErr: io.EOF,
|
|
|
|
}
|
|
|
|
data, err := json.Marshal(wrappedErr)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
s := string(data)
|
|
|
|
if s != "\""+FailureEOFError+"\"" {
|
|
|
|
t.Fatal("invalid serialization", s)
|
|
|
|
}
|
|
|
|
})
|
2021-09-07 17:09:30 +02:00
|
|
|
}
|
2021-09-08 21:19:51 +02:00
|
|
|
|
|
|
|
func TestNewErrWrapper(t *testing.T) {
|
|
|
|
t.Run("panics if the classifier is nil", func(t *testing.T) {
|
|
|
|
recovered := &atomicx.Int64{}
|
|
|
|
func() {
|
|
|
|
defer func() {
|
|
|
|
if recover() != nil {
|
|
|
|
recovered.Add(1)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
NewErrWrapper(nil, CloseOperation, io.EOF)
|
|
|
|
}()
|
|
|
|
if recovered.Load() != 1 {
|
|
|
|
t.Fatal("did not panic")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("panics if the operation is empty", func(t *testing.T) {
|
|
|
|
recovered := &atomicx.Int64{}
|
|
|
|
func() {
|
|
|
|
defer func() {
|
|
|
|
if recover() != nil {
|
|
|
|
recovered.Add(1)
|
|
|
|
}
|
|
|
|
}()
|
2022-01-07 17:31:21 +01:00
|
|
|
NewErrWrapper(classifyGenericError, "", io.EOF)
|
2021-09-08 21:19:51 +02:00
|
|
|
}()
|
|
|
|
if recovered.Load() != 1 {
|
|
|
|
t.Fatal("did not panic")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("panics if the error is nil", func(t *testing.T) {
|
|
|
|
recovered := &atomicx.Int64{}
|
|
|
|
func() {
|
|
|
|
defer func() {
|
|
|
|
if recover() != nil {
|
|
|
|
recovered.Add(1)
|
|
|
|
}
|
|
|
|
}()
|
2022-01-07 17:31:21 +01:00
|
|
|
NewErrWrapper(classifyGenericError, CloseOperation, nil)
|
2021-09-08 21:19:51 +02:00
|
|
|
}()
|
|
|
|
if recovered.Load() != 1 {
|
|
|
|
t.Fatal("did not panic")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("otherwise, works as intended", func(t *testing.T) {
|
2022-01-07 17:31:21 +01:00
|
|
|
ew := NewErrWrapper(classifyGenericError, CloseOperation, io.EOF)
|
2021-09-08 21:19:51 +02:00
|
|
|
if ew.Failure != FailureEOFError {
|
|
|
|
t.Fatal("unexpected failure")
|
|
|
|
}
|
|
|
|
if ew.Operation != CloseOperation {
|
|
|
|
t.Fatal("unexpected operation")
|
|
|
|
}
|
|
|
|
if ew.WrappedErr != io.EOF {
|
|
|
|
t.Fatal("unexpected WrappedErr")
|
|
|
|
}
|
|
|
|
})
|
2021-11-06 17:49:58 +01:00
|
|
|
|
|
|
|
t.Run("when the underlying error is already a wrapped error", func(t *testing.T) {
|
|
|
|
ew := NewErrWrapper(classifySyscallError, ReadOperation, ECONNRESET)
|
|
|
|
var err1 error = ew
|
|
|
|
err2 := fmt.Errorf("cannot read: %w", err1)
|
2022-01-07 17:31:21 +01:00
|
|
|
ew2 := NewErrWrapper(classifyGenericError, HTTPRoundTripOperation, err2)
|
2021-11-06 17:49:58 +01:00
|
|
|
if ew2.Failure != ew.Failure {
|
|
|
|
t.Fatal("not the same failure")
|
|
|
|
}
|
2022-01-07 17:31:21 +01:00
|
|
|
if ew2.Operation != HTTPRoundTripOperation {
|
2021-11-06 17:49:58 +01:00
|
|
|
t.Fatal("not the same operation")
|
|
|
|
}
|
|
|
|
if ew2.WrappedErr != err2 {
|
|
|
|
t.Fatal("invalid underlying error")
|
|
|
|
}
|
|
|
|
})
|
2021-09-08 21:19:51 +02:00
|
|
|
}
|
2021-09-27 14:55:47 +02:00
|
|
|
|
|
|
|
func TestNewTopLevelGenericErrWrapper(t *testing.T) {
|
|
|
|
out := NewTopLevelGenericErrWrapper(io.EOF)
|
|
|
|
if out.Failure != FailureEOFError {
|
|
|
|
t.Fatal("invalid failure")
|
|
|
|
}
|
|
|
|
if out.Operation != TopLevelOperation {
|
|
|
|
t.Fatal("invalid operation")
|
|
|
|
}
|
|
|
|
if !errors.Is(out, io.EOF) {
|
|
|
|
t.Fatal("invalid underlying error using errors.Is")
|
|
|
|
}
|
|
|
|
if !errors.Is(out.WrappedErr, io.EOF) {
|
|
|
|
t.Fatal("invalid WrappedErr")
|
|
|
|
}
|
|
|
|
}
|
2022-01-07 17:31:21 +01:00
|
|
|
|
|
|
|
func TestClassifyOperation(t *testing.T) {
|
|
|
|
t.Run("for connect", func(t *testing.T) {
|
|
|
|
// You're doing HTTP and connect fails. You want to know
|
|
|
|
// that connect failed not that HTTP failed.
|
|
|
|
err := &ErrWrapper{Operation: ConnectOperation}
|
|
|
|
if classifyOperation(err, HTTPRoundTripOperation) != ConnectOperation {
|
|
|
|
t.Fatal("unexpected result")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("for http_round_trip", func(t *testing.T) {
|
|
|
|
// You're doing DoH and something fails inside HTTP. You want
|
|
|
|
// to know about the internal HTTP error, not resolve.
|
|
|
|
err := &ErrWrapper{Operation: HTTPRoundTripOperation}
|
|
|
|
if classifyOperation(err, ResolveOperation) != HTTPRoundTripOperation {
|
|
|
|
t.Fatal("unexpected result")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("for resolve", func(t *testing.T) {
|
|
|
|
// You're doing HTTP and the DNS fails. You want to
|
|
|
|
// know that resolve failed.
|
|
|
|
err := &ErrWrapper{Operation: ResolveOperation}
|
|
|
|
if classifyOperation(err, HTTPRoundTripOperation) != ResolveOperation {
|
|
|
|
t.Fatal("unexpected result")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("for tls_handshake", func(t *testing.T) {
|
|
|
|
// You're doing HTTP and the TLS handshake fails. You want
|
|
|
|
// to know about a TLS handshake error.
|
|
|
|
err := &ErrWrapper{Operation: TLSHandshakeOperation}
|
|
|
|
if classifyOperation(err, HTTPRoundTripOperation) != TLSHandshakeOperation {
|
|
|
|
t.Fatal("unexpected result")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("for minor operation", func(t *testing.T) {
|
|
|
|
// You just noticed that TLS handshake failed and you
|
|
|
|
// have a child error telling you that read failed. Here
|
|
|
|
// you want to know about a TLS handshake error.
|
|
|
|
err := &ErrWrapper{Operation: ReadOperation}
|
|
|
|
if classifyOperation(err, TLSHandshakeOperation) != TLSHandshakeOperation {
|
|
|
|
t.Fatal("unexpected result")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("for quic_handshake", func(t *testing.T) {
|
|
|
|
// You're doing HTTP and the QUIC handshake fails. You want
|
|
|
|
// to know about a QUIC handshake error.
|
|
|
|
err := &ErrWrapper{Operation: QUICHandshakeOperation}
|
|
|
|
if classifyOperation(err, HTTPRoundTripOperation) != QUICHandshakeOperation {
|
|
|
|
t.Fatal("unexpected result")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("for quic_handshake_start", func(t *testing.T) {
|
|
|
|
// You're doing HTTP and the QUIC handshake fails. You want
|
|
|
|
// to know about a QUIC handshake error.
|
|
|
|
err := &ErrWrapper{Operation: "quic_handshake_start"}
|
|
|
|
if classifyOperation(err, HTTPRoundTripOperation) != QUICHandshakeOperation {
|
|
|
|
t.Fatal("unexpected result")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("for quic_handshake_done", func(t *testing.T) {
|
|
|
|
// You're doing HTTP and the QUIC handshake fails. You want
|
|
|
|
// to know about a QUIC handshake error.
|
|
|
|
err := &ErrWrapper{Operation: "quic_handshake_done"}
|
|
|
|
if classifyOperation(err, HTTPRoundTripOperation) != QUICHandshakeOperation {
|
|
|
|
t.Fatal("unexpected result")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|