1// Copyright 2021 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package bigquery
16
17import (
18	"errors"
19	"io"
20	"net/http"
21	"net/url"
22	"testing"
23
24	"golang.org/x/xerrors"
25	"google.golang.org/api/googleapi"
26)
27
28func TestRetryableErrors(t *testing.T) {
29	for _, tc := range []struct {
30		description string
31		in          error
32		want        bool
33	}{
34		{
35			"nil error",
36			nil,
37			false,
38		},
39		{
40			"http stream closed",
41			errors.New("http2: stream closed"),
42			true,
43		},
44		{
45			"io ErrUnexpectedEOF",
46			io.ErrUnexpectedEOF,
47			true,
48		},
49		{
50			"unavailable",
51			&googleapi.Error{
52				Code:    http.StatusServiceUnavailable,
53				Message: "foo",
54			},
55			true,
56		},
57		{
58			"url connection error",
59			&url.Error{Op: "blah", URL: "blah", Err: errors.New("connection refused")},
60			true,
61		},
62		{
63			"url other error",
64			&url.Error{Op: "blah", URL: "blah", Err: errors.New("blah")},
65			false,
66		},
67		{
68			"wrapped retryable",
69			xerrors.Errorf("test of wrapped retryable: %w", &googleapi.Error{
70				Code:    http.StatusServiceUnavailable,
71				Message: "foo",
72				Errors: []googleapi.ErrorItem{
73					{Reason: "backendError", Message: "foo"},
74				},
75			}),
76			true,
77		},
78		{
79			"wrapped non-retryable",
80			xerrors.Errorf("test of wrapped retryable: %w", errors.New("blah")),
81			false,
82		},
83		{
84			// not retried per https://google.aip.dev/194
85			"internal error",
86			&googleapi.Error{
87				Code: http.StatusInternalServerError,
88			},
89			false,
90		},
91		{
92			"internal w/backend reason",
93			&googleapi.Error{
94				Code:    http.StatusServiceUnavailable,
95				Message: "foo",
96				Errors: []googleapi.ErrorItem{
97					{Reason: "backendError", Message: "foo"},
98				},
99			},
100			true,
101		},
102		{
103			"internal w/rateLimitExceeded reason",
104			&googleapi.Error{
105				Code:    http.StatusServiceUnavailable,
106				Message: "foo",
107				Errors: []googleapi.ErrorItem{
108					{Reason: "rateLimitExceeded", Message: "foo"},
109				},
110			},
111			true,
112		},
113		{
114			"bad gateway error",
115			&googleapi.Error{
116				Code:    http.StatusBadGateway,
117				Message: "foo",
118			},
119			true,
120		},
121	} {
122		got := retryableError(tc.in)
123		if got != tc.want {
124			t.Errorf("case (%s) mismatch:  got %t want %t", tc.description, got, tc.want)
125		}
126	}
127}
128