refactor(netxlite/errorsx): change all tests to be unit tests ()

Later we will try to write comprehensive integration tests for
the whole netxlite package. We want just unit tests here.
This commit is contained in:
Simone Basso 2021-09-07 22:10:29 +02:00 committed by GitHub
parent b7786a7324
commit 1472f7530b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 122 additions and 124 deletions
internal/netxlite/errorsx

@ -124,19 +124,6 @@ const (
quicTLSUnrecognizedName = 112
)
// quicIsCertificateError tells us whether a specific TLS alert error
// we received is actually an error depending on the certificate.
//
// The set of checks we implement here is a set of heuristics based
// on our understanding of the TLS spec and may need tweaks.
func quicIsCertificateError(alert uint8) bool {
return (alert == quicTLSAlertBadCertificate ||
alert == quicTLSAlertUnsupportedCertificate ||
alert == quicTLSAlertCertificateExpired ||
alert == quicTLSAlertCertificateRevoked ||
alert == quicTLSAlertCertificateUnknown)
}
// ClassifyQUICHandshakeError maps an error occurred during the QUIC
// handshake to an OONI failure string.
//
@ -196,6 +183,29 @@ func ClassifyQUICHandshakeError(err error) string {
return ClassifyGenericError(err)
}
// quicIsCertificateError tells us whether a specific TLS alert error
// we received is actually an error depending on the certificate.
//
// The set of checks we implement here is a set of heuristics based
// on our understanding of the TLS spec and may need tweaks.
func quicIsCertificateError(alert uint8) bool {
// List out each case separately so we know we test them
switch alert {
case quicTLSAlertBadCertificate:
return true
case quicTLSAlertUnsupportedCertificate:
return true
case quicTLSAlertCertificateExpired:
return true
case quicTLSAlertCertificateRevoked:
return true
case quicTLSAlertCertificateUnknown:
return true
default:
return false
}
}
// ErrDNSBogon indicates that we found a bogon address. Code that
// filters for DNS bogons MUST use this error.
var ErrDNSBogon = errors.New("dns: detected bogon address")

