Initial Commit

This commit is contained in:
2016-08-13 18:20:14 -05:00
commit 50f4a86fd8
408 changed files with 15301 additions and 0 deletions

40
go/bank-account/README.md Normal file
View File

@@ -0,0 +1,40 @@
# Bank Account
Bank accounts can be accessed in different ways at the same time.
A bank account can be accessed in multiple ways. Clients can make
deposits and withdrawals using the internet, mobile phones, etc. Shops
can charge against the account.
Create an account that can be accessed from multiple threads/processes
(terminology depends on your programming language).
It should be possible to close an account; operations against a closed
account must fail.
## Instructions
Run the test file, and fix each of the errors in turn. When you get the
first test to pass, go to the first pending or skipped test, and make
that pass as well. When all of the tests are passing, feel free to
submit.
Remember that passing code is just the first step. The goal is to work
towards a solution that is as readable and expressive as you can make
it.
Have fun!
To run the tests simply run the command `go test` in the exercise directory.
If the test suite contains benchmarks, you can run these with the `-bench`
flag:
go test -bench .
For more detailed info about the Go track see the [help
page](http://help.exercism.io/getting-started-with-go.html).
## Source
[view source]()

View File

@@ -0,0 +1,58 @@
package account
import "sync"
// Account just represents a user's account
type Account struct {
sync.RWMutex
balance int
closed bool
}
// Open returns a new account
func Open(amt int) *Account {
a := new(Account)
_, ok := a.Deposit(amt)
if ok {
return a
}
return nil
}
// Close returns the payout amount and an 'ok' flag
func (a *Account) Close() (int, bool) {
a.Lock()
ret := a.balance
if a.closed {
a.Unlock()
return 0, false
}
a.closed = true
a.balance = 0
a.Unlock()
return ret, true
}
// Balance returns the current account balance
// and an 'ok' flag
func (a *Account) Balance() (int, bool) {
if a.closed {
return 0, false
}
return a.balance, true
}
// Deposit takes an amount (can be a withdrawal)
// and returns the new balance and an 'ok' flag
func (a *Account) Deposit(amount int) (int, bool) {
var ret int
var ok bool
a.Lock()
if !a.closed && a.balance+amount >= 0 {
a.balance += amount
ret = a.balance
ok = true
}
a.Unlock()
return ret, ok
}

View File

@@ -0,0 +1,289 @@
// API:
//
// Open(initalDeposit int64) *Account
// (Account) Close() (payout int64, ok bool)
// (Account) Balance() (balance int64, ok bool)
// (Account) Deposit(amount uint64) (newBalance int64, ok bool)
//
// If Open is given a negative initial deposit, it must return nil.
// Deposit must handle a negative amount as a withdrawal.
// If any Account method is called on an closed account, it must not modify
// the account and must return ok = false.
package account
import (
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
)
func TestSeqOpenBalanceClose(t *testing.T) {
// open account
const amt = 10
a := Open(amt)
if a == nil {
t.Fatalf("Open(%d) = nil, want non-nil *Account.", amt)
}
t.Logf("Account 'a' opened with initial balance of %d.", amt)
// verify balance after open
switch b, ok := a.Balance(); {
case !ok:
t.Fatal("a.Balance() returned !ok, want ok.")
case b != amt:
t.Fatalf("a.Balance() = %d, want %d", b, amt)
}
// close account
switch p, ok := a.Close(); {
case !ok:
t.Fatalf("a.Close() returned !ok, want ok.")
case p != amt:
t.Fatalf("a.Close() returned payout = %d, want %d.", p, amt)
}
t.Log("Account 'a' closed.")
// verify balance no longer accessible
if b, ok := a.Balance(); ok {
t.Log("Balance still available on closed account.")
t.Fatalf("a.Balance() = %d, %t. Want ok == false", b, ok)
}
}
func TestSeqOpenDepositClose(t *testing.T) {
// open account
const openAmt = 10
a := Open(openAmt)
if a == nil {
t.Fatalf("Open(%d) = nil, want non-nil *Account.", openAmt)
}
t.Logf("Account 'a' opened with initial balance of %d.", openAmt)
// deposit
const depAmt = 20
const newAmt = openAmt + depAmt
switch b, ok := a.Deposit(depAmt); {
case !ok:
t.Fatalf("a.Deposit(%d) returned !ok, want ok.", depAmt)
case b != openAmt+depAmt:
t.Fatalf("a.Deposit(%d) = %d, want new balance = %d", depAmt, b, newAmt)
}
t.Logf("Deposit of %d accepted to account 'a'", depAmt)
// close account
switch p, ok := a.Close(); {
case !ok:
t.Fatalf("a.Close() returned !ok, want ok.")
case p != newAmt:
t.Fatalf("a.Close() returned payout = %d, want %d.", p, newAmt)
}
t.Log("Account 'a' closed.")
// verify deposits no longer accepted
if b, ok := a.Deposit(1); ok {
t.Log("Deposit accepted on closed account.")
t.Fatalf("a.Deposit(1) = %d, %t. Want ok == false", b, ok)
}
}
func TestMoreSeqCases(t *testing.T) {
// open account 'a' as before
const openAmt = 10
a := Open(openAmt)
if a == nil {
t.Fatalf("Open(%d) = nil, want non-nil *Account.", openAmt)
}
t.Logf("Account 'a' opened with initial balance of %d.", openAmt)
// open account 'z' with zero balance
z := Open(0)
if z == nil {
t.Fatal("Open(0) = nil, want non-nil *Account.")
}
t.Log("Account 'z' opened with initial balance of 0.")
// attempt to open account with negative opening balance
if Open(-10) != nil {
t.Fatal("Open(-10) seemed to work, " +
"want nil result for negative opening balance.")
}
// verify both balances a and z still there
switch b, ok := a.Balance(); {
case !ok:
t.Fatal("a.Balance() returned !ok, want ok.")
case b != openAmt:
t.Fatalf("a.Balance() = %d, want %d", b, openAmt)
}
switch b, ok := z.Balance(); {
case !ok:
t.Fatal("z.Balance() returned !ok, want ok.")
case b != 0:
t.Fatalf("z.Balance() = %d, want 0", b)
}
// withdrawals
const wAmt = 3
const newAmt = openAmt - wAmt
switch b, ok := a.Deposit(-wAmt); {
case !ok:
t.Fatalf("a.Deposit(%d) returned !ok, want ok.", -wAmt)
case b != newAmt:
t.Fatalf("a.Deposit(%d) = %d, want new balance = %d", -wAmt, b, newAmt)
}
t.Logf("Withdrawal of %d accepted from account 'a'", wAmt)
if _, ok := z.Deposit(-1); ok {
t.Fatal("z.Deposit(-1) returned ok, want !ok.")
}
// verify both balances
switch b, ok := a.Balance(); {
case !ok:
t.Fatal("a.Balance() returned !ok, want ok.")
case b != newAmt:
t.Fatalf("a.Balance() = %d, want %d", b, newAmt)
}
switch b, ok := z.Balance(); {
case !ok:
t.Fatal("z.Balance() returned !ok, want ok.")
case b != 0:
t.Fatalf("z.Balance() = %d, want 0", b)
}
// close just z
switch p, ok := z.Close(); {
case !ok:
t.Fatalf("z.Close() returned !ok, want ok.")
case p != 0:
t.Fatalf("z.Close() returned payout = %d, want 0.", p)
}
t.Log("Account 'z' closed.")
// verify 'a' balance one more time
switch b, ok := a.Balance(); {
case !ok:
t.Fatal("a.Balance() returned !ok, want ok.")
case b != newAmt:
t.Fatalf("a.Balance() = %d, want %d", b, newAmt)
}
}
func TestConcClose(t *testing.T) {
if runtime.NumCPU() < 2 {
t.Skip("Multiple CPU cores required for concurrency tests.")
}
if runtime.GOMAXPROCS(0) < 2 {
runtime.GOMAXPROCS(2)
}
// test competing close attempts
for rep := 0; rep < 1000; rep++ {
const openAmt = 10
a := Open(openAmt)
if a == nil {
t.Fatalf("Open(%d) = nil, want non-nil *Account.", openAmt)
}
var start sync.WaitGroup
start.Add(1)
const closeAttempts = 10
res := make(chan string)
for i := 0; i < closeAttempts; i++ {
go func() { // on your mark,
start.Wait() // get set...
switch p, ok := a.Close(); {
case !ok:
if p != 0 {
t.Errorf("a.Close() = %d, %t. "+
"Want payout = 0 for unsuccessful close", p, ok)
res <- "fail"
} else {
res <- "already closed"
}
case p != openAmt:
t.Errorf("a.Close() = %d, %t. "+
"Want payout = %d for successful close", p, ok, openAmt)
res <- "fail"
default:
res <- "close" // exactly one goroutine should reach here
}
}()
}
start.Done() // ...go
var closes, fails int
for i := 0; i < closeAttempts; i++ {
switch <-res {
case "close":
closes++
case "fail":
fails++
}
}
switch {
case fails > 0:
t.FailNow() // error already logged by other goroutine
case closes == 0:
t.Fatal("Concurrent a.Close() attempts all failed. " +
"Want one to succeed.")
case closes > 1:
t.Fatalf("%d concurrent a.Close() attempts succeeded, "+
"each paying out %d!. Want just one to succeed.",
closes, openAmt)
}
}
}
func TestConcDeposit(t *testing.T) {
if runtime.NumCPU() < 2 {
t.Skip("Multiple CPU cores required for concurrency tests.")
}
if runtime.GOMAXPROCS(0) < 2 {
runtime.GOMAXPROCS(2)
}
a := Open(0)
if a == nil {
t.Fatal("Open(0) = nil, want non-nil *Account.")
}
const amt = 10
const c = 1000
var negBal int32
var start, g sync.WaitGroup
start.Add(1)
g.Add(3 * c)
for i := 0; i < c; i++ {
go func() { // deposit
start.Wait()
a.Deposit(amt) // ignore return values
g.Done()
}()
go func() { // withdraw
start.Wait()
for {
if _, ok := a.Deposit(-amt); ok {
break
}
time.Sleep(time.Microsecond) // retry
}
g.Done()
}()
go func() { // watch that balance stays >= 0
start.Wait()
if p, _ := a.Balance(); p < 0 {
atomic.StoreInt32(&negBal, 1)
}
g.Done()
}()
}
start.Done()
g.Wait()
if negBal == 1 {
t.Fatal("Balance went negative with concurrent deposits and " +
"withdrawals. Want balance always >= 0.")
}
if p, ok := a.Balance(); !ok || p != 0 {
t.Fatalf("After equal concurrent deposits and withdrawals, "+
"a.Balance = %d, %t. Want 0, true", p, ok)
}
}