1
exercism fetch go error-handling

error_handling_test.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package erratum

import (
	"errors"
	"testing"
)

// Please review the README for this exercise carefully before implementation.

// Little helper to let us customize behaviour of the resource on a per-test
// basis.
type mockResource struct {
	close  func() error
	frob   func(string)
	defrob func(string)
}

func (mr mockResource) Close() error      { return mr.close() }
func (mr mockResource) Frob(input string) { mr.frob(input) }
func (mr mockResource) Defrob(tag string) { mr.defrob(tag) }

// Use should not return an error on the "happy" path.
func TestNoErrors(t *testing.T) {
	var frobInput string
	var closeCalled bool
	mr := mockResource{
		close: func() error { closeCalled = true; return nil },
		frob:  func(input string) { frobInput = input },
	}
	opener := func() (Resource, error) { return mr, nil }
	inp := "hello"
	err := Use(opener, inp)
	if err != nil {
		t.Fatalf("Unexpected error from Use: %v", err)
	}
	if frobInput != inp {
		t.Fatalf("Wrong string passed to Frob: got %v, expected %v", frobInput, inp)
	}
	if !closeCalled {
		t.Fatalf("Close was not called")
	}
}

// Use should keep trying if a transient error is returned on open.
func TestKeepTryOpenOnTransient(t *testing.T) {
	var frobInput string
	mr := mockResource{
		close: func() error { return nil },
		frob:  func(input string) { frobInput = input },
	}
	nthCall := 0
	opener := func() (Resource, error) {
		if nthCall < 3 {
			nthCall++
			return mockResource{}, TransientError{errors.New("some error")}
		}
		return mr, nil
	}
	inp := "hello"
	err := Use(opener, inp)
	if err != nil {
		t.Fatalf("Unexpected error from Use: %v", err)
	}
	if frobInput != inp {
		t.Fatalf("Wrong string passed to Frob: got %v, expected %v", frobInput, inp)
	}
}

// Use should fail if a non-transient error is returned on open.
func TestFailOpenOnNonTransient(t *testing.T) {
	nthCall := 0
	opener := func() (Resource, error) {
		if nthCall < 3 {
			nthCall++
			return mockResource{}, TransientError{errors.New("some error")}
		}
		return nil, errors.New("too awesome")
	}
	inp := "hello"
	err := Use(opener, inp)
	if err == nil {
		t.Fatalf("Unexpected lack of error from Use")
	}
	if err.Error() != "too awesome" {
		t.Fatalf("Invalid error returned from Use")
	}
}

// Use should call Defrob and Close on FrobError panic from Frob
// and return the error.
func TestCallDefrobAndCloseOnFrobError(t *testing.T) {
	tag := "moo"
	var closeCalled bool
	var defrobTag string
	mr := mockResource{
		close: func() error { closeCalled = true; return nil },
		frob:  func(input string) { panic(FrobError{tag, errors.New("meh")}) },
		defrob: func(tag string) {
			if closeCalled {
				t.Fatalf("Close was called before Defrob")
			}
			defrobTag = tag
		},
	}
	opener := func() (Resource, error) { return mr, nil }
	inp := "hello"
	err := Use(opener, inp)
	if err == nil {
		t.Fatalf("Unexpected lack of error from Use")
	}
	if err.Error() != "meh" {
		t.Fatalf("Invalid error returned from Use")
	}
	if defrobTag != tag {
		t.Fatalf("Wrong string passed to Defrob: got %v, expected %v", defrobTag, tag)
	}
	if !closeCalled {
		t.Fatalf("Close was not called")
	}
}

// Use should call Close but not Defrob on non-FrobError panic from Frob
// and return the error.
func TestCallCloseNonOnFrobError(t *testing.T) {
	var closeCalled bool
	var defrobCalled bool
	mr := mockResource{
		close:  func() error { closeCalled = true; return nil },
		frob:   func(input string) { panic(errors.New("meh")) },
		defrob: func(tag string) { defrobCalled = true },
	}
	opener := func() (Resource, error) { return mr, nil }
	inp := "hello"
	err := Use(opener, inp)
	if err == nil {
		t.Fatalf("Unexpected lack of error from Use")
	}
	if err.Error() != "meh" {
		t.Fatalf("Invalid error returned from Use")
	}
	if defrobCalled {
		t.Fatalf("Defrob was called")
	}
	if !closeCalled {
		t.Fatalf("Close was not called")
	}
}