Merge pull request 'Ooops should have been working on master' (#1) from drew/sql-it into master
Reviewed-on: #1drew/sql-it
commit
001756b7e3
@ -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
|
||||
|
@ -0,0 +1,3 @@
|
||||
module datastructures
|
||||
|
||||
go 1.23.1
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -0,0 +1,301 @@
|
||||
# Using the Debugger
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
```
|
||||
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
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
module debugger
|
||||
|
||||
go 1.23.1
|
@ -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()
|
||||
}
|
@ -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.
|
@ -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
|
||||
```
|
Binary file not shown.
Binary file not shown.
@ -0,0 +1,5 @@
|
||||
module try-sqlite
|
||||
|
||||
go 1.21.0
|
||||
|
||||
require github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
@ -0,0 +1,2 @@
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS todos;
|
@ -0,0 +1,2 @@
|
||||
PRAGMA foreign_keys=1;
|
||||
CREATE TABLE todos (id INTEGER PRIMARY KEY AUTOINCREMENT, subject TEXT, TODO TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP);
|
@ -0,0 +1 @@
|
||||
ALTER TABLE todos DROP COLUMN completed;
|
@ -0,0 +1 @@
|
||||
ALTER TABLE todos ADD completed BOOL DEFAULT false;
|
@ -1,3 +1,10 @@
|
||||
module gowiki
|
||||
|
||||
go 1.21.0
|
||||
|
||||
require (
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 // indirect
|
||||
github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b // indirect
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
|
||||
)
|
||||
|
@ -0,0 +1,8 @@
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM=
|
||||
github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM=
|
||||
github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b h1:R7hev4b96zgXjKbS2ZNbHBnDvyFZhH+LlMqtKH6hIkU=
|
||||
github.com/tursodatabase/go-libsql v0.0.0-20240429120401-651096bbee0b/go.mod h1:TjsB2miB8RW2Sse8sdxzVTdeGlx74GloD5zJYUC38d8=
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU=
|
||||
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
|
@ -0,0 +1,36 @@
|
||||
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 {
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
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",
|
||||
"http://hackaday.com",
|
||||
"waat://furhurterwe.geds",
|
||||
}
|
||||
|
||||
want := map[string]bool{
|
||||
"http://google.com": true,
|
||||
"http://hackaday.com": true,
|
||||
"waat://furhurterwe.geds": false,
|
||||
}
|
||||
|
||||
got := CheckWebsites(mockWebsiteChecker, websites)
|
||||
|
||||
if !reflect.DeepEqual(want, got) {
|
||||
t.Fatalf("Wanted %v, got %v", want, got)
|
||||
}
|
||||
}
|
@ -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.
|
@ -0,0 +1,3 @@
|
||||
module concurrency
|
||||
|
||||
go 1.22.5
|
@ -0,0 +1,3 @@
|
||||
module my_select
|
||||
|
||||
go 1.22.5
|
@ -0,0 +1,66 @@
|
||||
package my_select
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defaultTimeout time.Duration = 10 * time.Second
|
||||
|
||||
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
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
// }
|
@ -0,0 +1,51 @@
|
||||
package my_select
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestRacer(t *testing.T) {
|
||||
|
||||
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()
|
||||
|
||||
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 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 {
|
||||
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
time.Sleep(delay)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
module my_sync
|
||||
|
||||
go 1.23.1
|
@ -0,0 +1,23 @@
|
||||
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++
|
||||
}
|
||||
|
||||
func (c *Counter) Value() int {
|
||||
return c.count
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
module reflection
|
||||
|
||||
go 1.23.1
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue