2017-08-15

This commit is contained in:
2017-08-15 09:55:28 -05:00
parent 549ba5084e
commit 4a9b690bb2
28 changed files with 1153 additions and 18 deletions

View 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.

View 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)

View 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
}

View 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")
}
}