First Commit

This commit is contained in:
mitch 2022-01-19 23:20:12 -05:00
commit 828f289f3c
36 changed files with 1763 additions and 0 deletions

105
.gitignore vendored Normal file
View File

@ -0,0 +1,105 @@
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.gitignore.io/api/macos,visualstudiocode,terraform,windows
# Edit at https://www.gitignore.io/?templates=macos,visualstudiocode,terraform,windows
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Terraform ###
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
*.plan
# Crash log files
crash.log
# Ignore any .tfvars files that are generated automatically for each Terraform run. Most
# .tfvars files are managed as part of configuration and so should be included in
# version control.
#
# example.tfvars
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
secrets.tfvars
# Include override files you do wish to add to version control using negated pattern
#
# !example_override.tf
### VisualStudioCode ###
.vscode/*
#!.vscode/settings.json
#!.vscode/tasks.json
#!.vscode/launch.json
#!.vscode/extensions.json
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.gitignore.io/api/macos,visualstudiocode,terraform,windows
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
.vscode/*
.idea

33
adder.go Normal file
View File

@ -0,0 +1,33 @@
package main
//func main() {
//}
func Add(numbers []int) int {
var sum int
for _, num := range numbers {
sum += num
}
return sum
}
func SumAll(numbersToSum ...[]int) []int {
var sums []int
for _, numbers := range numbersToSum {
sums = append(sums, Add(numbers))
}
return sums
}
func SumAllTails(numbersToSum ...[]int) []int {
var sums []int
for _, numbers := range numbersToSum {
if len(numbers) == 0 {
sums = append(sums, 0)
} else {
tail := numbers[1:]
sums = append(sums, Add(tail))
}
}
return sums
}

65
adder_test.go Normal file
View File

@ -0,0 +1,65 @@
package main
import (
"reflect"
"testing"
)
func TestAdder(t *testing.T) {
assertCorrectMessage := func(t testing.TB, sum, expect int) {
if sum != expect {
t.Errorf("expected %d but got %d", sum, expect)
}
}
t.Run("2 + 2", func(t *testing.T) {
numbers := []int{2, 2}
sum := Add(numbers)
expect := 4
assertCorrectMessage(t, sum, expect)
})
t.Run("1 + 1", func(t *testing.T) {
numbers := []int{1, 1}
sum := Add(numbers)
expect := 2
assertCorrectMessage(t, sum, expect)
})
t.Run("1 + 2 + 3 + 4 + 5", func(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
sum := Add(numbers)
expect := 15
assertCorrectMessage(t, sum, expect)
})
}
func TestAdderSlice(t *testing.T) {
got := SumAll([]int{1, 2}, []int{0, 9})
want := []int{3, 9}
// this is not type safe
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
func TestSumAllTails(t *testing.T) {
checkSums := func(t testing.TB, got, want []int) {
t.Helper()
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v want %v", got, want)
}
}
t.Run("make the some of some slices", func(t *testing.T) {
got := SumAllTails([]int{1, 2}, []int{0, 9})
want := []int{2, 9}
checkSums(t, got, want)
})
t.Run("safely sum empty slices", func(t *testing.T) {
got := SumAllTails([]int{}, []int{3, 4, 5})
want := []int{0, 9}
checkSums(t, got, want)
})
}

34
clockface.go Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"math"
"time"
)
const secondHandLength = 90
const clockCentreX = 150
const clockCentreY = 150
type Point struct {
X float64
Y float64
}
func SecondHand(t time.Time) Point {
p := secondHandPoint(t)
p = Point{p.X * secondHandLength, p.Y * secondHandLength} //scale
p = Point{p.X, -p.Y} //flip
p = Point{p.X + clockCentreX, p.Y + clockCentreY} //translate
return p
}
func secondsInRadians(t time.Time) float64 {
return math.Pi / (30 / (float64(t.Second())))
}
func secondHandPoint(t time.Time) Point {
angle := secondsInRadians(t)
x := math.Sin(angle)
y := math.Cos(angle)
return Point{x, y}
}

1
clockface/clockface.go Normal file
View File

@ -0,0 +1 @@
package clockface

View File

@ -0,0 +1,34 @@
package clockface
import (
"math"
"time"
)
const secondHandLength = 90
const clockCentreX = 150
const clockCentreY = 150
type Point struct {
X float64
Y float64
}
func SecondHand(t time.Time) Point {
p := secondHandPoint(t)
p = Point{p.X * secondHandLength, p.Y * secondHandLength} //scale
p = Point{p.X, -p.Y} //flip
p = Point{p.X + clockCentreX, p.Y + clockCentreY} //translate
return p
}
func secondsInRadians(t time.Time) float64 {
return math.Pi / (30 / (float64(t.Second())))
}
func secondHandPoint(t time.Time) Point {
angle := secondsInRadians(t)
x := math.Sin(angle)
y := math.Cos(angle)
return Point{x, y}
}

View File

@ -0,0 +1,28 @@
package clockface
import (
"testing"
"time"
)
func TestSecondHandAtMidnight(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 0, 0, time.UTC)
want := Point{X: 150, Y: 150 - 90}
got := SecondHand(tm)
if got != want {
t.Errorf("Got %v, want %v", got, want)
}
}
func TestSecondHandAt30Seconds(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 30, 0, time.UTC)
want := Point{X: 150, Y: 150 + 90}
got := SecondHand(tm)
if got != want {
t.Errorf("Got %v, wanted %v", got, want)
}
}

View File

@ -0,0 +1,65 @@
package clockface
import (
"math"
"testing"
"time"
)
func simpleTime(hours, minutes, seconds int) time.Time {
return time.Date(312, time.October, 28, hours, minutes, seconds, 0, time.UTC)
}
func testName(t time.Time) string {
return t.Format("15:04:05")
}
func TestSecondsInRadians(t *testing.T) {
cases := []struct {
time time.Time
angle float64
}{
{simpleTime(0, 0, 30), math.Pi},
{simpleTime(0, 0, 0), 0},
{simpleTime(0, 0, 45), (math.Pi / 2) * 3},
{simpleTime(0, 0, 7), (math.Pi / 30) * 7},
}
for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := secondsInRadians(c.time)
if !roughlyEqualFloat64(got, c.angle) {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}
func TestSecondHandVector(t *testing.T) {
cases := []struct {
time time.Time
point Point
}{
{simpleTime(0, 0, 30), Point{0, -1}},
{simpleTime(0, 0, 45), Point{-1, 0}},
}
for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := secondHandPoint(c.time)
if !roughlyEqualPoint(got, c.point) {
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
}
})
}
}
func roughlyEqualFloat64(a, b float64) bool {
const equalityThreshold = 1e-7
return math.Abs(a-b) < equalityThreshold
}
func roughlyEqualPoint(a, b Point) bool {
return roughlyEqualFloat64(a.X, b.X) &&
roughlyEqualFloat64(a.Y, b.Y)
}

87
clockface_test.go Normal file
View File

@ -0,0 +1,87 @@
package main
import (
"math"
"testing"
"time"
)
func simpleTime(hours, minutes, seconds int) time.Time {
return time.Date(312, time.October, 28, hours, minutes, seconds, 0, time.UTC)
}
func testName(t time.Time) string {
return t.Format("15:04:05")
}
func TestSecondsInRadians(t *testing.T) {
cases := []struct {
time time.Time
angle float64
}{
{simpleTime(0, 0, 30), math.Pi},
{simpleTime(0, 0, 0), 0},
{simpleTime(0, 0, 45), (math.Pi / 2) * 3},
{simpleTime(0, 0, 7), (math.Pi / 30) * 7},
}
for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := secondsInRadians(c.time)
if !roughlyEqualFloat64(got, c.angle) {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}
func TestSecondHandVector(t *testing.T) {
cases := []struct {
time time.Time
point Point
}{
{simpleTime(0, 0, 30), Point{0, -1}},
{simpleTime(0, 0, 45), Point{-1, 0}},
}
for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := secondHandPoint(c.time)
if !roughlyEqualPoint(got, c.point) {
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
}
})
}
}
func roughlyEqualFloat64(a, b float64) bool {
const equalityThreshold = 1e-7
return math.Abs(a-b) < equalityThreshold
}
func roughlyEqualPoint(a, b Point) bool {
return roughlyEqualFloat64(a.X, b.X) &&
roughlyEqualFloat64(a.Y, b.Y)
}
func TestSecondHandAtMidnight(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 0, 0, time.UTC)
want := Point{X: 150, Y: 150 - 90}
got := SecondHand(tm)
if got != want {
t.Errorf("Got %v, want %v", got, want)
}
}
func TestSecondHandAt30Seconds(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 30, 0, time.UTC)
want := Point{X: 150, Y: 150 + 90}
got := SecondHand(tm)
if got != want {
t.Errorf("Got %v, wanted %v", got, want)
}
}

25
concurrency.go Normal file
View File

@ -0,0 +1,25 @@
package main
type WebsiteChecker func(string) bool
type result struct {
string
bool
}
func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
results := make(map[string]bool)
resultChannel := make(chan result)
for _, url := range urls {
go func(u string) {
resultChannel <- result{u, wc(u)}
}(url)
}
for i := 0; i < len(urls); i++ {
r := <-resultChannel
results[r.string] = r.bool
}
return results
}

50
concurrency_test.go Normal file
View File

@ -0,0 +1,50 @@
package main
import (
"reflect"
"testing"
"time"
)
func mockWebsiteChecker(url string) bool {
if url == "waat://furhurterwe.geds" {
return false
}
return true
}
func slowStubWebsiteChecker(_ string) bool {
time.Sleep(20 * time.Millisecond)
return true
}
func TestCheckWebsites(t *testing.T) {
websites := []string{
"http://google.com",
"http://blog.gypsydave5.com",
"waat://furhurterwe.geds",
}
want := map[string]bool{
"http://google.com": true,
"http://blog.gypsydave5.com": true,
"waat://furhurterwe.geds": false,
}
got := CheckWebsites(mockWebsiteChecker, websites)
if !reflect.DeepEqual(want, got) {
t.Fatalf("Wanted %v, got %v", want, got)
}
}
func BenchmarkCheckWebsites(b *testing.B) {
urls := make([]string, 100)
for i := 0; i < len(urls); i++ {
urls[i] = "a url"
}
for i := 0; i < b.N; i++ {
CheckWebsites(slowStubWebsiteChecker, urls)
}
}

22
context.go Normal file
View File

@ -0,0 +1,22 @@
package main
import (
"context"
"fmt"
"net/http"
)
type Store interface {
Fetch(ctx context.Context) (string, error)
}
func Server(store Store) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := store.Fetch(r.Context())
if err != nil {
return // todo: log error however you like
}
fmt.Fprint(w, data)
}
}

95
context_test.go Normal file
View File

@ -0,0 +1,95 @@
package main
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
)
type SpyStore struct {
response string
t *testing.T
}
func (s *SpyStore) Fetch(ctx context.Context) (string, error) {
data := make(chan string, 1)
go func() {
var result string
for _, c := range s.response {
select {
case <-ctx.Done():
s.t.Log("spy store got cancelled")
return
default:
time.Sleep(10 * time.Millisecond)
result += string(c)
}
}
data <- result
}()
select {
case <-ctx.Done():
return "", ctx.Err()
case res := <-data:
return res, nil
}
}
type SpyResponseWriter struct {
written bool
}
func (s *SpyResponseWriter) Header() http.Header {
s.written = true
return nil
}
func (s *SpyResponseWriter) Write([]byte) (int, error) {
s.written = true
return 0, errors.New("not implemented")
}
func (s *SpyResponseWriter) WriteHeader(statusCode int) {
s.written = true
}
func TestServer(t *testing.T) {
data := "hello world"
t.Run("Returns data from store", func(t *testing.T) {
store := &SpyStore{response: data, t: t}
svr := Server(store)
request := httptest.NewRequest(http.MethodGet, "/", nil)
response := httptest.NewRecorder()
svr.ServeHTTP(response, request)
if response.Body.String() != data {
t.Errorf(`got "%s", want "%s"`, response.Body.String(), data)
}
})
t.Run("tells store to cancel work if request is cancelled", func(t *testing.T) {
store := &SpyStore{response: data, t: t}
svr := Server(store)
request := httptest.NewRequest(http.MethodGet, "/", nil)
cancellingCtx, cancel := context.WithCancel(request.Context())
time.AfterFunc(5*time.Millisecond, cancel)
request = request.WithContext(cancellingCtx)
response := &SpyResponseWriter{}
svr.ServeHTTP(response, request)
if response.written {
t.Error("a response should not have been written")
}
})
}

14
di.go Normal file
View File

@ -0,0 +1,14 @@
package main
import (
"fmt"
"io"
)
func Greet(writer io.Writer, name string) {
fmt.Fprintf(writer, "Hello, %s", name)
}
//func main() {
// Greet(os.Stdout, "Elodie")
//}

18
di_test.go Normal file
View File

@ -0,0 +1,18 @@
package main
import (
"bytes"
"testing"
)
func TestGreet(t *testing.T) {
buffer := bytes.Buffer{}
Greet(&buffer, "Chris")
got := buffer.String()
want := "Hello, Chris"
if got != want {
t.Errorf("got %q want %q", got, want)
}
}

53
dictionary.go Normal file
View File

@ -0,0 +1,53 @@
package main
const (
ErrNotFound = DictionaryErr("could not find the word you were looking for")
ErrWordExists = DictionaryErr("cannot add word because it already exists")
ErrWordDoesNotExist = DictionaryErr("cannot update word because it does not exist")
)
type Dictionary map[string]string
type DictionaryErr string
func (e DictionaryErr) Error() string {
return string(e)
}
func (d Dictionary) Search(word string) (string, error) {
definition, ok := d[word]
if !ok {
return "", ErrNotFound
}
return definition, nil
}
func (d Dictionary) Add(word, definition string) error {
_, err := d.Search(word)
switch err {
case ErrNotFound:
d[word] = definition
case nil:
return ErrWordExists
default:
return err
}
return nil
}
func (d Dictionary) Update(word, definition string) error {
_, err := d.Search(word)
switch err {
case ErrNotFound:
return ErrWordDoesNotExist
case nil:
d[word] = definition
default:
return err
}
return nil
}
func (d Dictionary) Delete(word string) {
delete(d, word)
}

105
dictionary_test.go Normal file
View File

@ -0,0 +1,105 @@
package main
import "testing"
func TestSearch(t *testing.T) {
dictionary := Dictionary{"test": "this is just a test"}
t.Run("known word", func(t *testing.T) {
word := "test"
definition := "this is just a test"
assertDefinition(t, dictionary, word, definition)
})
t.Run("unknown word", func(t *testing.T) {
_, got := dictionary.Search("unknown")
assertError(t, got, ErrNotFound)
})
}
func TestAdd(t *testing.T) {
t.Run("new word", func(t *testing.T) {
dictionary := Dictionary{}
word := "test"
definition := "this is just a test"
err := dictionary.Add(word, definition)
assertError(t, err, nil)
assertDefinition(t, dictionary, word, definition)
})
t.Run("existing word", func(t *testing.T) {
word := "test"
definition := "this is just a test"
dictionary := Dictionary{word: definition}
err := dictionary.Add(word, "new test")
assertError(t, err, ErrWordExists)
assertDefinition(t, dictionary, word, definition)
})
}
func TestUpdate(t *testing.T) {
t.Run("existing word", func(t *testing.T) {
word := "test"
definition := "this is just a test"
dictionary := Dictionary{word: definition}
newDefinition := "new definition"
err := dictionary.Update(word, newDefinition)
assertError(t, err, nil)
assertDefinition(t, dictionary, word, newDefinition)
})
t.Run("new word", func(t *testing.T) {
word := "test"
definition := "this is just a test"
dictionary := Dictionary{}
err := dictionary.Update(word, definition)
assertError(t, err, ErrWordDoesNotExist)
})
}
func TestDelete(t *testing.T) {
t.Run("existing word", func(t *testing.T) {
word := "test"
definition := "this is just a test"
dictionary := Dictionary{word: definition}
dictionary.Delete(word)
_, err := dictionary.Search(word)
if err != ErrNotFound {
t.Errorf("Expected %q to be deleted", word)
}
})
}
func assertDefinition(t testing.TB, dictionary Dictionary, word, definition string) {
t.Helper()
got, err := dictionary.Search(word)
if err != nil {
t.Fatal("Should find added word:", err)
}
if definition != got {
t.Errorf("got %q want %q", got, definition)
}
}
func assertError(t testing.TB, got, want error) {
t.Helper()
if got != want {
t.Errorf("got error %q want %q", got, want)
}
}

38
fintech.go Normal file
View File

@ -0,0 +1,38 @@
package main
import (
"errors"
"fmt"
)
type Bitcoin int
type Stringer interface {
String() string
}
type Wallet struct {
balance Bitcoin
}
var ErrInsufficientFunds = errors.New("cannot withdraw, insufficient funds")
func (w *Wallet) Deposit(amount Bitcoin) {
w.balance += amount
}
func (w *Wallet) Withdraw(amount Bitcoin) error {
if amount > w.balance {
return ErrInsufficientFunds
}
w.balance -= amount
return nil
}
func (w *Wallet) Balance() Bitcoin {
return w.balance
}
func (b Bitcoin) String() string {
return fmt.Sprintf("%d BTC", b)
}

53
fintech_test.go Normal file
View File

@ -0,0 +1,53 @@
package main
import "testing"
func TestWallet(t *testing.T) {
assertBalance := func(t testing.TB, wallet Wallet, want Bitcoin) {
t.Helper()
got := wallet.Balance()
if got != want {
t.Errorf("got %s want %s", got, want)
}
}
assertError := func(t testing.TB, got error, want error) {
t.Helper()
if got == nil {
t.Fatal("wanted an error but didn't get one")
}
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
assertNoError := func(t testing.TB, got error) {
t.Helper()
if got != nil {
t.Fatal("got an error but didn't want one")
}
}
t.Run("Deposit", func(t *testing.T) {
wallet := Wallet{}
wallet.Deposit(Bitcoin(10))
assertBalance(t, wallet, Bitcoin(10))
})
t.Run("Withdraw", func(t *testing.T) {
wallet := Wallet{balance: Bitcoin(20)}
err := wallet.Withdraw(Bitcoin(10))
assertNoError(t, err)
assertBalance(t, wallet, Bitcoin(10))
})
t.Run("Withdraw error", func(t *testing.T) {
startingBalance := Bitcoin(20)
wallet := Wallet{balance: startingBalance}
err := wallet.Withdraw(Bitcoin(30))
assertError(t, err, ErrInsufficientFunds)
assertBalance(t, wallet, startingBalance)
})
}

3
go.mod Normal file
View File

@ -0,0 +1,3 @@
module gotdd
go 1.17

34
hello.go Normal file
View File

@ -0,0 +1,34 @@
package main
import "fmt"
const spanish = "Spanish"
const french = "French"
const englishHelloPrefix = "Hello, "
const spanishHelloPrefix = "Hola, "
const frenchHelloPrefix = "Bonjour, "
func main() {
fmt.Println(Hello("world", ""))
}
func Hello(name string, language string) string {
if name == "" {
name = "World"
}
prefix := greetingPrefix(language)
return prefix + name
}
func greetingPrefix(language string) (prefix string) {
switch language {
case french:
prefix = frenchHelloPrefix
case spanish:
prefix = spanishHelloPrefix
default:
prefix = englishHelloPrefix
}
return
}

35
hello_test.go Normal file
View File

@ -0,0 +1,35 @@
package main
import "testing"
func TestHello(t *testing.T) {
assertCorrectMessage := func(t testing.TB, got, want string) {
t.Helper()
if got != want {
t.Errorf("got %q want %q", got, want)
}
}
t.Run("saying hello to people", func(t *testing.T) {
got := Hello("Chris", "")
want := "Hello, Chris"
assertCorrectMessage(t, got, want)
})
t.Run("say 'Hello, World' when an empty string is supplied", func(t *testing.T) {
got := Hello("", "")
want := "Hello, World"
assertCorrectMessage(t, got, want)
})
t.Run("In spanish", func(t *testing.T) {
got := Hello("Elodie", "Spanish")
want := "Hola, Elodie"
assertCorrectMessage(t, got, want)
})
t.Run("In French", func(t *testing.T) {
got := Hello("Frankie", "French")
want := "Bonjour, Frankie"
assertCorrectMessage(t, got, want)
})
}

45
mocking.go Normal file
View File

@ -0,0 +1,45 @@
package main
import (
"fmt"
"io"
"os"
"time"
)
const finalWord = "Go!"
const countdownStart = 3
type Sleeper interface {
Sleep()
}
type DefaultSleeper struct {
}
type ConfigurableSleeper struct {
duration time.Duration
sleep func(duration time.Duration)
}
func Countdown_main() {
sleeper := &ConfigurableSleeper{1 * time.Second, time.Sleep}
Countdown(os.Stdout, sleeper)
}
func (d *DefaultSleeper) Sleep() {
time.Sleep(1 * time.Second)
}
func Countdown(out io.Writer, sleeper Sleeper) {
for i := countdownStart; i > 0; i-- {
sleeper.Sleep()
fmt.Fprintln(out, i)
}
sleeper.Sleep()
fmt.Fprint(out, finalWord)
}
func (c *ConfigurableSleeper) Sleep() {
c.sleep(c.duration)
}

95
mocking_test.go Normal file
View File

@ -0,0 +1,95 @@
package main
import (
"bytes"
"reflect"
"testing"
"time"
)
type SpySleeper struct {
Calls int
}
func (s *SpySleeper) Sleep() {
s.Calls++
}
type SpyTime struct {
durationSlept time.Duration
}
func (s *SpyTime) Sleep(duration time.Duration) {
s.durationSlept = duration
}
type SpyCountdownOperations struct {
Calls []string
}
func (s *SpyCountdownOperations) Sleep() {
s.Calls = append(s.Calls, sleep)
}
func (s *SpyCountdownOperations) Write(p []byte) (n int, err error) {
s.Calls = append(s.Calls, write)
return
}
const write = "write"
const sleep = "sleep"
func TestCountdown(t *testing.T) {
t.Run("default countdown test", func(t *testing.T) {
buffer := &bytes.Buffer{}
spySleeper := &SpySleeper{}
Countdown(buffer, spySleeper)
got := buffer.String()
want := `3
2
1
Go!`
if got != want {
t.Errorf("got %q want %q", got, want)
}
if spySleeper.Calls != 4 {
t.Errorf("not enough calls to sleeper, want 4 got %d", spySleeper.Calls)
}
})
t.Run("sleep before every print", func(t *testing.T) {
spySleepPrinter := &SpyCountdownOperations{}
Countdown(spySleepPrinter, spySleepPrinter)
want := []string{
sleep,
write,
sleep,
write,
sleep,
write,
sleep,
write,
}
if !reflect.DeepEqual(want, spySleepPrinter.Calls) {
t.Errorf("wanted calls %v got %v", want, spySleepPrinter.Calls)
}
})
}
func TestConfigurableSleeper(t *testing.T) {
sleepTime := 5 * time.Second
spyTime := &SpyTime{}
sleeper := ConfigurableSleeper{sleepTime, spyTime.Sleep}
sleeper.Sleep()
if spyTime.durationSlept != sleepTime {
t.Errorf("should have slept for %v but slept for %v", sleepTime, spyTime.durationSlept)
}
}

57
reflection.go Normal file
View File

@ -0,0 +1,57 @@
package main
import "reflect"
type Person struct {
Name string
Profile Profile
}
type Profile struct {
Age int
City string
}
func walk(x interface{}, fn func(input string)) {
val := getValue(x)
walkValue := func(value reflect.Value) {
walk(value.Interface(), fn)
}
switch val.Kind() {
case reflect.String:
fn(val.String())
case reflect.Struct:
for i := 0; i < val.NumField(); i++ {
walkValue(val.Field(i))
}
case reflect.Slice, reflect.Array:
for i := 0; i < val.Len(); i++ {
walkValue(val.Index(i))
}
case reflect.Map:
for _, key := range val.MapKeys() {
walkValue(val.MapIndex(key))
}
case reflect.Chan:
for v, ok := val.Recv(); ok; v, ok = val.Recv() {
walkValue(v)
}
case reflect.Func:
valFnResult := val.Call(nil)
for _, res := range valFnResult {
walkValue(res)
}
}
}
func getValue(x interface{}) reflect.Value {
val := reflect.ValueOf(x)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
return val
}

148
reflection_test.go Normal file
View File

@ -0,0 +1,148 @@
package main
import (
"reflect"
"testing"
)
func TestWalk(t *testing.T) {
cases := []struct {
Name string
Input interface{}
ExpectedCalls []string
}{
{
"Struct with one string field",
struct {
Name string
}{"Chris"},
[]string{"Chris"},
},
{
"Struct with two string field",
struct {
Name string
City string
}{"Chris", "London"},
[]string{"Chris", "London"},
},
{
"Struct with non string fields",
struct {
Name string
City int
}{"Chris", 33},
[]string{"Chris"},
},
{
"Nested fields",
Person{
"Chris",
Profile{33, "London"},
},
[]string{"Chris", "London"},
},
{
"Pointers to things",
&Person{
"Chris",
Profile{33, "London"},
},
[]string{"Chris", "London"},
},
{
"Slices",
[]Profile{
{33, "London"},
{34, "Reykjavik"},
},
[]string{"London", "Reykjavik"},
},
{
"Arrays",
[2]Profile{
{33, "London"},
{34, "Reykjavik"},
},
[]string{"London", "Reykjavik"},
},
}
for _, test := range cases {
t.Run(test.Name, func(t *testing.T) {
var got []string
walk(test.Input, func(input string) {
got = append(got, input)
})
if !reflect.DeepEqual(got, test.ExpectedCalls) {
t.Errorf("got %v, want %v", got, test.ExpectedCalls)
}
})
}
t.Run("with maps", func(t *testing.T) {
aMap := map[string]string{
"Foo": "Bar",
"Baz": "Boz",
}
var got []string
walk(aMap, func(input string) {
got = append(got, input)
})
assertContains(t, got, "Bar")
assertContains(t, got, "Boz")
})
t.Run("with channels", func(t *testing.T) {
aChannel := make(chan Profile)
go func() {
aChannel <- Profile{33, "Berlin"}
aChannel <- Profile{34, "Katowice"}
close(aChannel)
}()
var got []string
want := []string{"Berlin", "Katowice"}
walk(aChannel, func(input string) {
got = append(got, input)
})
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
})
t.Run("with function", func(t *testing.T) {
aFunction := func() (Profile, Profile) {
return Profile{33, "Berlin"}, Profile{34, "Katowice"}
}
var got []string
want := []string{"Berlin", "Katowice"}
walk(aFunction, func(input string) {
got = append(got, input)
})
if !reflect.DeepEqual(got, want) {
t.Errorf("got %v, want %v", got, want)
}
})
}
func assertContains(t testing.TB, haystack []string, needle string) {
t.Helper()
contains := false
for _, x := range haystack {
if x == needle {
contains = true
}
}
if !contains {
t.Errorf("expected %+v to contain %q but it didn't", haystack, needle)
}
}

9
repeat.go Normal file
View File

@ -0,0 +1,9 @@
package main
func Repeat(character string) string {
var repeated string
for i := 0; i < 5; i++ {
repeated += character
}
return repeated
}

18
repeat_test.go Normal file
View File

@ -0,0 +1,18 @@
package main
import "testing"
func TestRepeat(t *testing.T) {
repeated := Repeat("a")
expected := "aaaaa"
if repeated != expected {
t.Errorf("Expected %q but got %q", expected, repeated)
}
}
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
}

90
roman_numerals.go Normal file
View File

@ -0,0 +1,90 @@
package main
import "strings"
// ConvertToArabic converts a Roman Numeral to an Arabic number.
func ConvertToArabic(roman string) (total uint16) {
for _, symbols := range windowedRoman(roman).Symbols() {
total += allRomanNumerals.ValueOf(symbols...)
}
return
}
// ConvertToRoman converts an Arabic number to a Roman Numeral.
func ConvertToRoman(arabic uint16) string {
var result strings.Builder
for _, numeral := range allRomanNumerals {
for arabic >= numeral.Value {
result.WriteString(numeral.Symbol)
arabic -= numeral.Value
}
}
return result.String()
}
type romanNumeral struct {
Value uint16
Symbol string
}
type romanNumerals []romanNumeral
func (r romanNumerals) ValueOf(symbols ...byte) uint16 {
symbol := string(symbols)
for _, s := range r {
if s.Symbol == symbol {
return s.Value
}
}
return 0
}
func (r romanNumerals) Exists(symbols ...byte) bool {
symbol := string(symbols)
for _, s := range r {
if s.Symbol == symbol {
return true
}
}
return false
}
var allRomanNumerals = romanNumerals{
{1000, "M"},
{900, "CM"},
{500, "D"},
{400, "CD"},
{100, "C"},
{90, "XC"},
{50, "L"},
{40, "XL"},
{10, "X"},
{9, "IX"},
{5, "V"},
{4, "IV"},
{1, "I"},
}
type windowedRoman string
func (w windowedRoman) Symbols() (symbols [][]byte) {
for i := 0; i < len(w); i++ {
symbol := w[i]
notAtEnd := i+1 < len(w)
if notAtEnd && isSubtractive(symbol) && allRomanNumerals.Exists(symbol, w[i+1]) {
symbols = append(symbols, []byte{symbol, w[i+1]})
i++
} else {
symbols = append(symbols, []byte{symbol})
}
}
return
}
func isSubtractive(symbol uint8) bool {
return symbol == 'I' || symbol == 'X' || symbol == 'C'
}

84
roman_numerals_test.go Normal file
View File

@ -0,0 +1,84 @@
package main
import (
"fmt"
"testing"
"testing/quick"
)
var (
cases = []struct {
Arabic uint16
Roman string
}{
{Arabic: 1, Roman: "I"},
{Arabic: 2, Roman: "II"},
{Arabic: 3, Roman: "III"},
{Arabic: 4, Roman: "IV"},
{Arabic: 5, Roman: "V"},
{Arabic: 6, Roman: "VI"},
{Arabic: 7, Roman: "VII"},
{Arabic: 8, Roman: "VIII"},
{Arabic: 9, Roman: "IX"},
{Arabic: 10, Roman: "X"},
{Arabic: 14, Roman: "XIV"},
{Arabic: 18, Roman: "XVIII"},
{Arabic: 20, Roman: "XX"},
{Arabic: 39, Roman: "XXXIX"},
{Arabic: 40, Roman: "XL"},
{Arabic: 47, Roman: "XLVII"},
{Arabic: 49, Roman: "XLIX"},
{Arabic: 50, Roman: "L"},
{Arabic: 100, Roman: "C"},
{Arabic: 90, Roman: "XC"},
{Arabic: 400, Roman: "CD"},
{Arabic: 500, Roman: "D"},
{Arabic: 900, Roman: "CM"},
{Arabic: 1000, Roman: "M"},
{Arabic: 1984, Roman: "MCMLXXXIV"},
{Arabic: 3999, Roman: "MMMCMXCIX"},
{Arabic: 2014, Roman: "MMXIV"},
{Arabic: 1006, Roman: "MVI"},
{Arabic: 798, Roman: "DCCXCVIII"},
}
)
func TestConvertingToRomanNumerals(t *testing.T) {
for _, test := range cases {
t.Run(fmt.Sprintf("%d gets converted to '%s", test.Arabic, test.Roman), func(t *testing.T) {
got := ConvertToRoman(test.Arabic)
if got != test.Roman {
t.Errorf("got %q, want %q", got, test.Roman)
}
})
}
}
func TestConvertingToArabic(t *testing.T) {
for _, test := range cases {
t.Run(fmt.Sprintf("%q gets converted to %d", test.Roman, test.Arabic), func(t *testing.T) {
got := ConvertToArabic(test.Roman)
if got != test.Arabic {
t.Errorf("got %d, want %d", got, test.Arabic)
}
})
}
}
func TestPropertiesOfConversion(t *testing.T) {
assertion := func(arabic uint16) bool {
if arabic > 3999 {
return true
}
t.Log("testing", arabic)
roman := ConvertToRoman(arabic)
fromRoman := ConvertToArabic(roman)
return fromRoman == arabic
}
if err := quick.Check(assertion, &quick.Config{
MaxCount: 1000,
}); err != nil {
t.Error("failed checks", err)
}
}

39
select.go Normal file
View File

@ -0,0 +1,39 @@
package main
import (
"fmt"
"net/http"
"time"
)
var tenSecondTimeout = 10 * time.Second
func Racer(a, b string) (winner string, err error) {
return ConfigurableRacer(a, b, tenSecondTimeout)
}
func ConfigurableRacer(a, b string, timeout time.Duration) (winner string, err error) {
select {
case <-ping(a):
return a, nil
case <-ping(b):
return b, nil
case <-time.After(timeout):
return "", fmt.Errorf("timed out waiting for %s and %s", a, b)
}
}
func ping(url string) chan struct{} {
ch := make(chan struct{})
go func() {
http.Get(url)
close(ch)
}()
return ch
}
func measureResponseTime(url string) time.Duration {
start := time.Now()
http.Get(url)
return time.Since(start)
}

47
select_test.go Normal file
View File

@ -0,0 +1,47 @@
package main
import (
"net/http"
"net/http/httptest"
"testing"
"time"
)
func TestRacer(t *testing.T) {
t.Run("Test that fast server returns", func(t *testing.T) {
slowServer := makeDelayedServer(time.Millisecond * 20)
fastServer := makeDelayedServer(time.Millisecond * 0)
defer slowServer.Close()
defer fastServer.Close()
slowURL := slowServer.URL
fastURL := fastServer.URL
want := fastURL
got, _ := Racer(slowURL, fastURL)
if got != want {
t.Errorf("got %q, want %q", got, want)
}
})
t.Run("returns an error if a server doesn't respond within 10s", func(t *testing.T) {
server := makeDelayedServer(25 * time.Millisecond)
defer server.Close()
_, err := ConfigurableRacer(server.URL, server.URL, 20*time.Millisecond)
if err == nil {
t.Error("expected an error, but didn't get one")
}
})
}
func makeDelayedServer(delay time.Duration) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(delay)
w.WriteHeader(http.StatusOK)
}))
}

37
shapes.go Normal file
View File

@ -0,0 +1,37 @@
package main
import "math"
type Shape interface {
Area() float64
}
type Rectangle struct {
Width float64
Height float64
}
type Circle struct {
Radius float64
}
type Triangle struct {
Base float64
Height float64
}
func Perimeter(rectangle Rectangle) float64 {
return 2 * (rectangle.Width + rectangle.Height)
}
func (r Rectangle) Area() float64 {
return r.Height * r.Width
}
func (c Circle) Area() float64 {
return math.Pi * c.Radius * c.Radius
}
func (t Triangle) Area() float64 {
return 0.5 * t.Base * t.Height
}

33
shapes_test.go Normal file
View File

@ -0,0 +1,33 @@
package main
import "testing"
func TestPerimeter(t *testing.T) {
rectangle := Rectangle{10.0, 10.0}
got := Perimeter(rectangle)
want := 40.0
if got != want {
t.Errorf("got %.2f want %.2f", got, want)
}
}
func TestArea(t *testing.T) {
areaTests := []struct {
name string
shape Shape
hasArea float64
}{
{name: "Rectangle", shape: Rectangle{12, 6}, hasArea: 72.0},
{name: "Circle", shape: Circle{10}, hasArea: 314.1592653589793},
{name: "Triangle", shape: Triangle{12, 6}, hasArea: 36},
}
for _, tt := range areaTests {
got := tt.shape.Area()
if got != tt.hasArea {
t.Errorf("got %g want %g", got, tt.hasArea)
}
}
}

22
sync.go Normal file
View File

@ -0,0 +1,22 @@
package main
import "sync"
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Value() int {
return c.value
}
func NewCounter() *Counter {
return &Counter{}
}

42
sync_test.go Normal file
View File

@ -0,0 +1,42 @@
package main
import (
"sync"
"testing"
)
func TestCounter(t *testing.T) {
t.Run("incrementing the counter 3 times leaves it at 3", func(t *testing.T) {
counter := NewCounter()
counter.Inc()
counter.Inc()
counter.Inc()
assertCounter(t, counter, 3)
})
t.Run("it runs safely concurrently", func(t *testing.T) {
wantedCount := 1000
counter := NewCounter()
var wg sync.WaitGroup
wg.Add(wantedCount)
for i := 0; i < wantedCount; i++ {
go func() {
counter.Inc()
wg.Done()
}()
}
wg.Wait()
assertCounter(t, counter, wantedCount)
})
}
func assertCounter(t testing.TB, got *Counter, want int) {
t.Helper()
if got.Value() != want {
t.Errorf("got %d, want %d", got.Value(), want)
}
}