@ -2,12 +2,9 @@ package errorsx
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
"io"
"net"
"syscall"
"testing"
"github.com/google/go-cmp/cmp"
@ -16,6 +13,9 @@ import (
)
func TestClassifyGenericError(t *testing.T) {
// Please, keep this list sorted in the same order
// in which checks appear on the code
t.Run("for input being already an ErrWrapper", func(t *testing.T) {
err := &ErrWrapper{Failure: FailureEOFError}
if ClassifyGenericError(err) != FailureEOFError {
@ -23,20 +23,18 @@ func TestClassifyGenericError(t *testing.T) {
}
})
t.Run("for already wrapped error", func(t *testing.T) {
err := io.EOF
if ClassifyGenericError(err) != FailureEOFError {
t.Fatal("unexpected result")
t.Run("for a system call error", func(t *testing.T) {
if ClassifyGenericError(EWOULDBLOCK) != FailureOperationWouldBlock {
t.Fatal("unexpected results")
}
})
t.Run("for context.Canceled", func(t *testing.T) {
if ClassifyGenericError(context.Canceled) != FailureInterrupted {
t.Fatal("unexpected result")
}
})
// Now we enter into classifyWithStringSuffix. We test it here
// since we want to test the ClassifyGenericError in is
// entirety here and the classifyWithStringSuffix function
// is just an implementation detail.
t.Run("for operation was canceled error", func(t *testing.T) {
t.Run("for operation was canceled", func(t *testing.T) {
if ClassifyGenericError(errors.New("operation was canceled")) != FailureInterrupted {
t.Fatal("unexpected result")
}
@ -44,45 +42,12 @@ func TestClassifyGenericError(t *testing.T) {
t.Run("for EOF", func(t *testing.T) {
if ClassifyGenericError(io.EOF) != FailureEOFError {
t.Fatal("unexpected results")
}
})
t.Run("for canceled", func(t *testing.T) {
if ClassifyGenericError(syscall.ECANCELED) != FailureOperationCanceled {
t.Fatal("unexpected results")
}
})
t.Run("for connection_refused", func(t *testing.T) {
if ClassifyGenericError(syscall.ECONNREFUSED) != FailureConnectionRefused {
t.Fatal("unexpected results")
}
})
t.Run("for connection_reset", func(t *testing.T) {
if ClassifyGenericError(syscall.ECONNRESET) != FailureConnectionReset {
t.Fatal("unexpected results")
}
})
t.Run("for host_unreachable", func(t *testing.T) {
if ClassifyGenericError(syscall.EHOSTUNREACH) != FailureHostUnreachable {
t.Fatal("unexpected results")
}
})
t.Run("for system timeout", func(t *testing.T) {
if ClassifyGenericError(syscall.ETIMEDOUT) != FailureTimedOut {
t.Fatal("unexpected results")
t.Fatal("unexpected result")
}
})
t.Run("for context deadline exceeded", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 1)
defer cancel()
<-ctx.Done()
if ClassifyGenericError(ctx.Err()) != FailureGenericTimeoutError {
if ClassifyGenericError(context.DeadlineExceeded) != FailureGenericTimeoutError {
t.Fatal("unexpected results")
}
})
@ -93,22 +58,13 @@ func TestClassifyGenericError(t *testing.T) {
}
})
t.Run("for i/o error", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 1)
defer cancel() // fail immediately
conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", "www.google.com:80")
if err == nil {
t.Fatal("expected an error here")
}
if conn != nil {
t.Fatal("expected nil connection here")
}
if ClassifyGenericError(err) != FailureGenericTimeoutError {
t.Run("for i/o timeout", func(t *testing.T) {
if ClassifyGenericError(errors.New("i/o timeout")) != FailureGenericTimeoutError {
t.Fatal("unexpected results")
}
})
t.Run("for TLS handshake timeout error", func(t *testing.T) {
t.Run("for TLS handshake timeout", func(t *testing.T) {
err := errors.New("net/http: TLS handshake timeout")
if ClassifyGenericError(err) != FailureGenericTimeoutError {
t.Fatal("unexpected results")
@ -116,53 +72,44 @@ func TestClassifyGenericError(t *testing.T) {
})
t.Run("for no such host", func(t *testing.T) {
if ClassifyGenericError(&net.DNSError{
Err: "no such host",
}) != FailureDNSNXDOMAINError {
if ClassifyGenericError(errors.New("no such host")) != FailureDNSNXDOMAINError {
t.Fatal("unexpected results")
}
})
t.Run("for errors including IPv4 address", func(t *testing.T) {
input := errors.New("read tcp 10.0.2.15:56948->93.184.216.34:443: use of closed network connection")
expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
out := ClassifyGenericError(input)
if out != expected {
t.Fatal(cmp.Diff(expected, out))
// Now we're back in ClassifyGenericError
t.Run("for context.Canceled", func(t *testing.T) {
if ClassifyGenericError(context.Canceled) != FailureInterrupted {
t.Fatal("unexpected result")
}
})
t.Run("for errors including IPv6 address", func(t *testing.T) {
input := errors.New("read tcp [::1]:56948->[::1]:443: use of closed network connection")
expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
out := ClassifyGenericError(input)
if out != expected {
t.Fatal(cmp.Diff(expected, out))
}
})
t.Run("for unknown errors", func(t *testing.T) {
t.Run("with an IPv4 address", func(t *testing.T) {
input := errors.New("read tcp 10.0.2.15:56948->93.184.216.34:443: use of closed network connection")
expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
out := ClassifyGenericError(input)
if out != expected {
t.Fatal(cmp.Diff(expected, out))
}
})
t.Run("for i/o error", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 1)
defer cancel() // fail immediately
udpAddr := &net.UDPAddr{IP: net.ParseIP("216.58.212.164"), Port: 80, Zone: ""}
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
if err != nil {
t.Fatal(err)
}
sess, err := quic.DialEarlyContext(ctx, udpConn, udpAddr, "google.com:80", &tls.Config{}, &quic.Config{})
if err == nil {
t.Fatal("expected an error here")
}
if sess != nil {
t.Fatal("expected nil session here")
}
if ClassifyGenericError(err) != FailureGenericTimeoutError {
t.Fatal("unexpected results")
}
t.Run("with an IPv6 address", func(t *testing.T) {
input := errors.New("read tcp [::1]:56948->[::1]:443: use of closed network connection")
expected := "unknown_failure: read tcp [scrubbed]->[scrubbed]: use of closed network connection"
out := ClassifyGenericError(input)
if out != expected {
t.Fatal(cmp.Diff(expected, out))
}
})
})
}
func TestClassifyQUICHandshakeError(t *testing.T) {
// Please, keep this list sorted in the same order
// in which checks appear on the code
t.Run("for input being already an ErrWrapper", func(t *testing.T) {
err := &ErrWrapper{Failure: FailureEOFError}
if ClassifyQUICHandshakeError(err) != FailureEOFError {
@ -170,58 +117,93 @@ func TestClassifyQUICHandshakeError(t *testing.T) {
}
})
t.Run("for connection_reset", func(t *testing.T) {
if ClassifyQUICHandshakeError(&quic.StatelessResetError{}) != FailureConnectionReset {
t.Fatal("unexpected results")
}
})
t.Run("for incompatible quic version", func(t *testing.T) {
if ClassifyQUICHandshakeError(&quic.VersionNegotiationError{}) != FailureQUICIncompatibleVersion {
t.Fatal("unexpected results")
}
})
t.Run("for quic connection refused", func(t *testing.T) {
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: quic.ConnectionRefused}) != FailureConnectionRefused {
t.Run("for stateless reset", func(t *testing.T) {
if ClassifyQUICHandshakeError(&quic.StatelessResetError{}) != FailureConnectionReset {
t.Fatal("unexpected results")
}
})
t.Run("for quic handshake timeout", func(t *testing.T) {
t.Run("for handshake timeout", func(t *testing.T) {
if ClassifyQUICHandshakeError(&quic.HandshakeTimeoutError{}) != FailureGenericTimeoutError {
t.Fatal("unexpected results")
}
})
t.Run("for QUIC idle connection timeout", func(t *testing.T) {
t.Run("for idle timeout", func(t *testing.T) {
if ClassifyQUICHandshakeError(&quic.IdleTimeoutError{}) != FailureGenericTimeoutError {
t.Fatal("unexpected results")
}
})
t.Run("for QUIC CRYPTO Handshake", func(t *testing.T) {
var err quic.TransportErrorCode = quicTLSAlertHandshakeFailure
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
t.Run("for connection refused", func(t *testing.T) {
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: quic.ConnectionRefused}) != FailureConnectionRefused {
t.Fatal("unexpected results")
}
})
t.Run("for QUIC CRYPTO Invalid Certificate", func(t *testing.T) {
t.Run("for bad certificate", func(t *testing.T) {
var err quic.TransportErrorCode = quicTLSAlertBadCertificate
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
t.Fatal("unexpected results")
}
})
t.Run("for QUIC CRYPTO Unknown CA", func(t *testing.T) {
t.Run("for unsupported certificate", func(t *testing.T) {
var err quic.TransportErrorCode = quicTLSAlertUnsupportedCertificate
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
t.Fatal("unexpected results")
}
})
t.Run("for certificate expired", func(t *testing.T) {
var err quic.TransportErrorCode = quicTLSAlertCertificateExpired
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
t.Fatal("unexpected results")
}
})
t.Run("for certificate revoked", func(t *testing.T) {
var err quic.TransportErrorCode = quicTLSAlertCertificateRevoked
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
t.Fatal("unexpected results")
}
})
t.Run("for certificate unknown", func(t *testing.T) {
var err quic.TransportErrorCode = quicTLSAlertCertificateUnknown
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidCertificate {
t.Fatal("unexpected results")
}
})
t.Run("for decrypt error", func(t *testing.T) {
var err quic.TransportErrorCode = quicTLSAlertDecryptError
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
t.Fatal("unexpected results")
}
})
t.Run("for handshake failure", func(t *testing.T) {
var err quic.TransportErrorCode = quicTLSAlertHandshakeFailure
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLFailedHandshake {
t.Fatal("unexpected results")
}
})
t.Run("for unknown CA", func(t *testing.T) {
var err quic.TransportErrorCode = quicTLSAlertUnknownCA
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLUnknownAuthority {
t.Fatal("unexpected results")
}
})
t.Run("for QUIC CRYPTO Bad Hostname", func(t *testing.T) {
t.Run("for unrecognized hostname", func(t *testing.T) {
var err quic.TransportErrorCode = quicTLSUnrecognizedName
if ClassifyQUICHandshakeError(&quic.TransportError{ErrorCode: err}) != FailureSSLInvalidHostname {
t.Fatal("unexpected results")
@ -236,6 +218,9 @@ func TestClassifyQUICHandshakeError(t *testing.T) {
}
func TestClassifyResolverError(t *testing.T) {
// Please, keep this list sorted in the same order
// in which checks appear on the code
t.Run("for input being already an ErrWrapper", func(t *testing.T) {
err := &ErrWrapper{Failure: FailureEOFError}
if ClassifyResolverError(err) != FailureEOFError {
@ -257,6 +242,9 @@ func TestClassifyResolverError(t *testing.T) {
}
func TestClassifyTLSHandshakeError(t *testing.T) {
// Please, keep this list sorted in the same order
// in which checks appear on the code
t.Run("for input being already an ErrWrapper", func(t *testing.T) {
err := &ErrWrapper{Failure: FailureEOFError}
if ClassifyTLSHandshakeError(err) != FailureEOFError {