ooni-probe-cli/internal/netxlite/errwrapper_test.go
Simone Basso 1c057d322d
cleanup: merge legacy errorsx in netxlite and hide classifiers (#655)
This diff implements the first two cleanups defined at https://github.com/ooni/probe/issues/1956:

> - [ ] observe that `netxlite` and `netx` differ in error wrapping only in the way in which we set `ErrWrapper.Operation`. Observe that the code using `netxlite` does not care about such a field. Therefore, we can modify `netxlite` to set such a field using the code of `netx` and we can remove `netx` specific code for errors (which currently lives inside of the `./internal/engine/legacy/errorsx` package
>
> - [ ] after we've done the previous cleanup, we can make all the classifiers code private, since there's no code outside `netxlite` that needs them

A subsequent diff will address the remaining cleanup.

While there, notice that there are failing, unrelated obfs4 tests, so disable them in short mode. (I am confident these tests are unrelated because they fail for me when running test locally from the `master` branch.)
2022-01-07 17:31:21 +01:00

213 lines
5.7 KiB
Go

package netxlite
import (
"encoding/json"
"errors"
"fmt"
"io"
"testing"
"github.com/ooni/probe-cli/v3/internal/atomicx"
)
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")
}
})
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")
}
})
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)
}
})
}
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)
}
}()
NewErrWrapper(classifyGenericError, "", io.EOF)
}()
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)
}
}()
NewErrWrapper(classifyGenericError, CloseOperation, nil)
}()
if recovered.Load() != 1 {
t.Fatal("did not panic")
}
})
t.Run("otherwise, works as intended", func(t *testing.T) {
ew := NewErrWrapper(classifyGenericError, CloseOperation, io.EOF)
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")
}
})
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)
ew2 := NewErrWrapper(classifyGenericError, HTTPRoundTripOperation, err2)
if ew2.Failure != ew.Failure {
t.Fatal("not the same failure")
}
if ew2.Operation != HTTPRoundTripOperation {
t.Fatal("not the same operation")
}
if ew2.WrappedErr != err2 {
t.Fatal("invalid underlying error")
}
})
}
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")
}
}
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")
}
})
}