First Commit
This commit is contained in:
commit
828f289f3c
105
.gitignore
vendored
Normal file
105
.gitignore
vendored
Normal 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
33
adder.go
Normal 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
65
adder_test.go
Normal 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
34
clockface.go
Normal 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
1
clockface/clockface.go
Normal file
@ -0,0 +1 @@
|
||||
package clockface
|
34
clockface/clockface/main.go
Normal file
34
clockface/clockface/main.go
Normal 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}
|
||||
}
|
28
clockface/clockface_acceptance_test.go
Normal file
28
clockface/clockface_acceptance_test.go
Normal 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)
|
||||
}
|
||||
}
|
65
clockface/clockface_test.go
Normal file
65
clockface/clockface_test.go
Normal 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
87
clockface_test.go
Normal 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
25
concurrency.go
Normal 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
50
concurrency_test.go
Normal 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
22
context.go
Normal 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
95
context_test.go
Normal 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
14
di.go
Normal 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
18
di_test.go
Normal 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
53
dictionary.go
Normal 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
105
dictionary_test.go
Normal 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
38
fintech.go
Normal 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
53
fintech_test.go
Normal 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)
|
||||
})
|
||||
}
|
34
hello.go
Normal file
34
hello.go
Normal 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
35
hello_test.go
Normal 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
45
mocking.go
Normal 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
95
mocking_test.go
Normal 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
57
reflection.go
Normal 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
148
reflection_test.go
Normal 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
9
repeat.go
Normal 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
18
repeat_test.go
Normal 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
90
roman_numerals.go
Normal 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
84
roman_numerals_test.go
Normal 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
39
select.go
Normal 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
47
select_test.go
Normal 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
37
shapes.go
Normal 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
33
shapes_test.go
Normal 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
22
sync.go
Normal 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
42
sync_test.go
Normal 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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user