diff --git a/08-concurrency-foundations/60-contexts/flight/flight.go b/08-concurrency-foundations/60-contexts/flight/flight.go new file mode 100644 index 0000000..0f121de --- /dev/null +++ b/08-concurrency-foundations/60-contexts/flight/flight.go @@ -0,0 +1,6 @@ +package flight + +type Position struct { + Lat float32 + Lng float32 +} diff --git a/08-concurrency-foundations/60-contexts/main.go b/08-concurrency-foundations/60-contexts/main.go index 15caf93..5289bab 100644 --- a/08-concurrency-foundations/60-contexts/main.go +++ b/08-concurrency-foundations/60-contexts/main.go @@ -3,8 +3,25 @@ package main import ( "context" "net/http" + "time" + + "github.com/teivah/100-go-mistakes/08-concurrency-foundations/60-contexts/flight" ) +type publisher interface { + Publish(ctx context.Context, position flight.Position) error +} + +type publishHandler struct { + pub publisher +} + +func (h publishHandler) publishPosition(position flight.Position) error { + ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second) + defer cancel() + return h.pub.Publish(ctx, position) +} + type key string const isValidHostKey key = "isValidHost" diff --git a/09-concurrency-practice/63-goroutines-loop-variables/main.go b/09-concurrency-practice/63-goroutines-loop-variables/main.go index bdfe51d..7f06c18 100644 --- a/09-concurrency-practice/63-goroutines-loop-variables/main.go +++ b/09-concurrency-practice/63-goroutines-loop-variables/main.go @@ -27,8 +27,8 @@ func listing3() { s := []int{1, 2, 3} for _, i := range s { - go func(i int) { - fmt.Print(i) + go func(val int) { + fmt.Print(val) }(i) } } diff --git a/09-concurrency-practice/70-mutex-slices-maps/main.go b/09-concurrency-practice/70-mutex-slices-maps/main.go index 662abdb..23b8b48 100644 --- a/09-concurrency-practice/70-mutex-slices-maps/main.go +++ b/09-concurrency-practice/70-mutex-slices-maps/main.go @@ -17,7 +17,7 @@ func main() { } type Cache struct { - mu sync.Mutex + mu sync.RWMutex balances map[string]float64 } @@ -28,9 +28,9 @@ func (c *Cache) AddBalance(id string, balance float64) { } func (c *Cache) AverageBalance1() float64 { - c.mu.Lock() + c.mu.RLock() balances := c.balances - c.mu.Unlock() + c.mu.RUnlock() sum := 0. for _, balance := range balances { @@ -40,8 +40,8 @@ func (c *Cache) AverageBalance1() float64 { } func (c *Cache) AverageBalance2() float64 { - c.mu.Lock() - defer c.mu.Unlock() + c.mu.RLock() + defer c.mu.RUnlock() sum := 0. for _, balance := range c.balances { @@ -51,12 +51,12 @@ func (c *Cache) AverageBalance2() float64 { } func (c *Cache) AverageBalance3() float64 { - c.mu.Lock() + c.mu.RLock() m := make(map[string]float64, len(c.balances)) for k, v := range c.balances { m[k] = v } - c.mu.Unlock() + c.mu.RUnlock() sum := 0. for _, balance := range m { diff --git a/10-standard-lib/78-sql/rows-iterations-errors/main.go b/10-standard-lib/78-sql/rows-iterations-errors/main.go index 9e3e70c..8a1334a 100644 --- a/10-standard-lib/78-sql/rows-iterations-errors/main.go +++ b/10-standard-lib/78-sql/rows-iterations-errors/main.go @@ -6,8 +6,8 @@ import ( "log" ) -func (c *conn) get1(ctx context.Context, id string) (string, int, error) { - rows, err := c.db.QueryContext(ctx, +func get1(ctx context.Context, db *sql.DB, id string) (string, int, error) { + rows, err := db.QueryContext(ctx, "SELECT DEP, AGE FROM EMP WHERE ID = ?", id) if err != nil { return "", 0, err @@ -33,8 +33,8 @@ func (c *conn) get1(ctx context.Context, id string) (string, int, error) { return department, age, nil } -func (c *conn) get2(ctx context.Context, id string) (string, int, error) { - rows, err := c.db.QueryContext(ctx, +func get2(ctx context.Context, db *sql.DB, id string) (string, int, error) { + rows, err := db.QueryContext(ctx, "SELECT DEP, AGE FROM EMP WHERE ID = ?", id) if err != nil { return "", 0, err @@ -62,7 +62,3 @@ func (c *conn) get2(ctx context.Context, id string) (string, int, error) { return department, age, nil } - -type conn struct { - db *sql.DB -} diff --git a/11-testing/87-time-api/listing1/main.go b/11-testing/87-time-api/listing1/main.go index 8ee2c2e..5b483ec 100644 --- a/11-testing/87-time-api/listing1/main.go +++ b/11-testing/87-time-api/listing1/main.go @@ -15,7 +15,7 @@ type Event struct { Data string } -func (c *Cache) TrimBefore(since time.Duration) { +func (c *Cache) TrimOlderThan(since time.Duration) { c.mu.RLock() defer c.mu.RUnlock() diff --git a/11-testing/87-time-api/listing1/main_test.go b/11-testing/87-time-api/listing1/main_test.go index 089c4f3..8c02799 100644 --- a/11-testing/87-time-api/listing1/main_test.go +++ b/11-testing/87-time-api/listing1/main_test.go @@ -5,7 +5,7 @@ import ( "time" ) -func TestCache_TrimBefore(t *testing.T) { +func TestCache_TrimOlderThan(t *testing.T) { events := []Event{ {Timestamp: time.Now().Add(-20 * time.Millisecond)}, {Timestamp: time.Now().Add(-10 * time.Millisecond)}, @@ -13,7 +13,7 @@ func TestCache_TrimBefore(t *testing.T) { } cache := &Cache{} cache.Add(events) - cache.TrimBefore(15 * time.Millisecond) + cache.TrimOlderThan(15 * time.Millisecond) got := cache.GetAll() expected := 2 if len(got) != expected { diff --git a/11-testing/87-time-api/listing2/main.go b/11-testing/87-time-api/listing2/main.go index d06c88e..6d0d944 100644 --- a/11-testing/87-time-api/listing2/main.go +++ b/11-testing/87-time-api/listing2/main.go @@ -25,7 +25,7 @@ type Event struct { Data string } -func (c *Cache) TrimBefore(since time.Duration) { +func (c *Cache) TrimOlderThan(since time.Duration) { c.mu.RLock() defer c.mu.RUnlock() diff --git a/11-testing/87-time-api/listing2/main_test.go b/11-testing/87-time-api/listing2/main_test.go index ae181c4..f977ee0 100644 --- a/11-testing/87-time-api/listing2/main_test.go +++ b/11-testing/87-time-api/listing2/main_test.go @@ -5,7 +5,7 @@ import ( "time" ) -func TestCache_TrimBefore(t *testing.T) { +func TestCache_TrimOlderThan(t *testing.T) { events := []Event{ {Timestamp: parseTime(t, "2020-01-01T12:00:00.04Z")}, {Timestamp: parseTime(t, "2020-01-01T12:00:00.05Z")}, @@ -15,7 +15,7 @@ func TestCache_TrimBefore(t *testing.T) { return parseTime(t, "2020-01-01T12:00:00.06Z") }} cache.Add(events) - cache.TrimBefore(15 * time.Millisecond) + cache.TrimOlderThan(15 * time.Millisecond) // ... } diff --git a/11-testing/87-time-api/listing3/main.go b/11-testing/87-time-api/listing3/main.go index fed414e..89f09e8 100644 --- a/11-testing/87-time-api/listing3/main.go +++ b/11-testing/87-time-api/listing3/main.go @@ -15,7 +15,7 @@ type Event struct { Data string } -func (c *Cache) TrimBefore(now time.Time, since time.Duration) { +func (c *Cache) TrimOlderThan(now time.Time, since time.Duration) { c.mu.RLock() defer c.mu.RUnlock() diff --git a/11-testing/87-time-api/listing3/main_test.go b/11-testing/87-time-api/listing3/main_test.go index 501cd82..762a008 100644 --- a/11-testing/87-time-api/listing3/main_test.go +++ b/11-testing/87-time-api/listing3/main_test.go @@ -5,7 +5,7 @@ import ( "time" ) -func TestCache_TrimBefore(t *testing.T) { +func TestCache_TrimOlderThan(t *testing.T) { events := []Event{ {Timestamp: parseTime(t, "2020-01-01T12:00:00.04Z")}, {Timestamp: parseTime(t, "2020-01-01T12:00:00.05Z")}, @@ -13,7 +13,7 @@ func TestCache_TrimBefore(t *testing.T) { } cache := &Cache{} cache.Add(events) - cache.TrimBefore(parseTime(t, "2020-01-01T12:00:00.06Z"), 15*time.Millisecond) + cache.TrimOlderThan(parseTime(t, "2020-01-01T12:00:00.06Z"), 15*time.Millisecond) got := cache.GetAll() expected := 2 if len(got) != expected { diff --git a/11-testing/87-time-api/listing4/main.go b/11-testing/87-time-api/listing4/main.go new file mode 100644 index 0000000..5b6ac00 --- /dev/null +++ b/11-testing/87-time-api/listing4/main.go @@ -0,0 +1,42 @@ +package listing1 + +import ( + "sync" + "time" +) + +type Cache struct { + mu sync.RWMutex + events []Event +} + +type Event struct { + Timestamp time.Time + Data string +} + +func (c *Cache) TrimOlderThan(t time.Time) { + c.mu.RLock() + defer c.mu.RUnlock() + + for i := 0; i < len(c.events); i++ { + if c.events[i].Timestamp.After(t) { + c.events = c.events[i:] + return + } + } +} + +func (c *Cache) Add(events []Event) { + c.mu.Lock() + defer c.mu.Unlock() + + c.events = append(c.events, events...) +} + +func (c *Cache) GetAll() []Event { + c.mu.RLock() + defer c.mu.RUnlock() + + return c.events +} diff --git a/11-testing/87-time-api/listing4/main_test.go b/11-testing/87-time-api/listing4/main_test.go new file mode 100644 index 0000000..68f0f38 --- /dev/null +++ b/11-testing/87-time-api/listing4/main_test.go @@ -0,0 +1,31 @@ +package listing1 + +import ( + "testing" + "time" +) + +func TestCache_TrimOlderThan(t *testing.T) { + events := []Event{ + {Timestamp: parseTime(t, "2020-01-01T12:00:00.04Z")}, + {Timestamp: parseTime(t, "2020-01-01T12:00:00.05Z")}, + {Timestamp: parseTime(t, "2020-01-01T12:00:00.06Z")}, + } + cache := &Cache{} + cache.Add(events) + cache.TrimOlderThan(parseTime(t, "2020-01-01T12:00:00.06Z"). + Add(-15 * time.Millisecond)) + got := cache.GetAll() + expected := 2 + if len(got) != expected { + t.Fatalf("expected %d, got %d", expected, len(got)) + } +} + +func parseTime(t *testing.T, timestamp string) time.Time { + ts, err := time.Parse(time.RFC3339, timestamp) + if err != nil { + t.FailNow() + } + return ts +} diff --git a/11-testing/88-utility-package/iotest/main.go b/11-testing/88-utility-package/iotest/main.go index bfb8593..ec37f84 100644 --- a/11-testing/88-utility-package/iotest/main.go +++ b/11-testing/88-utility-package/iotest/main.go @@ -12,7 +12,7 @@ func (l LowerCaseReader) Read(p []byte) (int, error) { return 0, nil } -func foo(r io.Reader) error { +func foo1(r io.Reader) error { b, err := io.ReadAll(r) if err != nil { return err @@ -23,6 +23,17 @@ func foo(r io.Reader) error { return nil } +func foo2(r io.Reader) error { + b, err := readAll(r, 3) + if err != nil { + return err + } + + // ... + _ = b + return nil +} + func readAll(r io.Reader, retries int) ([]byte, error) { b := make([]byte, 0, 512) for { @@ -33,10 +44,10 @@ func readAll(r io.Reader, retries int) ([]byte, error) { b = b[:len(b)+n] if err != nil { if err == io.EOF { - err = nil + return b, nil } retries-- - if retries <= 0 { + if retries < 0 { return b, err } } diff --git a/11-testing/88-utility-package/iotest/main_test.go b/11-testing/88-utility-package/iotest/main_test.go index fc127eb..16ba851 100644 --- a/11-testing/88-utility-package/iotest/main_test.go +++ b/11-testing/88-utility-package/iotest/main_test.go @@ -16,8 +16,17 @@ func TestLowerCaseReader(t *testing.T) { } } -func TestFoo(t *testing.T) { - err := foo(iotest.TimeoutReader( +func TestFoo1(t *testing.T) { + err := foo1(iotest.TimeoutReader( + strings.NewReader(randomString(1024)), + )) + if err != nil { + t.Fatal(err) + } +} + +func TestFoo2(t *testing.T) { + err := foo2(iotest.TimeoutReader( strings.NewReader(randomString(1024)), )) if err != nil { diff --git a/11-testing/90-testing-features/setup-teardown/main_test.go b/11-testing/90-testing-features/setup-teardown/main_test.go index 41120e2..ed659a1 100644 --- a/11-testing/90-testing-features/setup-teardown/main_test.go +++ b/11-testing/90-testing-features/setup-teardown/main_test.go @@ -7,8 +7,8 @@ import ( ) func TestMySQLIntegration(t *testing.T) { - s := setupMySQL() - defer teardownMySQL(s) + setupMySQL() + defer teardownMySQL() // ... } @@ -26,12 +26,12 @@ func createConnection(t *testing.T, dsn string) *sql.DB { } func TestMain(m *testing.M) { - s := setupMySQL() + setupMySQL() code := m.Run() - teardownMySQL(s) + teardownMySQL() os.Exit(code) } -func setupMySQL() interface{} { return nil } +func setupMySQL() {} -func teardownMySQL(m interface{}) {} +func teardownMySQL() {} diff --git a/11-testing/90-testing-features/utility-function/main.go b/11-testing/90-testing-features/utility-function/main.go index 15a501b..38423f0 100644 --- a/11-testing/90-testing-features/utility-function/main.go +++ b/11-testing/90-testing-features/utility-function/main.go @@ -1,22 +1,5 @@ package main -func NewStore() Store { - return Store{} -} - -type Store struct{} - -func (s Store) PutCustomer(Customer) error { - return nil -} - type Customer struct { id string } - -func customerFactory(id string) (Customer, error) { - if id == "" { - return Customer{}, nil - } - return Customer{id: id}, nil -} diff --git a/11-testing/90-testing-features/utility-function/main_test.go b/11-testing/90-testing-features/utility-function/main_test.go index eaee7ec..f09e3c9 100644 --- a/11-testing/90-testing-features/utility-function/main_test.go +++ b/11-testing/90-testing-features/utility-function/main_test.go @@ -1,36 +1,44 @@ package main -import "testing" +import ( + "errors" + "testing" +) -func TestPutCustomer1(t *testing.T) { - customer, err := createCustomer1() +func TestCustomer1(t *testing.T) { + customer, err := createCustomer1("foo") if err != nil { t.Fatal(err) } - err = NewStore().PutCustomer(customer) - // Check err - _ = err + // ... + _ = customer } -func createCustomer1() (Customer, error) { - customer, err := customerFactory("foo") +func createCustomer1(someArg string) (Customer, error) { + customer, err := customerFactory(someArg) if err != nil { - return Customer{}, nil + return Customer{}, err } return customer, nil } -func TestPutCustomer(t *testing.T) { - customer := createCustomer2(t) - err := NewStore().PutCustomer(customer) - // Check err - _ = err +func TestCustomer2(t *testing.T) { + customer := createCustomer2(t, "foo") + // ... + _ = customer } -func createCustomer2(t *testing.T) Customer { - customer, err := customerFactory("foo") +func createCustomer2(t *testing.T, someArg string) Customer { + customer, err := customerFactory(someArg) if err != nil { t.Fatal(err) } return customer } + +func customerFactory(someArg string) (Customer, error) { + if someArg == "" { + return Customer{}, errors.New("empty") + } + return Customer{id: someArg}, nil +}