2017-08-15
This commit is contained in:
31
go/error-handling/README.md
Normal file
31
go/error-handling/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Error Handling
|
||||
|
||||
Implement various kinds of error handling and resource management.
|
||||
|
||||
An important point of programming is how to handle errors and close
|
||||
resources even if errors occur.
|
||||
|
||||
This exercise requires you to handle various errors. Because error handling
|
||||
is rather programming language specific you'll have to refer to the tests
|
||||
for your track to see what's exactly required.
|
||||
|
||||
## Running the tests
|
||||
|
||||
To run the tests run the command `go test` from within the exercise directory.
|
||||
|
||||
If the test suite contains benchmarks, you can run these with the `-bench`
|
||||
flag:
|
||||
|
||||
go test -bench .
|
||||
|
||||
Keep in mind that each reviewer will run benchmarks on a different machine, with
|
||||
different specs, so the results from these benchmark tests may vary.
|
||||
|
||||
## Further information
|
||||
|
||||
For more detailed information about the Go track, including how to get help if
|
||||
you're having trouble, please visit the exercism.io [Go language page](http://exercism.io/languages/go/about).
|
||||
|
||||
|
||||
## Submitting Incomplete Solutions
|
||||
It's possible to submit an incomplete solution so you can see how others have completed the exercise.
|
57
go/error-handling/common.go
Normal file
57
go/error-handling/common.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package erratum
|
||||
|
||||
import "io"
|
||||
|
||||
// These are the support types and interface definitions used in the
|
||||
// implementation if your Use function. See the test suite file at
|
||||
// for information on the expected implementation.
|
||||
//
|
||||
// Because this is part of the package "erratum", if your solution file
|
||||
// is also declared in the package you will automatically have access to
|
||||
// these definitions (you do not have to re-declare them).
|
||||
|
||||
// TransientError is an error that may occur while opening a resource via
|
||||
// ResourceOpener.
|
||||
type TransientError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e TransientError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
// FrobError is a possible error from doing some frobbing, your implementation
|
||||
// will require calling your Resource's Defrob(string) method.
|
||||
// When this error occurs, the FrobError's defrobTag string will contain the
|
||||
// string you must pass into Defrob.
|
||||
type FrobError struct {
|
||||
defrobTag string
|
||||
inner error
|
||||
}
|
||||
|
||||
func (e FrobError) Error() string {
|
||||
return e.inner.Error()
|
||||
}
|
||||
|
||||
type Resource interface {
|
||||
|
||||
// Resource is using composition to inherit the requirements of the io.Closer
|
||||
// interface. What this means is that a Resource will have a .Close() method.
|
||||
io.Closer
|
||||
|
||||
// Frob does something with the input string.
|
||||
// Because this is an incredibly badly designed system if there is an error
|
||||
// it panics.
|
||||
//
|
||||
// The paniced error may be a FrobError in which case Defrob should be called
|
||||
// with the defrobTag string.
|
||||
Frob(string)
|
||||
|
||||
Defrob(string)
|
||||
}
|
||||
|
||||
// ResourceOpener is a function that creates a resource.
|
||||
//
|
||||
// It may return a wrapped error of type TransientError. In this case the resource
|
||||
// is temporarily unavailable and the caller should retry soon.
|
||||
type ResourceOpener func() (Resource, error)
|
35
go/error-handling/error_handling.go
Normal file
35
go/error-handling/error_handling.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package erratum
|
||||
|
||||
const testVersion = 2
|
||||
|
||||
func Use(o ResourceOpener, inp string) (err error) {
|
||||
var r Resource
|
||||
moveOn := false
|
||||
for !moveOn {
|
||||
moveOn = true
|
||||
if r, err = o(); err != nil {
|
||||
switch err.(type) {
|
||||
case TransientError:
|
||||
moveOn = false
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
defer r.Close()
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
switch v := rec.(type) {
|
||||
case FrobError:
|
||||
r.Defrob(v.defrobTag)
|
||||
err = v
|
||||
case error:
|
||||
err = v
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
r.Frob(inp)
|
||||
|
||||
return err
|
||||
}
|
191
go/error-handling/error_handling_test.go
Normal file
191
go/error-handling/error_handling_test.go
Normal file
@@ -0,0 +1,191 @@
|
||||
package erratum
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Because this exercise is generally unique to each language and how it
|
||||
// handles errors, most of the definition of your expected solution is provided
|
||||
// here instead of the README.
|
||||
// You should read this carefully (more than once) before implementation.
|
||||
|
||||
// Define a function `Use(o ResourceOpener, input string) error` that opens a
|
||||
// resource, calls Frob(input) and closes the resource (in all cases). Your
|
||||
// function should properly handle errors, as defined by the expectations of
|
||||
// this test suite. ResourceOpener will be a function you may invoke directly
|
||||
// `o()` in an attempt to "open" the resource. It returns a Resource and error
|
||||
// value in the idiomatic Go fashion:
|
||||
// https://blog.golang.org/error-handling-and-go
|
||||
//
|
||||
// See the ./common.go file for the definitions of Resource, ResourceOpener,
|
||||
// FrobError and TransientError.
|
||||
//
|
||||
// There will be a few places in your Use function where errors may occur:
|
||||
//
|
||||
// - Invoking the ResourceOpener function passed into Use as the first
|
||||
// parameter, it may fail with a TransientError, if so keep trying to open it.
|
||||
// If it is some other sort of error, return it.
|
||||
//
|
||||
// - Calling the Frob function on the Resource returned from the ResourceOpener
|
||||
// function, it may panic with a FrobError (or another type of error). If
|
||||
// it is indeed a FrobError you will have to call the Resource's Defrob
|
||||
// function using the FrobError's defrobTag variable as input. Either way
|
||||
// return the error.
|
||||
//
|
||||
// Also note: if the Resource was opened successfully make sure to call its
|
||||
// Close function no matter what (even if errors occur).
|
||||
//
|
||||
// If you are new to Go errors or panics here is a good place to start:
|
||||
// https://blog.golang.org/defer-panic-and-recover
|
||||
//
|
||||
// You may also need to look at named return values as a helpful way to
|
||||
// return error information from panic recovery:
|
||||
// https://tour.golang.org/basics/7
|
||||
|
||||
const targetTestVersion = 2
|
||||
|
||||
// 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) }
|
||||
|
||||
func TestTestVersion(t *testing.T) {
|
||||
if testVersion != targetTestVersion {
|
||||
t.Fatalf("Found testVersion = %v, want %v", testVersion, targetTestVersion)
|
||||
}
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user