From f42a41fa1c55737ea417adbb762a3d3f018c379b Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sat, 20 Jul 2024 18:46:18 -0400 Subject: [PATCH 01/13] Finally making progress --- go-sql-database/README.md | 0 go-sql-database/try-sqlite/README.md | 23 ++++ go-sql-database/try-sqlite/data/trysqlite.db | Bin 0 -> 20480 bytes .../try-sqlite/data/trysqlite.db-shm | Bin 0 -> 32768 bytes .../try-sqlite/data/trysqlite.db-wal | 0 go-sql-database/try-sqlite/go.mod | 5 + go-sql-database/try-sqlite/go.sum | 2 + go-sql-database/try-sqlite/main.go | 114 ++++++++++++++++++ .../try-sqlite/migrations/10_todos.down.sql | 0 .../try-sqlite/migrations/10_todos.up.sql | 2 + .../20_alter_todos_completed.down.sql | 1 + .../20_alter_todos_completed.up.sql | 1 + go-web-app/gowiki/go.mod | 7 ++ go-web-app/gowiki/go.sum | 8 ++ 14 files changed, 163 insertions(+) create mode 100644 go-sql-database/README.md create mode 100644 go-sql-database/try-sqlite/README.md create mode 100644 go-sql-database/try-sqlite/data/trysqlite.db create mode 100644 go-sql-database/try-sqlite/data/trysqlite.db-shm create mode 100644 go-sql-database/try-sqlite/data/trysqlite.db-wal create mode 100644 go-sql-database/try-sqlite/go.mod create mode 100644 go-sql-database/try-sqlite/go.sum create mode 100644 go-sql-database/try-sqlite/main.go create mode 100644 go-sql-database/try-sqlite/migrations/10_todos.down.sql create mode 100644 go-sql-database/try-sqlite/migrations/10_todos.up.sql create mode 100644 go-sql-database/try-sqlite/migrations/20_alter_todos_completed.down.sql create mode 100644 go-sql-database/try-sqlite/migrations/20_alter_todos_completed.up.sql create mode 100644 go-web-app/gowiki/go.sum diff --git a/go-sql-database/README.md b/go-sql-database/README.md new file mode 100644 index 0000000..e69de29 diff --git a/go-sql-database/try-sqlite/README.md b/go-sql-database/try-sqlite/README.md new file mode 100644 index 0000000..ebed68b --- /dev/null +++ b/go-sql-database/try-sqlite/README.md @@ -0,0 +1,23 @@ +# Regular SQLite in Go + +## Install the Migrations Tool + +This will in stall the migrate tool for our system. + +``` +go install -tags 'sqlite3' github.com/golang-migrate/migrate/v4/cmd/migrate@latest +``` + +You could also use golang-migrate as a library and migrate your DB in code. For that you would need to install that package as part of your module dependencies. + +``` +go get github.com/mattn/go-sqlite3 +``` + +## Running Migrations + +### Using the CLI + +``` +migrate -database sqlite3://./data/trysqlite.db -path ./migrations +``` \ No newline at end of file diff --git a/go-sql-database/try-sqlite/data/trysqlite.db b/go-sql-database/try-sqlite/data/trysqlite.db new file mode 100644 index 0000000000000000000000000000000000000000..51730b81f1e1b6a42c3507598b1eaea8dae00fc6 GIT binary patch literal 20480 zcmeI)O>f#T7zc10(m_|z>`*D!N0qh_m97-nru8&h(h8Ng(jXOS5(>m+*$AN|nYP`! zeTIFPeujOZ9=6NOTP5gJX{QPFw_!kl1|3l*Beuf2hwA+nbLc7>`R3fB*y_009U<00Izz z00jP{z_p0RG)?2*^}uTPMBv$;KmJgT>PFr)$jl#>3>tS)(y^&nF^vyQ;X|ppFRv_$_+=~_D+-9+CP{BCPH%cZQ zHR^R4-5PbCoB47rMd85ym*E}H9mouXO^4NL>2BgntLF>7raaPic6f7~rGM3v+rpMF zu0ql8h=tR&xy%t4`k*pFfGA?|A)I*moq4 z-+3=wtK~Y~p%ut(KRVTERElQ}Lp}}#}6(j6Yt_T=sOWGxnNa4#}4 zzaER@?9Wn~k43w(Zft>3j?aCmwZ0KxJ##^@2<~liN+d z-ravgne;&}bMP|zm#)eF|CXt@%PxZ74*>{300Izz00bZa0SG_<0uX?}yg-~swe|5o GR{Q~!8xjKm literal 0 HcmV?d00001 diff --git a/go-sql-database/try-sqlite/data/trysqlite.db-shm b/go-sql-database/try-sqlite/data/trysqlite.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10 GIT binary patch literal 32768 zcmeIuAr62r3 Date: Sat, 20 Jul 2024 18:47:27 -0400 Subject: [PATCH 02/13] Down grade --- go-sql-database/try-sqlite/migrations/10_todos.down.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/go-sql-database/try-sqlite/migrations/10_todos.down.sql b/go-sql-database/try-sqlite/migrations/10_todos.down.sql index e69de29..4649281 100644 --- a/go-sql-database/try-sqlite/migrations/10_todos.down.sql +++ b/go-sql-database/try-sqlite/migrations/10_todos.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS todos; \ No newline at end of file -- 2.38.4 From 5425bc78bf1597be3ed7f5c0131953ef36e00995 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sat, 20 Jul 2024 21:38:14 -0400 Subject: [PATCH 03/13] More useful notes --- .../try-sqlite/data/trysqlite.db-shm | Bin 32768 -> 0 bytes .../try-sqlite/data/trysqlite.db-wal | 0 go-sql-database/try-sqlite/main.go | 21 +++++++++++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) delete mode 100644 go-sql-database/try-sqlite/data/trysqlite.db-shm delete mode 100644 go-sql-database/try-sqlite/data/trysqlite.db-wal diff --git a/go-sql-database/try-sqlite/data/trysqlite.db-shm b/go-sql-database/try-sqlite/data/trysqlite.db-shm deleted file mode 100644 index fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeIuAr62r3 Date: Sun, 21 Jul 2024 18:55:06 -0400 Subject: [PATCH 04/13] Saving the work --- go-sql-database/try-sqlite/data/trysqlite.db | Bin 20480 -> 20480 bytes .../try-sqlite/data/trysqlite.db-shm | Bin 0 -> 32768 bytes .../try-sqlite/data/trysqlite.db-wal | Bin 0 -> 61832 bytes go-sql-database/try-sqlite/main.go | 146 +++++++++++++++++- 4 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 go-sql-database/try-sqlite/data/trysqlite.db-shm create mode 100644 go-sql-database/try-sqlite/data/trysqlite.db-wal diff --git a/go-sql-database/try-sqlite/data/trysqlite.db b/go-sql-database/try-sqlite/data/trysqlite.db index 51730b81f1e1b6a42c3507598b1eaea8dae00fc6..d1cb1d4707c98bbeca85bc90527d5c05b9dec555 100644 GIT binary patch delta 696 zcmZozz}T>WaRZkDAAbUG00M3n6tLi}5945GQ1&!7<|ru2PcKT$&CSe9&q+-z%2PTnS4c@LNlZ#CPE{x@O)bhyEmkx#Ff!3KFxNFQR4_EOGBvj{Fu)?jz`!Ixgt_&& z4K}kfF-J2PB*ehLxPu6TahhvlW@TUkH@6<_39uN@-~>tyHi8=rH`fSgE(cC?>xl?r z6Jsk=OEiN)LJSNH8*my-q`CEIK@76XJdBHyG+}OKXl!MK;$Wzd8zVb|syZ;Od1t1i mWaRZmZW}ju;QzK+P~jCn4+8@SBeOVXNq$OxF#sl(4ATGr diff --git a/go-sql-database/try-sqlite/data/trysqlite.db-shm b/go-sql-database/try-sqlite/data/trysqlite.db-shm new file mode 100644 index 0000000000000000000000000000000000000000..cbb88628a887a036bc4640d2aa242cba2f466be1 GIT binary patch literal 32768 zcmeI)yNQB95CGuO^ZiZ^Y(lOB`>+5b16we#15*tJlglqsxbT_~Y{Z*iL`=;Tzky|j zWtnBZ0%mv`MG>t$rHDa4b2U%pxcK_nt^N+<{dE01*{;54pY!FWF?{fSwz@>o_k0)E z>szX1b@?~*qxG$cf&c*m1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!CuL3Y6j__iWXo z9?jhS)roEt8@dF6cMvG&uDM3EqxTNOnvnnj0t5&UAV7cs0RjXF5FkK+009C72oNAZ gfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5O{Hce>1NquK)l5 literal 0 HcmV?d00001 diff --git a/go-sql-database/try-sqlite/data/trysqlite.db-wal b/go-sql-database/try-sqlite/data/trysqlite.db-wal new file mode 100644 index 0000000000000000000000000000000000000000..1da2d5de98a2917bbac88cbf93e9fb85d184fb5c GIT binary patch literal 61832 zcmeI*Pe>GD7{~EhGymkD5yKLKn8=8N#o5_e2M01VDEdn$D=OGYmu*R1TUWYBMOcxC zIwVL`r%;RtJk_3xga|rx5W)_HU{C~k2ueD1=$%ztNA0r13OjyRJ^122%lnz}g?)H_ ztZ}92n&h9Zm82@ke!OgY5qU6vYSDi{Kn(kK(zZOse0W)Nns=C6Gc$Ze?s&0d+8%yfiOOoVE zxv8r+n5yoqn_d!i!*1(p%j;^QZi%b9K{M32q^`XruIQNCx=Va@QP*fKb8|l!rYI70 z)SX}2?<=gIe;aGcnTyn7clLq+0tg_000IagfB*ukBEYu!-F@ZjOZmJmPy0R#|0009ILKmY**5U>R@If8QWnUciwf@CAlSt=XIDd4U2s%;yy5{KVh|5I_I{1Q0*~0R#|0z-t2Z z1?USn`T~{ias;79GoWg!ru$VRUDq(vOS#Gs{2q8bGBsQMo*aSKPA2w)00IagfB*sr zAbpO5y)bfjvPV#)v3a_ lcSlp?2t0iK*%|@}Ab Date: Wed, 21 Aug 2024 10:15:39 -0400 Subject: [PATCH 05/13] Save all the things --- go-sql-database/README.md | 6 ++++ go-sql-database/try-sqlite/data/trysqlite.db | Bin 20480 -> 20480 bytes .../try-sqlite/data/trysqlite.db-shm | Bin 32768 -> 32768 bytes .../try-sqlite/data/trysqlite.db-wal | Bin 61832 -> 0 bytes go-sql-database/try-sqlite/main.go | 3 ++ go-web-app/README.md | 12 +++++++ .../concurrency/CheckWebsites.go | 13 ++++++++ .../concurrency/CheckWebsites_test.go | 30 ++++++++++++++++++ learn_go_with_tests/concurrency/go.mod | 3 ++ 9 files changed, 67 insertions(+) create mode 100644 learn_go_with_tests/concurrency/CheckWebsites.go create mode 100644 learn_go_with_tests/concurrency/CheckWebsites_test.go create mode 100644 learn_go_with_tests/concurrency/go.mod diff --git a/go-sql-database/README.md b/go-sql-database/README.md index e69de29..297cebc 100644 --- a/go-sql-database/README.md +++ b/go-sql-database/README.md @@ -0,0 +1,6 @@ +# Learning SQL DB Access + + +Resources: + +http://go-database-sql.org/index.html It's the missing guide to the docs "how" to use the database/sql package. \ No newline at end of file diff --git a/go-sql-database/try-sqlite/data/trysqlite.db b/go-sql-database/try-sqlite/data/trysqlite.db index d1cb1d4707c98bbeca85bc90527d5c05b9dec555..8c051354e93b1d1d0cf067a4a9e67aae17dba510 100644 GIT binary patch delta 51 zcmZozz}T>WaYLd#Ge0}SWaYLd#GZ#C<GD7{~EhGymkD5yKLKn8=8N#o5_e2M01VDEdn$D=OGYmu*R1TUWYBMOcxC zIwVL`r%;RtJk_3xga|rx5W)_HU{C~k2ueD1=$%ztNA0r13OjyRJ^122%lnz}g?)H_ ztZ}92n&h9Zm82@ke!OgY5qU6vYSDi{Kn(kK(zZOse0W)Nns=C6Gc$Ze?s&0d+8%yfiOOoVE zxv8r+n5yoqn_d!i!*1(p%j;^QZi%b9K{M32q^`XruIQNCx=Va@QP*fKb8|l!rYI70 z)SX}2?<=gIe;aGcnTyn7clLq+0tg_000IagfB*ukBEYu!-F@ZjOZmJmPy0R#|0009ILKmY**5U>R@If8QWnUciwf@CAlSt=XIDd4U2s%;yy5{KVh|5I_I{1Q0*~0R#|0z-t2Z z1?USn`T~{ias;79GoWg!ru$VRUDq(vOS#Gs{2q8bGBsQMo*aSKPA2w)00IagfB*sr zAbpO5y)bfjvPV#)v3a_ lcSlp?2t0iK*%|@}Ab Date: Wed, 21 Aug 2024 11:19:06 -0400 Subject: [PATCH 06/13] Finished up concurrency --- .../concurrency/CheckWebsites.go | 23 ++++++++++++++++++- .../concurrency/CheckWebsites_test.go | 18 +++++++++++++++ learn_go_with_tests/concurrency/READMD.md | 9 ++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 learn_go_with_tests/concurrency/READMD.md diff --git a/learn_go_with_tests/concurrency/CheckWebsites.go b/learn_go_with_tests/concurrency/CheckWebsites.go index 9438de0..1fbbfda 100644 --- a/learn_go_with_tests/concurrency/CheckWebsites.go +++ b/learn_go_with_tests/concurrency/CheckWebsites.go @@ -1,12 +1,33 @@ package concurrency 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 { - results[url] = wc(url) + // anonymous function call required because go routines must call + // a function to start. An anonymous function maintains access to + // the lexical scope in which they are defined - all the variables + // that are available at the point when you declare the anonymous + //function are also available in the body of the function. + // url := url // create a new variable to avoid capturing the loop variable. + // go func() { + // results[url] = wc(url) + // }() + go func(u string) { + resultChannel <- result{u, wc(u)} // send statement + }(url) // or just pass by value to give the func it's own url + } + + for i := 0; i < len(urls); i++ { + r := <-resultChannel // receive statement + results[r.string] = r.bool } return results diff --git a/learn_go_with_tests/concurrency/CheckWebsites_test.go b/learn_go_with_tests/concurrency/CheckWebsites_test.go index 9a4ca69..be58d15 100644 --- a/learn_go_with_tests/concurrency/CheckWebsites_test.go +++ b/learn_go_with_tests/concurrency/CheckWebsites_test.go @@ -3,12 +3,30 @@ package concurrency import ( "reflect" "testing" + "time" ) func mockWebsiteChecker(url string) bool { return url != "waat://furhurterwe.geds" } +func slowStubWebsiteChecker(_ string) bool { + time.Sleep(20 * time.Millisecond) + return true +} + +// What does testing.B do? +func BenchmarkCheckWebsites(b *testing.B) { + urls := make([]string, 100) + for i := 0; i < len(urls); i++ { + urls[i] = "a url" + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + CheckWebsites(slowStubWebsiteChecker, urls) + } +} + func TestWebsites(t *testing.T) { websites := []string{ "http://google.com", diff --git a/learn_go_with_tests/concurrency/READMD.md b/learn_go_with_tests/concurrency/READMD.md new file mode 100644 index 0000000..5efa9ec --- /dev/null +++ b/learn_go_with_tests/concurrency/READMD.md @@ -0,0 +1,9 @@ +# Concurrency + +https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/concurrency + +This was an interesting chapter. The high-level takeaways: + +- An anonymous function maintains access to the lexical scope in which they are defined +- Go can help identify race conditions with [race detector](https://blog.golang.org/race-detector) `go test -race` +- Coordinating go routines can be accomplished with channels. Channels are a data structure that can both receive and send values. This allows cross go routine communication. Channels have a type, and you will commonly see structs passed around. \ No newline at end of file -- 2.38.4 From 215018850114cf4034e52dc6c7a2dedd03e48126 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Wed, 21 Aug 2024 17:21:08 -0400 Subject: [PATCH 07/13] racer --- learn_go_with_tests/my_select/go.mod | 3 ++ learn_go_with_tests/my_select/racer.go | 23 ++++++++++++++ learn_go_with_tests/my_select/racer_test.go | 34 +++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 learn_go_with_tests/my_select/go.mod create mode 100644 learn_go_with_tests/my_select/racer.go create mode 100644 learn_go_with_tests/my_select/racer_test.go diff --git a/learn_go_with_tests/my_select/go.mod b/learn_go_with_tests/my_select/go.mod new file mode 100644 index 0000000..696d9bc --- /dev/null +++ b/learn_go_with_tests/my_select/go.mod @@ -0,0 +1,3 @@ +module my_select + +go 1.22.5 diff --git a/learn_go_with_tests/my_select/racer.go b/learn_go_with_tests/my_select/racer.go new file mode 100644 index 0000000..43b89d8 --- /dev/null +++ b/learn_go_with_tests/my_select/racer.go @@ -0,0 +1,23 @@ +package my_select + +import ( + "net/http" + "time" +) + +func Racer(a string, b string) (winner string) { + aDuration := measureResponseTime(a) + bDuration := measureResponseTime(b) + + if aDuration < bDuration { + return a + } + + return b +} + +func measureResponseTime(url string) time.Duration { + start := time.Now() + http.Get(url) + return time.Since(start) +} diff --git a/learn_go_with_tests/my_select/racer_test.go b/learn_go_with_tests/my_select/racer_test.go new file mode 100644 index 0000000..d5d27a3 --- /dev/null +++ b/learn_go_with_tests/my_select/racer_test.go @@ -0,0 +1,34 @@ +package my_select + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestRacer(t *testing.T) { + + slowServer := makeDelayedServer(20 * time.Microsecond) + defer slowServer.Close() + + fastServer := makeDelayedServer(0 * time.Microsecond) + 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) + } +} + +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) + })) +} -- 2.38.4 From c4e7f0d5eac19a57aff4c9a97ba910cbd43641f1 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Tue, 24 Sep 2024 16:30:01 -0400 Subject: [PATCH 08/13] A little datastructures --- datastructures/README.md | 7 ++++ datastructures/queue.go | 41 ++++++++++++++++++ datastructures/set.go | 91 ++++++++++++++++++++++++++++++++++++++++ datastructures/stack.go | 66 +++++++++++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 datastructures/README.md create mode 100644 datastructures/queue.go create mode 100644 datastructures/set.go create mode 100644 datastructures/stack.go diff --git a/datastructures/README.md b/datastructures/README.md new file mode 100644 index 0000000..fabe3c9 --- /dev/null +++ b/datastructures/README.md @@ -0,0 +1,7 @@ +# Basic Data Structures + +These are basic implementations of standard data structures. They are not thread safe implementations. To avoid race conditions you would need to use a mutex (mutual exclusion lock) to lock the data structure before any reads or writes. The `sync` package has a `RWMutex` that can be used. + + +## Example of using Mutex + diff --git a/datastructures/queue.go b/datastructures/queue.go new file mode 100644 index 0000000..271005f --- /dev/null +++ b/datastructures/queue.go @@ -0,0 +1,41 @@ +package datastructures + +import "errors" + +// Queue a First in First out data structure. +type Queue struct { + elements []any +} + +// Enqueue adds and element to the queue +func (q *Queue) Enqueue(el any) { + q.elements = append(q.elements, el) +} + +// Dequeue removes an element from the queue +func (q *Queue) Dequeue() (any, error) { + if q.IsEmpty() { + return nil, errors.New("empty queue") + } + el := q.elements[0] + q.elements = q.elements[1:] + return el, nil +} + +// Peek retrieves a copy of the next element in the queue. +func (q *Queue) Peek() (any, error) { + if q.IsEmpty() { + return nil, errors.New("empty queue") + } + return q.elements[0], nil +} + +// IsEmpty checks if the queue has any elements +func (q *Queue) IsEmpty() bool { + return q.Size() == 0 +} + +// Size checks the size of the queue +func (q *Queue) Size() int { + return len(q.elements) +} diff --git a/datastructures/set.go b/datastructures/set.go new file mode 100644 index 0000000..b8d5390 --- /dev/null +++ b/datastructures/set.go @@ -0,0 +1,91 @@ +package datastructures + +type Set struct { + elements map[any]bool +} + +func (s *Set) Add(el any) { + s.elements[el] = true +} + +func (s *Set) Remove(el any) { + delete(s.elements, el) +} + +func (s *Set) IsEmpty() bool { + return s.Size() == 0 +} + +func (s *Set) Size() int { + return len(s.elements) +} + +func (s *Set) Has(el any) bool { + _, ok := s.elements[el] + return ok +} + +func (s *Set) Difference(set Set) Set { + d := s.Copy() + for k := range d.elements { + if set.Has(k) { + d.Remove(k) + } + } + + return d +} + +func (s *Set) IsSubset(t Set) bool { + for k := range s.elements { + if !t.Has(k) { + return false + } + } + return true +} + +func (s *Set) List() []any { + list := make([]any, s.Size()) + for k := range s.elements { + list = append(list, k) + } + return list +} + +func (s *Set) Copy() Set { + c := NewSet() + for k := range s.elements { + c.Add(k) + } + return c +} + +// NewSet creates a new instance of Set +func NewSet() Set { + return Set{elements: make(map[any]bool)} +} + +// Union determines the OR relationship on all sets under consideration +func Union(sets ...Set) Set { + u := sets[0].Copy() + for _, set := range sets[1:] { + for k := range set.elements { + u.Add(k) + } + } + return u +} + +// Intersection determines the AND relationship of all sets under consideration +func Intersection(sets ...Set) Set { + i := sets[0].Copy() + for k := range i.elements { + for _, set := range sets[1:] { + if !set.Has(k) { + i.Remove(k) + } + } + } + return i +} diff --git a/datastructures/stack.go b/datastructures/stack.go new file mode 100644 index 0000000..e4252f7 --- /dev/null +++ b/datastructures/stack.go @@ -0,0 +1,66 @@ +package datastructures + +import "errors" + +// Stack a Last in First out data structure. +type Stack struct { + elements []any +} + +// Push add an element to the top of the stack +// +// Parameters: +// +// any: an element to add to the top of the stack. +func (s *Stack) Push(el any) { + s.elements = append(s.elements, el) +} + +// Pop removes and returns the top element of the stack. +// +// Returns: +// +// any: The top element of the stack +// +// error: If the stack is found to be empty +func (s *Stack) Pop() (any, error) { + if s.IsEmpty() { + return nil, errors.New("empty stack") + } + el := s.elements[len(s.elements)-1] + s.elements = s.elements[:len(s.elements)-1] + return el, nil +} + +// Peek checks the top item in the stack. +// +// Returns: +// +// any: a copy of the top element +// +// error: An error if the stack is empty +func (s *Stack) Peek() (any, error) { + if s.IsEmpty() { + return nil, errors.New("empty stack") + } + + return s.elements[len(s.elements)-1], nil +} + +// IsEmpty checks if the stack contains any elements. +// +// Returns: +// +// bool: True is the stack is empty. False if the stack contains elements. +func (s *Stack) IsEmpty() bool { + return s.Size() == 0 +} + +// Size checks this size of the stack +// +// Returns: +// +// int: The number of elements on the stack +func (s *Stack) Size() int { + return len(s.elements) +} -- 2.38.4 From 49410fd49e5dcb4de47d31833b8e118bf2022716 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Tue, 24 Sep 2024 21:12:57 -0400 Subject: [PATCH 09/13] Adding debugger notes --- datastructures/go.mod | 3 + debugger/README.md | 297 ++++++++++++++++++++++++++++++++++++++++++ debugger/go.mod | 3 + debugger/main.go | 51 ++++++++ 4 files changed, 354 insertions(+) create mode 100644 datastructures/go.mod create mode 100644 debugger/README.md create mode 100644 debugger/go.mod create mode 100644 debugger/main.go diff --git a/datastructures/go.mod b/datastructures/go.mod new file mode 100644 index 0000000..eb1324b --- /dev/null +++ b/datastructures/go.mod @@ -0,0 +1,3 @@ +module datastructures + +go 1.23.1 diff --git a/debugger/README.md b/debugger/README.md new file mode 100644 index 0000000..1dd9bb4 --- /dev/null +++ b/debugger/README.md @@ -0,0 +1,297 @@ +# Using the Debugger + +Go doesn't ship with a debugger but you can use the delve package instead. + +``` +go get github.com/go-delve/delve/cmd/dlv +``` + +``` +dlv version +Delve Debugger +Version: 1.23.1 +Build: $Id: 2eba762d75437d380e48fc42213853f13aa2904d $ +``` + +## Using dlv + +From within a go module run: + +``` +dlv debug + +Type 'help' for list of commands. +(dlv) +``` + +Now set a breakpoint on the main function + +``` +break main.main +``` + +This breaks not at your main function but in the go runtime. + +Now you can do something like set a break point at a line number in a file + +``` +break main.go:20 +``` + +Now we can continue until we hit a breakpoint + +``` +c or continue + + +> [Breakpoint 1] main.main() ./main.go:19 (hits goroutine(1):1 total:1) (PC: 0x4ae66e) + 14: + 15: func Three() { + 16: fmt.Println("Three!") + 17: } + 18: +=> 19: func main() { + 20: msg := "Hello debugger" + 21: One() + 22: fmt.Println(msg) + 23: + 24: } +``` + +Now we can check for locals + +``` +locals + +(no locals) +``` + +No surprising there are no local variables at this point. + +``` +n +``` + +Will advance us to the next line. But again no locals because that hasn't been executed yet. So `n` again. + +``` +locals + +msg = "Hello debugger" +``` + + + +``` +ls + +> main.main() ./main.go:21 (PC: 0x4ae687) + 16: fmt.Println("Three!") + 17: } + 18: + 19: func main() { + 20: msg := "Hello debugger" +=> 21: One() + 22: fmt.Println(msg) + 23: + 24: } +``` + +Now we can step into our function with `s` + +``` +s + +> main.One() ./main.go:5 (PC: 0x4ae48a) + 1: package main + 2: + 3: import "fmt" + 4: +=> 5: func One() { + 6: Two() + 7: fmt.Println("One!") + 8: } + 9: + 10: func Two() { +``` + +You can se now we have stepped into the function. + +We can `n` and `s` into `Two()`. + +``` +> main.Two() ./main.go:10 (PC: 0x4ae52a) + 5: func One() { + 6: Two() + 7: fmt.Println("One!") + 8: } + 9: +=> 10: func Two() { + 11: Three() + 12: fmt.Println("Two!") + 13: } + 14: + 15: func Three() { +``` + +How about we look at the stack trace at this point + +``` +stack +0 0x00000000004ae52a in main.Two + at ./main.go:10 +1 0x00000000004ae493 in main.One + at ./main.go:6 +2 0x00000000004ae68c in main.main + at ./main.go:21 +3 0x000000000043b647 in runtime.main + at /usr/local/go/src/runtime/proc.go:272 +4 0x0000000000474081 in runtime.goexit + at /usr/local/go/src/runtime/asm_amd64.s:1700 +``` + +Let's `n` and `s` one more time and check the stack trace. + +``` +0 0x00000000004ae5ca in main.Three + at ./main.go:15 +1 0x00000000004ae533 in main.Two + at ./main.go:11 +2 0x00000000004ae493 in main.One + at ./main.go:6 +3 0x00000000004ae68c in main.main + at ./main.go:21 +4 0x000000000043b647 in runtime.main + at /usr/local/go/src/runtime/proc.go:272 +5 0x0000000000474081 in runtime.goexit + at /usr/local/go/src/runtime/asm_amd64.s:1700 +``` + +If we needed to we could us `frame` to go back into the stack and and we can inspect the state of code at that point. + + +``` +frame 3 + +locals +``` + +But then we need to move back to `frame 0` and can continue debugging. + +``` +(dlv) ls +> main.Two() ./main.go:11 (PC: 0x4ae52e) + 6: Two() + 7: fmt.Println("One!") + 8: } + 9: + 10: func Two() { +=> 11: Three() + 12: fmt.Println("Two!") + 13: } + 14: + 15: func Three() { + 16: fmt.Println("Three!") +``` + +Instead of stepping to `Three()` let's step out of Two. + +``` +(dlv) so +Three! +Two! +> main.One() ./main.go:7 (PC: 0x4ae493) +Values returned: + + 2: + 3: import "fmt" + 4: + 5: func One() { + 6: Two() +=> 7: fmt.Println("One!") + 8: } + 9: + 10: func Two() { + 11: Three() + 12: fmt.Println("Two!") +``` + +We see out stdout messages now there is one to go. + +``` +(dlv) stack +0 0x00000000004ae493 in main.One + at ./main.go:7 +1 0x00000000004ae68c in main.main + at ./main.go:21 +2 0x000000000043b647 in runtime.main + at /usr/local/go/src/runtime/proc.go:272 +3 0x0000000000474081 in runtime.goexit + at /usr/local/go/src/runtime/asm_amd64.s:1700 +``` + +We can continue this until our process returns. But when we are back in our main function. Let's look at locals one more time. + +``` + 17: } + 18: + 19: func main() { + 20: msg := "Hello debugger" + 21: One() +=> 22: fmt.Println(msg) + 23: + 24: } + +locals + +msg = "Hello debugger" +``` + +We can't change this variable because Delve wants to avoid operations itself that involves memory allocations, but if we had a + +``` +func setMsg(value string) { + msg = value +} +``` + +We could use `call` to set that variable + +``` +call setMsg("dirp") +``` + +To close it could just `continue` until the process exits. + +``` +(dlv) c + +Process 29036 has exited with status 0 +``` + +## Wait there is more + +Help has tons of stuff + +``` +help +``` + +Like + +``` +restart +``` +Restarts the program of course. Our current breakpoints are still set. + +``` +(dlv) print msg +"Hello debugger" +``` + +And hey this time we used print to check a value. + +Technically you can edit code with `edit`. + + +## Debugging goroutines \ No newline at end of file diff --git a/debugger/go.mod b/debugger/go.mod new file mode 100644 index 0000000..f905c7f --- /dev/null +++ b/debugger/go.mod @@ -0,0 +1,3 @@ +module debugger + +go 1.23.1 diff --git a/debugger/main.go b/debugger/main.go new file mode 100644 index 0000000..8bc639a --- /dev/null +++ b/debugger/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "sync" +) + +func One() { + Two() + fmt.Println("One!") +} + +func Two() { + Three() + fmt.Println("Two!") +} + +func Three() { + fmt.Println("Three!") +} + +// Used in the simple example +// func main() { +// msg := "Hello debugger" +// One() +// fmt.Println(msg) + +// } + +// Used for Goroutine debugging + +func printMe(i int, wg *sync.WaitGroup) { + defer wg.Done() + fmt.Printf("I'm a go routine %d\n", i) +} + +func main() { + i := 0 + + var wg sync.WaitGroup + wg.Add(9) + + for i < 10 { + go func(rI int) { + printMe(rI, &wg) + }(i) + i++ + } + + wg.Wait() +} -- 2.38.4 From a68ba74b5ab2130d8d0dcda7f7d65645a25b20c9 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sun, 13 Oct 2024 09:16:59 -0400 Subject: [PATCH 10/13] Add debugger nodes and select chapter --- debugger/README.md | 6 +- .../concurrency/CheckWebsites.go | 2 + learn_go_with_tests/my_select/racer.go | 65 +++++++++++++++---- learn_go_with_tests/my_select/racer_test.go | 39 +++++++---- 4 files changed, 89 insertions(+), 23 deletions(-) diff --git a/debugger/README.md b/debugger/README.md index 1dd9bb4..90deb3d 100644 --- a/debugger/README.md +++ b/debugger/README.md @@ -2,6 +2,8 @@ Go doesn't ship with a debugger but you can use the delve package instead. +https://www.youtube.com/watch?v=UA0SirX6Siw + ``` go get github.com/go-delve/delve/cmd/dlv ``` @@ -294,4 +296,6 @@ And hey this time we used print to check a value. Technically you can edit code with `edit`. -## Debugging goroutines \ No newline at end of file +## Debugging goroutines + + diff --git a/learn_go_with_tests/concurrency/CheckWebsites.go b/learn_go_with_tests/concurrency/CheckWebsites.go index 1fbbfda..511d484 100644 --- a/learn_go_with_tests/concurrency/CheckWebsites.go +++ b/learn_go_with_tests/concurrency/CheckWebsites.go @@ -25,6 +25,8 @@ func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool { }(url) // or just pass by value to give the func it's own url } + // Improvement idea. We could use a wait group here. Instead we are blocking + // each go routine because we are using an unbuffered channel. for i := 0; i < len(urls); i++ { r := <-resultChannel // receive statement results[r.string] = r.bool diff --git a/learn_go_with_tests/my_select/racer.go b/learn_go_with_tests/my_select/racer.go index 43b89d8..305a610 100644 --- a/learn_go_with_tests/my_select/racer.go +++ b/learn_go_with_tests/my_select/racer.go @@ -1,23 +1,66 @@ package my_select import ( + "fmt" "net/http" "time" ) -func Racer(a string, b string) (winner string) { - aDuration := measureResponseTime(a) - bDuration := measureResponseTime(b) +var defaultTimeout time.Duration = 10 * time.Second - if aDuration < bDuration { - return a - } +func Racer(a, b string) (winner string, err error) { + return ConfigurableRacer(a, b, defaultTimeout) +} + +func ConfigurableRacer(a string, b string, timeout time.Duration) (winner string, err error) { + // aDuration := measureResponseTime(a) + // bDuration := measureResponseTime(b) + + // if aDuration < bDuration { + // return a + // } + + // return b + + select { + // Here we are simply waiting on the channel closing out + // The first one that does so will return it's url - return b + // These are blocking operations because they are unbuffered channels. + // This works though because `select` allows us to wait on multiple channels + case <-ping(a): + return a, nil + case <-ping(b): + return b, nil + // Super handy function during select, returns a channel, waits the timeout, then + //sends the current time that it was triggered at. + // Helps get us out of a blocking case. + case <-time.After(timeout): + return "", fmt.Errorf("timed out waiting for %s and %s", a, b) + } } -func measureResponseTime(url string) time.Duration { - start := time.Now() - http.Get(url) - return time.Since(start) +// In our case, we don't care what type is sent to +//the channel, we just want to signal we are done +// and closing the channel works perfectly! + +// a chan struct{} is the smallest data type available +// from a memory perspective + +func ping(url string) chan struct{} { + // Notice how we have to use make when creating a channel; rather than say var ch chan struct{}. When you use var the variable will be initialised with the "zero" value of the type. So for string it is "", int it is 0, etc. + + // For channels the zero value is nil and if you try and send to it with <- it will block forever because you cannot send to nil channels + 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) +// } diff --git a/learn_go_with_tests/my_select/racer_test.go b/learn_go_with_tests/my_select/racer_test.go index d5d27a3..7262ab6 100644 --- a/learn_go_with_tests/my_select/racer_test.go +++ b/learn_go_with_tests/my_select/racer_test.go @@ -9,21 +9,38 @@ import ( func TestRacer(t *testing.T) { - slowServer := makeDelayedServer(20 * time.Microsecond) - defer slowServer.Close() + t.Run("compares speeds of servers, returning url of the fastest one", func(t *testing.T) { + slowServer := makeDelayedServer(20 * time.Microsecond) + defer slowServer.Close() - fastServer := makeDelayedServer(0 * time.Microsecond) - defer fastServer.Close() + fastServer := makeDelayedServer(0 * time.Microsecond) + defer fastServer.Close() - slowUrl := slowServer.URL - fastUrl := fastServer.URL + slowUrl := slowServer.URL + fastUrl := fastServer.URL - want := fastUrl - got := Racer(slowUrl, fastUrl) + want := fastUrl + got, _ := Racer(slowUrl, fastUrl) - if got != want { - t.Errorf("got %q, want %q", got, want) - } + if got != want { + t.Errorf("got %q, want %q", got, want) + } + }) + + t.Run("returns an error if server doesn't respond within 10s", func(t *testing.T) { + serverA := makeDelayedServer(50 * time.Millisecond) + serverB := makeDelayedServer(60 * time.Millisecond) + + defer serverA.Close() + defer serverB.Close() + + _, err := ConfigurableRacer(serverA.URL, serverB.URL, 10*time.Millisecond) + + if err == nil { + t.Error("expected an error but didn't get one") + } + + }) } func makeDelayedServer(delay time.Duration) *httptest.Server { -- 2.38.4 From 6eb1756aa516e37ae0af2ab2b1d91e283fbc9b07 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sun, 13 Oct 2024 14:23:03 -0400 Subject: [PATCH 11/13] Adding reflection module --- learn_go_with_tests/reflection/go.mod | 3 + learn_go_with_tests/reflection/reflection.go | 99 ++++++++++ .../reflection/reflection_test.go | 177 ++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 learn_go_with_tests/reflection/go.mod create mode 100644 learn_go_with_tests/reflection/reflection.go create mode 100644 learn_go_with_tests/reflection/reflection_test.go diff --git a/learn_go_with_tests/reflection/go.mod b/learn_go_with_tests/reflection/go.mod new file mode 100644 index 0000000..fae5224 --- /dev/null +++ b/learn_go_with_tests/reflection/go.mod @@ -0,0 +1,3 @@ +module reflection + +go 1.23.1 diff --git a/learn_go_with_tests/reflection/reflection.go b/learn_go_with_tests/reflection/reflection.go new file mode 100644 index 0000000..daf122f --- /dev/null +++ b/learn_go_with_tests/reflection/reflection.go @@ -0,0 +1,99 @@ +package reflection + +import "reflect" + +// Go allows for `interface{}` which we can think as as just +// any type. (any is just an alias for interface{}) this allows for +// some metaprogramming features in the language reflection being one +// of them. Reflection allows us to examine structure at runtime. + +// The downside, you lose type safety and performance impact. + +// Only use reflection if you really need to. + +// See also https://blog.golang.org/laws-of-reflection + +func walk(x interface{}, fn func(input string)) { + // This code is very unsafe and very naive, + val := getValue(x) + + // if val.Kind() == reflect.Slice { + // for i := 0; i < val.Len(); i++ { + // walk(val.Index(i).Interface(), fn) + // } + // return + // } + + // // This would panic if there were no fields + // field := val.Field(0) + // // This would be wrong if the field had any value other than a string + // fn(field.String()) + + // // change for test table ( will fail case 2) + // for i := 0; i < val.NumField(); i++ { + // field := val.Field(i) + // fn(field.String()) + // } + + // THIS GOT messy + // for i := 0; i < val.NumField(); i++ { + // field := val.Field(i) + + // // if field.Kind() == reflect.String { + // // fn(field.String()) + // // } + + // // if field.Kind() == reflect.Struct { + // // // it got recursive + // // walk(field.Interface(), fn) + // // } + + // switch field.Kind() { + // case reflect.String: + // fn(field.String()) + // case reflect.Struct: + // walk(field.Interface(), fn) + // } + // } + + switch val.Kind() { + case reflect.Struct: + for i := 0; i < val.NumField(); i++ { + walk(val.Field(i).Interface(), fn) + } + case reflect.Slice, reflect.Array: + for i := 0; i < val.Len(); i++ { + walk(val.Index(i).Interface(), fn) + } + case reflect.Map: + for _, key := range val.MapKeys() { + walk(val.MapIndex(key).Interface(), fn) + } + + case reflect.Chan: + for { + if v, ok := val.Recv(); ok { + walk(v.Interface(), fn) + } else { + break + } + } + case reflect.Func: + valFnResult := val.Call(nil) + for _, res := range valFnResult { + walk(res.Interface(), fn) + } + case reflect.String: + fn(val.String()) + } +} + +func getValue(x interface{}) reflect.Value { + val := reflect.ValueOf(x) + + if val.Kind() == reflect.Pointer { + val = val.Elem() + } + + return val +} diff --git a/learn_go_with_tests/reflection/reflection_test.go b/learn_go_with_tests/reflection/reflection_test.go new file mode 100644 index 0000000..85dc83a --- /dev/null +++ b/learn_go_with_tests/reflection/reflection_test.go @@ -0,0 +1,177 @@ +package reflection + +import ( + "reflect" + "testing" +) + +type Person struct { + Name string + Profile Profile +} + +type Profile struct { + Age int + City string +} + +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) + } +} + +func TestWalk(t *testing.T) { + + cases := []struct { + Name string + Input interface{} + ExpectedCalls []string + }{ + // case 0 + { + Name: "struct with on string field", + Input: struct{ Name string }{"Drew"}, + ExpectedCalls: []string{"Drew"}, + }, + // case 1 + { + "struct with two string fields", + struct { + Name string + City string + }{"Chris", "London"}, + []string{"Chris", "London"}, + }, + // case 2 + { + "struct with non string field", + struct { + Name string + Age int + }{"Chris", 33}, + []string{"Chris"}, + }, + // case 3 Nested Struct + { + "nested fields", + Person{ + "Chris", + Profile{33, "London"}, + }, + []string{"Chris", "London"}, + }, + // case 4 struct as pointer + { + "pointers to things", + &Person{ + "Chris", + Profile{33, "London"}, + }, + []string{"Chris", "London"}, + }, + // case 5 slice of profiles + { + "slices", + []Profile{ + {33, "London"}, + {34, "Reykjavík"}, + }, + []string{"London", "Reykjavík"}, + }, + // case 6 array + { + "arrays", + [2]Profile{ + {33, "London"}, + {34, "Reykjavík"}, + }, + []string{"London", "Reykjavík"}, + }, + // case 7 map THIS HAS A GOTCHA + // maps in go do not guarantee order. It will eventually fake + // moved to a separate test run to handle that case + // { + // "maps", + // map[string]string{ + // "Cow": "Moo", + // "Sheep": "Baa", + // }, + // []string{"Moo", "Baa"}, + // }, + } + + 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("get %v, want %v", got, test.ExpectedCalls) + } + }) + + } + + t.Run("with mas", func(t *testing.T) { + aMap := map[string]string{ + "Cow": "Moo", + "Sheep": "Baa", + } + + var got []string + walk(aMap, func(input string) { + got = append(got, input) + }) + + assertContains(t, got, "Moo") + assertContains(t, got, "Baa") + }) + + 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) + } + }) +} -- 2.38.4 From 77a1027ab037c254d2586935cffeb720fe1daba4 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Sun, 13 Oct 2024 18:43:30 -0400 Subject: [PATCH 12/13] Failing concurrent test --- learn_go_with_tests/my_sync/go.mod | 3 ++ learn_go_with_tests/my_sync/my_sync.go | 13 ++++++ learn_go_with_tests/my_sync/my_sync_test.go | 44 +++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 learn_go_with_tests/my_sync/go.mod create mode 100644 learn_go_with_tests/my_sync/my_sync.go create mode 100644 learn_go_with_tests/my_sync/my_sync_test.go diff --git a/learn_go_with_tests/my_sync/go.mod b/learn_go_with_tests/my_sync/go.mod new file mode 100644 index 0000000..e52cb81 --- /dev/null +++ b/learn_go_with_tests/my_sync/go.mod @@ -0,0 +1,3 @@ +module my_sync + +go 1.23.1 diff --git a/learn_go_with_tests/my_sync/my_sync.go b/learn_go_with_tests/my_sync/my_sync.go new file mode 100644 index 0000000..d56aca6 --- /dev/null +++ b/learn_go_with_tests/my_sync/my_sync.go @@ -0,0 +1,13 @@ +package my_sync + +type Counter struct { + count int +} + +func (c *Counter) Inc() { + c.count++ +} + +func (c *Counter) Value() int { + return c.count +} diff --git a/learn_go_with_tests/my_sync/my_sync_test.go b/learn_go_with_tests/my_sync/my_sync_test.go new file mode 100644 index 0000000..f544c74 --- /dev/null +++ b/learn_go_with_tests/my_sync/my_sync_test.go @@ -0,0 +1,44 @@ +package my_sync + +import ( + "sync" + "testing" +) + +func assertCount(t testing.TB, got Counter, want int) { + t.Helper() + if got.Value() != want { + t.Errorf("got %d, want %d", got.Value(), want) + } +} + +func TestCounter(t *testing.T) { + t.Run("Incrementing the counter 3 times leaves it at 3", func(t *testing.T) { + counter := Counter{} + counter.Inc() + counter.Inc() + counter.Inc() + + assertCount(t, counter, 3) + + }) + + t.Run("it runs safely concurrently", func(t *testing.T) { + wantedCount := 1000 + counter := Counter{} + + var wg sync.WaitGroup + wg.Add(wantedCount) + + for i := 0; i < wantedCount; i++ { + go func() { + counter.Inc() + wg.Done() + }() + } + // blocking call to wait for all goroutines + wg.Wait() + + assertCount(t, counter, wantedCount) + }) +} -- 2.38.4 From f0ce635affce74ecee2dab8886f41324f21d4168 Mon Sep 17 00:00:00 2001 From: Drew Bednar Date: Mon, 14 Oct 2024 11:30:28 -0400 Subject: [PATCH 13/13] Finished sync --- learn_go_with_tests/my_sync/my_sync.go | 10 +++++++++ learn_go_with_tests/my_sync/my_sync_test.go | 25 ++++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/learn_go_with_tests/my_sync/my_sync.go b/learn_go_with_tests/my_sync/my_sync.go index d56aca6..97f0e9d 100644 --- a/learn_go_with_tests/my_sync/my_sync.go +++ b/learn_go_with_tests/my_sync/my_sync.go @@ -1,10 +1,20 @@ package my_sync +import "sync" + type Counter struct { + // mutual exclusion lock. Zero value is an unlocked Mutex + + // If we embedded the type it would be c.Lock() but that also makes all other + // Mutex functions part of the public interface, which we don't want. + // sync.Mutex + mu sync.Mutex count int } func (c *Counter) Inc() { + c.mu.Lock() + defer c.mu.Unlock() c.count++ } diff --git a/learn_go_with_tests/my_sync/my_sync_test.go b/learn_go_with_tests/my_sync/my_sync_test.go index f544c74..acfbd99 100644 --- a/learn_go_with_tests/my_sync/my_sync_test.go +++ b/learn_go_with_tests/my_sync/my_sync_test.go @@ -1,11 +1,29 @@ package my_sync +// READ https://go.dev/wiki/MutexOrChannel. The gist is that go prefers to share data by +// communicating, not by sharing. This mean stylistically you will see a lot more chan than Mutex + +// Channels are good for +// passing ownership of data, +// distributing units of work, +// communicating async results + +// Mutexes are good for caches, state. + import ( "sync" "testing" ) -func assertCount(t testing.TB, got Counter, want int) { +// go vet +// # my_sync +// # [my_sync] +// ./my_sync_test.go:8:36: assertCount passes lock by value: my_sync.Counter contains sync.Mutex +// ./my_sync_test.go:22:18: call of assertCount copies lock value: my_sync.Counter contains sync.Mutex +// ./my_sync_test.go:42:18: call of assertCount copies lock value: my_sync.Counter contains sync.Mutex +// We don't want to copy a mutex. So this should be changed to take a pointer to a counter + +func assertCount(t testing.TB, got *Counter, want int) { t.Helper() if got.Value() != want { t.Errorf("got %d, want %d", got.Value(), want) @@ -14,12 +32,13 @@ func assertCount(t testing.TB, got Counter, want int) { func TestCounter(t *testing.T) { t.Run("Incrementing the counter 3 times leaves it at 3", func(t *testing.T) { + // We could also implemented a `func NewCounter() *Counter {}` if we wanted to. counter := Counter{} counter.Inc() counter.Inc() counter.Inc() - assertCount(t, counter, 3) + assertCount(t, &counter, 3) }) @@ -39,6 +58,6 @@ func TestCounter(t *testing.T) { // blocking call to wait for all goroutines wg.Wait() - assertCount(t, counter, wantedCount) + assertCount(t, &counter, wantedCount) }) } -- 2.38.4