Merge branch 'master' of gitlab.com:slon/shad-go-private into treeiter

This commit is contained in:
Pavel 2022-04-02 14:49:28 +03:00
commit 6613189930
157 changed files with 1264 additions and 198 deletions

View file

@ -1,6 +1,99 @@
- group: SQL
start: 31-03-2022 18:00
deadline: 17-04-2022 23:59
tasks:
- task: wscat
score: 200
- task: dao
score: 100
- task: ledger
score: 200
- task: shopfront
score: 100
- group: HTTP
start: 23-03-2022 18:00
deadline: 10-04-2022 23:59
tasks:
- task: urlshortener
score: 100
- task: digitalclock
score: 100
- task: olympics
score: 200
- task: firewall
score: 200
- task: coverme
score: 300
- group: Concurrency with shared memory
start: 17-03-2022 23:59
deadline: 03-04-2022 23:59
tasks:
- task: dupcall
score: 200
- task: keylock
score: 200
- task: batcher
score: 200
- task: pubsub
score: 300
- group: "[HW] Gitfame"
start: 11-03-2022 11:59
deadline: 30-03-2022 23:59
tasks:
- task: gitfame
score: 0
- group: Testing
start: 10-03-2022 20:00
deadline: 29-03-2022 23:59
tasks:
- task: testequal
score: 100
- task: fileleak
score: 100
- task: tabletest
score: 100
- task: tparallel
score: 200
- group: Goroutines
start: 03-03-2022 18:00
deadline: 22-03-2022 23:59
tasks:
- task: tour1
score: 100
- task: once
score: 100
- task: rwmutex
score: 100
- task: waitgroup
score: 100
- task: cond
score: 100
- task: ratelimit
score: 100
- group: Interfaces
start: 24-02-2022 18:00
deadline: 13-03-2022 23:59
tasks:
- task: otp
score: 100
- task: lrucache
score: 100
- task: externalsort
score: 100
- task: retryupdate
score: 100
- task: ciletters
score: 100
- group: Basics - group: Basics
start: 17-02-2022 18:00 start: 17-02-2022 18:00
deadline: 27-02-2022 23:59 deadline: 06-03-2022 23:59
tasks: tasks:
- task: hotelbusiness - task: hotelbusiness
score: 100 score: 100

View file

@ -43,6 +43,13 @@ linters-settings:
stylecheck: stylecheck:
# https://staticcheck.io/docs/options#checks # https://staticcheck.io/docs/options#checks
checks: ["all", "-ST1018"] checks: ["all", "-ST1018"]
# https://staticcheck.io/docs/options#initialisms
initialisms: [
"ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS",
"RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL",
"UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "CSV",
]
linters: linters:
disable-all: true disable-all: true

View file

@ -11,8 +11,8 @@ check:
- golangci-lint run --build-tags private,solution ./... - golangci-lint run --build-tags private,solution ./...
#- go test -v -tags private,solution ./... #- go test -v -tags private,solution ./...
#- go test -v -race -tags private,solution ./... #- go test -v -race -tags private,solution ./...
- go test $(go list -tags private,solution ./... | grep -v /dockertest | grep -v /foolsday3) -tags private,solution -v ./... - sudo -u nobody HOME=/tmp PATH=$PATH /usr/local/go/bin/go test $(go list -tags private,solution ./... | grep -v /dockertest | grep -v /foolsday3) -tags private,solution -v ./...
- go test $(go list -tags private,solution ./... | grep -v /dockertest) -tags private,solution -v -race ./... - sudo -u nobody HOME=/tmp PATH=$PATH /usr/local/go/bin/go test $(go list -tags private,solution ./... | grep -v /dockertest) -tags private,solution -v -race ./...
rebuild-base-image: rebuild-base-image:
tags: tags:

View file

@ -1,6 +1,6 @@
# Курс по Го в ШАД # Курс по Го в ШАД
Для работы с кодом нужен go 1.17 или выше. Для работы с кодом нужен go 1.18 или выше.
```sh ```sh
git clone https://gitlab.com/slon/shad-go.git git clone https://gitlab.com/slon/shad-go.git

View file

@ -3,7 +3,7 @@
`Counter` используется для нахождения уникальных слов и подсчета вхождений каждого из них. `Counter` используется для нахождения уникальных слов и подсчета вхождений каждого из них.
Его интерфейс выглядит так: Его интерфейс выглядит так:
* `Count(r io.Reader) error` — функция, которая подсчитывает количество вхождений для каждого слова в тексте. * `Count(r io.Reader) error` — функция, которая подсчитывает количество вхождений каждого слова в тексте.
На вход подается io.Reader, в котором находится некоторый текст. На вход подается io.Reader, в котором находится некоторый текст.
Разделителями являются только переносы строк и пробелы. Разделителями являются только переносы строк и пробелы.
* `String() string` — преобразует мапу вида `{"слово": "количество вхождений"}` в форматированную строку. * `String() string` — преобразует мапу вида `{"слово": "количество вхождений"}` в форматированную строку.

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package allocs package allocs

View file

@ -1,5 +1,4 @@
//go:build !solution && !change //go:build !solution && !change
// +build !solution,!change
package allocs package allocs

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package batcher package batcher

View file

@ -1,5 +1,4 @@
//go:build race //go:build race
// +build race
package batcher package batcher

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package slow package slow

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package blowfish package blowfish

View file

@ -1,10 +1,10 @@
FROM golang:1.17 FROM golang:1.18
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
rsync libssl-dev \ rsync libssl-dev postgresql sudo redis-server \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.44.0 RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.2
RUN curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose RUN curl -L "https://github.com/docker/compose/releases/download/1.28.5/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
RUN chmod +x /usr/local/bin/docker-compose RUN chmod +x /usr/local/bin/docker-compose
RUN curl -fsSL https://get.docker.com | sh RUN curl -fsSL https://get.docker.com | sh

View file

@ -1,7 +1,8 @@
## ciletters ## ciletters
В этой задаче вам предстоит познакомиться со стандартным пакетом [text/template](https://golang.org/pkg/text/template/), В этой задаче вам предстоит познакомиться со стандартным пакетом [text/template](https://golang.org/pkg/text/template/),
позволяющим генерировать текст в определенном формате. позволяющим генерировать текст в определенном формате, а также с пакетом [embed](https://pkg.go.dev/embed),
позволяющим вкомпиливать в исполняемый файл строковые литералы.
### Легенда ### Легенда
@ -20,6 +21,8 @@
Нужно реализовать функцию `MakeLetter` из файла [letter.go](./letter.go), Нужно реализовать функцию `MakeLetter` из файла [letter.go](./letter.go),
которая по go объекту нотификации генерирует её текстовое представление. которая по go объекту нотификации генерирует её текстовое представление.
Для этого нужно написать `text/template` шаблон, сохранить его в отдельный файл, а затем получить его содержимое в коде с помомщью `go:embed`.
#### Прокомментированный пример из теста #### Прокомментированный пример из теста
``` ```
Your pipeline #194613 has failed! // 194613 -- это ID pipeline'а Your pipeline #194613 has failed! // 194613 -- это ID pipeline'а

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package ciletters package ciletters

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package ciletters package ciletters

View file

@ -7,7 +7,7 @@
### Что нужно сделать? ### Что нужно сделать?
Нужно написать реализацию Cond используя каналы. Нужно написать реализацию Cond, используя каналы.
Использование пакета [sync](https://golang.org/pkg/sync) в этой задаче запрещено! Использование пакета [sync](https://golang.org/pkg/sync) в этой задаче запрещено!

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package cond package cond

View file

@ -126,7 +126,7 @@ func TestCondBroadcast(t *testing.T) {
// nolint // nolint
func TestCondSignalStealing(t *testing.T) { func TestCondSignalStealing(t *testing.T) {
for iters := 0; iters < 1000; iters++ { for iters := 0; iters < 20000; iters++ {
var m sync.Mutex var m sync.Mutex
cond := New(&m) cond := New(&m)

View file

@ -16,12 +16,18 @@ Package main можно не тестировать.
Coverage 100% говорит ровно о том, что все строки кода выполнялись. Coverage 100% говорит ровно о том, что все строки кода выполнялись.
Хорошие тесты, в первую очередь, тестируют функциональность. Хорошие тесты, в первую очередь, тестируют функциональность.
Как посмотреть coverage: Как посмотреть общий coverage:
``` ```
go test -v -cover ./coverme/... go test -v -cover ./coverme/...
``` ```
Coverage можно выводить в html (см. ссылки), и эта функциональность поддерживается в Goland. Как посмотреть coverage пакета в html:
```
go test -v -coverprofile=/tmp/coverage.out ./coverme/models/...
go tool cover -html=/tmp/coverage.out
```
Аналогичная функциональность поддерживается в Goland.
Также рекомендуем ознакомиться с рассказом о cover в блоге (см. ссылки).
## Ссылки ## Ссылки
@ -44,7 +50,7 @@ Todo-app с минимальной функциональностью + client.
Health check: Health check:
``` ```
✗ curl -i -X GET localhost:6029/ ✗ curl -i -X GET localhost:6029/
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Type: application/json Content-Type: application/json
Date: Thu, 19 Mar 2020 21:46:02 GMT Date: Thu, 19 Mar 2020 21:46:02 GMT
@ -66,7 +72,7 @@ Content-Length: 51
Получить todo по id: Получить todo по id:
``` ```
✗ curl -i localhost:6029/todo/0 ✗ curl -i localhost:6029/todo/0
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Type: application/json Content-Type: application/json
Date: Thu, 19 Mar 2020 21:44:17 GMT Date: Thu, 19 Mar 2020 21:44:17 GMT
@ -77,7 +83,7 @@ Content-Length: 51
Получить все todo: Получить все todo:
``` ```
✗ curl -i -X GET localhost:6029/todo ✗ curl -i -X GET localhost:6029/todo
HTTP/1.1 200 OK HTTP/1.1 200 OK
Content-Type: application/json Content-Type: application/json
Date: Thu, 19 Mar 2020 21:44:37 GMT Date: Thu, 19 Mar 2020 21:44:37 GMT
@ -85,3 +91,19 @@ Content-Length: 53
[{"id":0,"title":"A","content":"a","finished":false}] [{"id":0,"title":"A","content":"a","finished":false}]
``` ```
Завершить todo:
```
✗ curl -i -X POST localhost:6029/todo/0/finish
HTTP/1.1 200 OK
Date: Thu, 24 Mar 2022 15:40:49 GMT
Content-Length: 0
✗ curl -i -X GET localhost:6029/todo
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 24 Mar 2022 15:41:04 GMT
Content-Length: 52
[{"id":0,"title":"A","content":"a","finished":true}]%
```

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package app package app
@ -9,7 +8,6 @@ import (
"net/http" "net/http"
"os" "os"
"strconv" "strconv"
"strings"
"github.com/gorilla/handlers" "github.com/gorilla/handlers"
"github.com/gorilla/mux" "github.com/gorilla/mux"
@ -34,10 +32,11 @@ func (app *App) Start(port int) {
func (app *App) initRoutes() { func (app *App) initRoutes() {
app.router = mux.NewRouter() app.router = mux.NewRouter()
app.router.HandleFunc("/", app.status).Methods("Get") app.router.HandleFunc("/", app.status).Methods("GET")
app.router.HandleFunc("/todo", app.list).Methods("Get") app.router.HandleFunc("/todo", app.list).Methods("GET")
app.router.HandleFunc("/todo/{id:[0-9]+}", app.getTodo).Methods("Get") app.router.HandleFunc("/todo/{id:[0-9]+}", app.getTodo).Methods("GET")
app.router.HandleFunc("/todo/create", app.addTodo).Methods("Post") app.router.HandleFunc("/todo/{id:[0-9]+}/finish", app.finishTodo).Methods("POST")
app.router.HandleFunc("/todo/create", app.addTodo).Methods("POST")
} }
func (app *App) run(addr string) { func (app *App) run(addr string) {
@ -80,7 +79,7 @@ func (app *App) addTodo(w http.ResponseWriter, r *http.Request) {
} }
func (app *App) getTodo(w http.ResponseWriter, r *http.Request) { func (app *App) getTodo(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/todo/")) id, err := strconv.Atoi(mux.Vars(r)["id"])
if err != nil { if err != nil {
utils.BadRequest(w, "ID must be an int") utils.BadRequest(w, "ID must be an int")
return return
@ -95,6 +94,21 @@ func (app *App) getTodo(w http.ResponseWriter, r *http.Request) {
_ = utils.RespondJSON(w, http.StatusOK, todo) _ = utils.RespondJSON(w, http.StatusOK, todo)
} }
func (app *App) finishTodo(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(mux.Vars(r)["id"])
if err != nil {
utils.BadRequest(w, "ID must be an int")
return
}
if err := app.db.FinishTodo(models.ID(id)); err != nil {
utils.ServerError(w)
return
}
w.WriteHeader(http.StatusOK)
}
func (app *App) status(w http.ResponseWriter, r *http.Request) { func (app *App) status(w http.ResponseWriter, r *http.Request) {
_ = utils.RespondJSON(w, http.StatusOK, "API is up and working!") _ = utils.RespondJSON(w, http.StatusOK, "API is up and working!")
} }

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package app package app

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package client package client
@ -69,3 +68,19 @@ func (c *Client) List() ([]*models.Todo, error) {
err = json.NewDecoder(resp.Body).Decode(&todos) err = json.NewDecoder(resp.Body).Decode(&todos)
return todos, err return todos, err
} }
func (c *Client) Finish(id models.ID) error {
u := fmt.Sprintf("%s/todo/%d/finish", c.addr, id)
resp, err := http.Post(u, "application/json", nil)
if err != nil {
return err
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
}
return err
}

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package main package main

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package models package models
@ -12,6 +11,7 @@ type Storage interface {
AddTodo(string, string) (*Todo, error) AddTodo(string, string) (*Todo, error)
GetTodo(ID) (*Todo, error) GetTodo(ID) (*Todo, error)
GetAll() ([]*Todo, error) GetAll() ([]*Todo, error)
FinishTodo(ID) error
} }
type InMemoryStorage struct { type InMemoryStorage struct {
@ -69,3 +69,16 @@ func (s *InMemoryStorage) GetAll() ([]*Todo, error) {
return out, nil return out, nil
} }
func (s *InMemoryStorage) FinishTodo(id ID) error {
s.mu.Lock()
defer s.mu.Unlock()
todo, ok := s.todos[id]
if !ok {
return fmt.Errorf("todo %d not found", id)
}
todo.MarkFinished()
return nil
}

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package models package models

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package utils package utils

47
dao/README.md Normal file
View file

@ -0,0 +1,47 @@
# dao
В этой задаче вам нужно реализовать data access object для доступа к таблице пользователей.
- Функция `CreateDao` должна создавать таблицу в базе данных.
- Метод `Create` должен создавать нового пользователя и назначать его `ID`.
За генерацию `ID` должна отвечать база данных, использовать `rand` запрещается.
`pgx` не поддерживает `LastInsertId`, используйте синтаксис `RETURNING id`.
- Метод `Update` меняет `Name` пользователя.
- Метод `Delete` удаляет пользователя из таблицы.
- Метод `Lookup` возвращает `Name` пользователя по `ID`.
- Метод `List` возвращает список всех пользователей в таблице.
## Запуск тестов на linux
Для работы тестов на ubuntu нужно установить пакет `postgresql`.
```
sudo apt install postgresql
```
Если вы работаете на другом дистрибутиве linux, воспользуйтесь своим пакетным менеджером. Для работы тестов нужны исполняемые файлы `postgres` и `initdb`.
Тесты сами запускают базу данных в начале, и останавливают её в конце.
База данных работает от текущего пользователя во временной директории.
## Запуск postgres в docker
Даже если у вас linux (и уж тем более, если нет), вы можете не захотеть ставить postgres в систему.
Альтернативный способ — запустить бд в докере.
Для этого нужно установить docker и docker-compose по инструкции из [dockertest](../dockertest/README.md).
Добиться успешного запуска
```
go test -v ./dockertest/...
```
Запускать тесты можно будет так:
```
(cd dao && docker-compose up -d && sleep 1 && env PGCONN="host=127.0.0.1 port=5432 database=shad-go user=gopher password=pass" go test -v ./... -count=1 || true && docker-compose down)
```
Эта команда стартует docker с postgresql, запускает тесты, передав им DSN через переменную окружения, удаляет контейнеры в конце.
Как подчистить контейнеры, если что-то пошло не так:
```
(cd dao && docker-compose down)
```

9
dao/dao.go Normal file
View file

@ -0,0 +1,9 @@
//go:build !solution
package dao
import "context"
func CreateDao(ctx context.Context, dsn string) (Dao, error) {
panic("not implemented")
}

55
dao/dao_test.go Normal file
View file

@ -0,0 +1,55 @@
package dao
import (
"context"
"database/sql"
"sort"
"testing"
"github.com/stretchr/testify/require"
"gitlab.com/slon/shad-go/pgfixture"
)
func TestDao(t *testing.T) {
dsn := pgfixture.Start(t)
ctx := context.Background()
dao, err := CreateDao(ctx, dsn)
require.NoError(t, err)
_, err = dao.Lookup(ctx, 42)
require.ErrorIs(t, err, sql.ErrNoRows)
aliceID, err := dao.Create(ctx, &User{Name: "Alice"})
require.NoError(t, err)
bobID, err := dao.Create(ctx, &User{Name: "Bob"})
require.NoError(t, err)
charlieID, err := dao.Create(ctx, &User{Name: "Charie"})
require.NoError(t, err)
require.Len(t, map[UserID]struct{}{aliceID: {}, bobID: {}, charlieID: {}}, 3)
alice, err := dao.Lookup(ctx, aliceID)
require.NoError(t, err)
require.Equal(t, alice, User{ID: aliceID, Name: "Alice"})
require.NoError(t, dao.Delete(ctx, bobID))
_, err = dao.Lookup(ctx, bobID)
require.ErrorIs(t, err, sql.ErrNoRows)
require.NoError(t, dao.Update(ctx, &User{ID: charlieID, Name: "Chaplin"}))
users, err := dao.List(ctx)
require.NoError(t, err)
sort.Slice(users, func(i, j int) bool {
return users[i].Name < users[j].Name
})
require.Equal(t, users, []User{
{ID: aliceID, Name: "Alice"},
{ID: charlieID, Name: "Chaplin"},
})
}

10
dao/docker-compose.yaml Normal file
View file

@ -0,0 +1,10 @@
version: '3'
services:
pg:
image: postgres:latest
environment:
POSTGRES_DB: shad-go
POSTGRES_USER: gopher
POSTGRES_PASSWORD: pass
ports:
- 5432:5432

22
dao/model.go Normal file
View file

@ -0,0 +1,22 @@
package dao
import (
"context"
)
type (
UserID int
User struct {
ID UserID
Name string
}
Dao interface {
Create(ctx context.Context, u *User) (UserID, error)
Update(ctx context.Context, u *User) error
Delete(ctx context.Context, id UserID) error
Lookup(ctx context.Context, id UserID) (User, error)
List(ctx context.Context) ([]User, error)
}
)

View file

@ -94,7 +94,7 @@ go install ./digitalclock/...
3. форматирование времени: https://gobyexample.com/time-formatting-parsing 3. форматирование времени: https://gobyexample.com/time-formatting-parsing
4. работа с картинками: https://golang.org/pkg/image/ 4. работа с картинками: https://golang.org/pkg/image/
Пример, создания простой png размера 5x5: Пример создания простой png размера 5x5:
```go ```go
package main package main

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package main package main

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package main package main

View file

@ -66,8 +66,8 @@ type Job struct {
## Архитектура системы ## Архитектура системы
Наша система будет состоять из трех компонент. Наша система будет состоять из трех компонент.
* Клиент - процесс запускающий сборку. * Клиент - процесс, запускающий сборку.
* Воркер - процесс запускающий команды компиляции и тестирования. * Воркер - процесс, запускающий команды компиляции и тестирования.
* Координатор - центральный процесс в системе, общается с клиентами и воркерами. Раздаёт задачи * Координатор - центральный процесс в системе, общается с клиентами и воркерами. Раздаёт задачи
воркерам. воркерам.

View file

@ -18,7 +18,7 @@ Client и Coordinator общаются через два вызова.
- `POST /build` - стартует новый билд. - `POST /build` - стартует новый билд.
* Client посылает в Body запроса json c описанием сборки. * Client посылает в Body запроса json c описанием сборки.
* Coordinator стримит в body ответа json сообщения описывающие прогресс сборки. * Coordinator стримит в body ответа json сообщения, описывающие прогресс сборки.
* Первым сообщением в ответе Coordinator присылает `buildID`. * Первым сообщением в ответе Coordinator присылает `buildID`.
* _Тут можно было бы использовать websocket, но нас устраивает более простое решение._ * _Тут можно было бы использовать websocket, но нас устраивает более простое решение._

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package api package api

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package api package api

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package api package api

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package api package api

View file

@ -13,9 +13,9 @@
`commit` помещает артефакт в кеш. `abort` отменяет запись артефакта, удаляя все данные. `commit` помещает артефакт в кеш. `abort` отменяет запись артефакта, удаляя все данные.
После первого вызова `commit`, все последующие вызовы `commit` и `abort` должны ничего не делать. После первого вызова `commit` все последующие вызовы `commit` и `abort` должны ничего не делать.
Точно так же, после вызова `abort`, все последующие вызовы `commit` и `abort` должны ничего не делать. Точно так же, после вызова `abort` все последующие вызовы `commit` и `abort` должны ничего не делать.
Горутина может начать читать артефакт, позвав метод `Get`. Много горутин могут читать артефакт одновременно. Горутина может начать читать артефакт, позвав метод `Get`. Много горутин могут читать артефакт одновременно.
Горутина должна позвать `unlock`, после того как она закончила работать с артефактом. Горутина должна позвать `unlock`, после того как она закончила работать с артефактом.

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package artifact package artifact

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package artifact package artifact

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package artifact package artifact

View file

@ -1,4 +1,4 @@
# build # build
Пакет `build` содержит описание графа сборки и набор хелпер-функций для работы с графом. Вам не нужно Пакет `build` содержит описание графа сборки и набор хелпер-функций для работы с графом. Вам не нужно
писать новый код в этом пакете, но нужно научиться пользоваться тем кодом который вам дан. писать новый код в этом пакете, но нужно научиться пользоваться тем кодом, который вам дан.

View file

@ -1,12 +1,12 @@
# client # client
Пакет `client` реализует клиента системы распределённой сборки. Клиент запускается локально, и имеет доступ к Пакет `client` реализует клиента системы распределённой сборки. Клиент запускается локально и имеет доступ к
директории с исходным кодом. директории с исходным кодом.
Клиент получает на вход `build.Graph` и запускает сборку на координаторе. Клиент получает на вход `build.Graph` и запускает сборку на координаторе.
После того, как координатор создал новую сборку, клиент заливает недостающие файлы и посылает сигнал о завершении стадии заливки. После того, как координатор создал новую сборку, клиент заливает недостающие файлы и посылает сигнал о завершении стадии заливки.
После этого, клиент следит за прогрессом сборки, дожидается завершения и выходит. После этого клиент следит за прогрессом сборки, дожидается завершения и выходит.
Клиент тестируется интеграционными тестами из пакета `disttest`. Клиент тестируется интеграционными тестами из пакета `disttest`.

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package client package client

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package dist package dist

View file

@ -12,7 +12,7 @@
- Вызов `GET /file?id=123` должен возвращать содержимое файла с `id=123`. - Вызов `GET /file?id=123` должен возвращать содержимое файла с `id=123`.
- Вызов `PUT /file?id=123` должен заливать содержимое файла с `id=123`. - Вызов `PUT /file?id=123` должен заливать содержимое файла с `id=123`.
**Обратите внимание:** Несколько клиентов могут начать заливать в кеш один и тот же набор файлов. В наивной реализации, **Обратите внимание:** Несколько клиентов могут начать заливать в кеш один и тот же набор файлов. В наивной реализации
первый клиент залочит файл на запись, а следующие упадут с ошибкой. Ваш код должен обрабатывать эту ситуацию корректно, первый клиент залочит файл на запись, а следующие упадут с ошибкой. Ваш код должен обрабатывать эту ситуацию корректно,
то есть последующие запросы должны дожидаться, пока первый запрос завершится. Для реализации этой логики то есть последующие запросы должны дожидаться, пока первый запрос завершится. Для реализации этой логики
поведения вам поможет пакет [singleflight](https://godoc.org/golang.org/x/sync/singleflight). поведения вам поможет пакет [singleflight](https://godoc.org/golang.org/x/sync/singleflight).

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package filecache package filecache

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package filecache package filecache

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package filecache package filecache

View file

@ -15,7 +15,7 @@
воркерами. воркерами.
Для того, чтобы зачесть домашнее задание, достаточно реализовать упрощённый алгоритм планирования с Для того, чтобы зачесть домашнее задание, достаточно реализовать упрощённый алгоритм планирования с
одной глобальной очередью. Функция `ScheduleJob` должна помещать `job` в очередь, или возвращать ссылку на существующий одной глобальной очередью. Функция `ScheduleJob` должна помещать `job` в очередь или возвращать ссылку на существующий
`pendingJob`. Функция `PickJob` должна извлекать первый элемент из очереди. Обратите внимание, что функция `PickJob` `pendingJob`. Функция `PickJob` должна извлекать первый элемент из очереди. Обратите внимание, что функция `PickJob`
принимает контекст. Поскольку это блокирующая операция, она должна поддерживать отмены. Если вы забудете принимает контекст. Поскольку это блокирующая операция, она должна поддерживать отмены. Если вы забудете
реализовать отмену в этом месте, интеграционные тесты будут зависать. реализовать отмену в этом месте, интеграционные тесты будут зависать.
@ -30,7 +30,7 @@
1. Одна глобальная очередь. 1. Одна глобальная очередь.
2. По две локальные очереди на воркер. 2. По две локальные очереди на воркер.
При запросе нового джоба воркер выбирает случайную джобу из трех очередей - глобальной, и двух локальных относящихся При запросе нового джоба воркер выбирает случайную джобу из трех очередей - глобальной и двух локальных, относящихся
к этому воркеру. Случайная очередь выбирается одним вызовом `select {}`. к этому воркеру. Случайная очередь выбирается одним вызовом `select {}`.
Ожидающий исполнения джоб всегда находится в первой локальной очереди воркеров, на которых есть Ожидающий исполнения джоб всегда находится в первой локальной очереди воркеров, на которых есть
@ -41,7 +41,7 @@
из множества зависимостей этого джоба. из множества зависимостей этого джоба.
Определения первой и второй локальной очереди не зависят от того, в каком порядке в шедулер пришли джобы Определения первой и второй локальной очереди не зависят от того, в каком порядке в шедулер пришли джобы
и информация о кеше артефактов. То есть, если джоб уже находится в глобальной очереди, и в этот момент приходит и информация о кеше артефактов. То есть, если джоб уже находится в глобальной очереди и в этот момент приходит
информация, что этот джоб находится в кеше на воркере `W0`, то джоб должен быть добавлен информация, что этот джоб находится в кеше на воркере `W0`, то джоб должен быть добавлен
в первую локальную очередь `W0`. в первую локальную очередь `W0`.
@ -51,5 +51,5 @@
Вместо реального времени, юниттесты шедулера используют библиотеку `clockwork`. Это накладывает ограничения Вместо реального времени, юниттесты шедулера используют библиотеку `clockwork`. Это накладывает ограничения
на детали вашей реализации. Ожидание `CacheTimeout` и `DepTimeout` должно быть реализовано как `select {}` на на детали вашей реализации. Ожидание `CacheTimeout` и `DepTimeout` должно быть реализовано как `select {}` на
канале, который вернула функция `TimeAfter`. Мы считаем что `CacheTimeout < DepTimeout`, и ожидание этих канале, который вернула функция `TimeAfter`. Мы считаем, что `CacheTimeout < DepTimeout`, и ожидание этих
таймаутов происходит последовательно в одной горутине. таймаутов происходит последовательно в одной горутине.

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package scheduler package scheduler

View file

@ -1,6 +1,6 @@
# tarstream # tarstream
Вам нужно уметь передавать директорию с артефактами между воркерами. Для этого, вам нужно Вам нужно уметь передавать директорию с артефактами между воркерами. Для этого вам нужно
реализовать две операции: реализовать две операции:
```go ```go

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package tarstream package tarstream

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package worker package worker

View file

@ -9,8 +9,6 @@
go test -v ./dockertest/... -count=1 go test -v ./dockertest/... -count=1
``` ```
Только **после того, как тесты пройдут локально** можете запушить решение в систему.
### С чего начать? ### С чего начать?
<details> <details>
@ -64,7 +62,7 @@ docker-compose up
Поискать решение проблемы в интернете. Поискать решение проблемы в интернете.
Если решение найдено, и проблема выглядит общей, сделать merge request с улучшением README. Если решение найдено и проблема выглядит общей, сделать merge request с улучшением README.
Если интернет не помог, спросить в чате. Если интернет не помог, спросить в чате.
@ -82,7 +80,7 @@ docker-compose down
### Docker cheat sheet ### Docker cheat sheet
Получить список образов Получить список образов:
``` ```
docker images docker images
``` ```

View file

@ -10,7 +10,7 @@
студентам, но будут запускаться в момент проверки решения в студентам, но будут запускаться в момент проверки решения в
тестирующей системе. тестирующей системе.
- При посылке решения, на сервер отправляются все файлы внутри пакета. - При посылке решения, на сервер отправляются все файлы внутри пакета.
- При тестировании, используются изменённые файлы пакета, и - При тестировании, используются изменённые файлы пакета и
оригинальные файлы тестов. оригинальные файлы тестов.
- Файл пакета можно защитить от изменения, добавив `//go:build !change` в начало файла. - Файл пакета можно защитить от изменения, добавив `//go:build !change` в начало файла.
В этом случае при тестировании посылки всегда будет использоваться оригинальная версия файла. В этом случае при тестировании посылки всегда будет использоваться оригинальная версия файла.

View file

@ -1,6 +1,6 @@
# dupcall # dupcall
В этом задании нужно реализовать свою вариацию на тему singleflight. В этом задании нужно реализовать свою вариацию на тему singleflight. Использовать в реализации singleflight нельзя.
Объект `dupcall.Call` должен дедуплицировать вызовы дорогой функции, правильно обрабатывая отмену контекста. Объект `dupcall.Call` должен дедуплицировать вызовы дорогой функции, правильно обрабатывая отмену контекста.

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package dupcall package dupcall

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package externalsort package externalsort

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package externalsort package externalsort

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package main package main

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package main package main

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package fileleak package fileleak

View file

@ -2,7 +2,7 @@
В этой задаче нужно написать примитивный файрвол. В этой задаче нужно написать примитивный файрвол.
Файрвол - это прокси сервер, пропускающий через себя все запросы, Файрвол - это прокси сервер, пропускающий через себя все запросы
и отвергающий некоторые из них по заданному набору правил. и отвергающий некоторые из них по заданному набору правил.
Пример правил можно посмотреть в [example.yaml](./configs/example.yaml). Пример правил можно посмотреть в [example.yaml](./configs/example.yaml).

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package main package main

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package main package main

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package main package main

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package main package main

View file

@ -2,6 +2,27 @@
В этом задании нужно реализовать консольную утилиту для подсчёта статистик авторов git репозитория. В этом задании нужно реализовать консольную утилиту для подсчёта статистик авторов git репозитория.
```
✗ gitfame --repository=. --extensions='.go,.md' --order-by=lines
Name Lines Commits Files
Joe Tsai 12154 92 49
colinnewell 130 1 1
Roger Peppe 59 1 2
A. Ishikawa 36 1 1
Tobias Klauser 33 1 2
178inaba 11 2 4
Kyle Lemons 11 1 1
Dmitri Shuralyov 8 1 2
ferhat elmas 7 1 4
Christian Muehlhaeuser 6 3 4
k.nakada 5 1 3
LMMilewski 5 1 2
Ernest Galbrun 3 1 1
Ross Light 2 1 1
Chris Morrow 1 1 1
Fiisio 1 1 1
```
### Статистики ### Статистики
* Количество строк * Количество строк
@ -10,6 +31,15 @@
Все статистики считаются для состояния репозитория на момент конкретного коммита. Все статистики считаются для состояния репозитория на момент конкретного коммита.
### Интерфейс
Утилита должна печатать результат в stdout.
При использовании невалидного значения флага или любой другой ошибке программа должна завершаться с ненулевым кодом возврата.
Расчёт может занимать довольно длительное время.
Хорошим качеством подобной утилиты является отображение прогресса (вынесенное за флаг).
Прогресс можно печатать в stderr в произвольном формате.
#### Расчёт #### Расчёт
Каждой строке интересующего подмножества файлов репозитория сопоставляется последний коммит, модифицировавший эту строку. Каждой строке интересующего подмножества файлов репозитория сопоставляется последний коммит, модифицировавший эту строку.
@ -30,7 +60,7 @@ f4640df4 (Fedor Korotkiy 2020-02-26 20:28:52 +0000 5) Задача считае
``` ```
`git blame` с флагом `--porcelain` (см. `git blame --help`) возвращает информацию в машиночитаемом формате. `git blame` с флагом `--porcelain` (см. `git blame --help`) возвращает информацию в машиночитаемом формате.
Кроме того, этот формат схлопывает соседние строки относящиеся к одному коммиту, Кроме того, этот формат схлопывает соседние строки, относящиеся к одному коммиту,
что может сильно сократить размер результата. Поэтому использовать нужно его. что может сильно сократить размер результата. Поэтому использовать нужно его.
Стоит помнить, что не все файлы из директории git проекта обязательно принадлежат git репозиторию. Стоит помнить, что не все файлы из директории git проекта обязательно принадлежат git репозиторию.
@ -41,15 +71,6 @@ f4640df4 (Fedor Korotkiy 2020-02-26 20:28:52 +0000 5) Задача считае
В процессе работы скрипт не должен менять состояние репозитория ни в какой момент, В процессе работы скрипт не должен менять состояние репозитория ни в какой момент,
поскольку с репозиторием могут параллельно работать. поскольку с репозиторием могут параллельно работать.
### Интерфейс
Утилита должна печатать результат в stdout.
При использовании невалидного значения флага или любой другой ошибке программа должна завершаться с ненулевым кодом возврата.
Расчёт может занимать довольно длительное время.
Хорошим качеством подобной утилиты является отображение прогресса (вынесенное за флаг).
Прогресс можно печатать в stderr в произвольном формате.
### Флаги ### Флаги
Утилита должна поддерживать следующий набор флагов: Утилита должна поддерживать следующий набор флагов:
@ -58,7 +79,7 @@ f4640df4 (Fedor Korotkiy 2020-02-26 20:28:52 +0000 5) Задача считае
**--revision** — указатель на коммит; HEAD по умолчанию **--revision** — указатель на коммит; HEAD по умолчанию
**--order-by** — ключ сортировки результатов, один из `lines` (дефолт), `commits`, `files`. **--order-by** — ключ сортировки результатов; один из `lines` (дефолт), `commits`, `files`.
По умолчанию результаты сортируются по убыванию ключа `(lines, commits, files)`. По умолчанию результаты сортируются по убыванию ключа `(lines, commits, files)`.
При равенстве ключей выше будет автор с лексикографически меньшим именем. При равенстве ключей выше будет автор с лексикографически меньшим именем.
@ -116,7 +137,7 @@ ferhat elmas,1,1,1
### Тесты ### Тесты
Команда для запуска тестов Команда для запуска тестов:
``` ```
go test -v ./gitfame/test/integration/... go test -v ./gitfame/test/integration/...
``` ```
@ -124,7 +145,7 @@ go test -v ./gitfame/test/integration/...
В [/tests/integration/testdata/bundles](test/integration/testdata/bundles) лежат запакованные git репозитории. В [/tests/integration/testdata/bundles](test/integration/testdata/bundles) лежат запакованные git репозитории.
Каждый интеграционный тест ссылается на какой-нибудь бандл. Каждый интеграционный тест ссылается на какой-нибудь бандл.
Как создать bundle? Находясь в git репозитории выполнить Как создать свой bundle? Находясь в git репозитории выполнить
``` ```
git bundle create my.bundle --all git bundle create my.bundle --all
``` ```
@ -164,6 +185,7 @@ export PATH=$GOPATH/bin:$PATH
В небольших проектах нет ничего плохого в том, чтобы весь код лежал плоско в корне. В небольших проектах нет ничего плохого в том, чтобы весь код лежал плоско в корне.
Здесь же, для ознакомления предлагаем изучить общепринятый подход. Здесь же, для ознакомления предлагаем изучить общепринятый подход.
В частности, писать реализацию в internal или pkg (в чём разница?).
#### Cli #### Cli
@ -174,18 +196,7 @@ export PATH=$GOPATH/bin:$PATH
В cobra используется библиотека [pflag](https://pkg.go.dev/github.com/spf13/pflag) для работы с флагами. В cobra используется библиотека [pflag](https://pkg.go.dev/github.com/spf13/pflag) для работы с флагами.
Библиотеку можно использовать и отдельно от cobra. Библиотеку можно использовать и отдельно от cobra.
`pflag` может побольше, чем стандартный [flag](https://golang.org/pkg/flag/), `pflag` может побольше, чем стандартный [flag](https://golang.org/pkg/flag/),
в частости, в `pflag` есть полезные для решаемой задачи флаги для работы с аргументами-массивами. в частности, в `pflag` есть полезные для решаемой задачи флаги для работы с аргументами-массивами.
Помимо библиотеки, в cobra есть ещё и бинарь (с именем cobra) для кодогенерации основы проекта.
Его можно установить в `GOPATH` командой
```
go get -u github.com/spf13/cobra
```
Генерируемый sample проект использует [viper](https://pkg.go.dev/github.com/spf13/viper) (библиотеку для работы с конфигами) и
[go-homedir](https://pkg.go.dev/github.com/mitchellh/go-homedir) (кроссплатформенную библиотеку для поиска домашней директории пользователя).
В этой задаче эти библиотеки не нужны, поэтому их нет в зависимостях проекта.
### Git ликбез ### Git ликбез
@ -207,7 +218,7 @@ go get -u github.com/spf13/cobra
**branch** (ветка) — это не объект `Git Object Database`, а всего лишь файл в директории `.git/refs/heads/` с хэшом последнего для этой ветки коммита. **branch** (ветка) — это не объект `Git Object Database`, а всего лишь файл в директории `.git/refs/heads/` с хэшом последнего для этой ветки коммита.
То есть ветка — это указатель на коммит. То есть ветка — это указатель на коммит.
**head** — это ссылка на коммит. В каждом репозитории по умолчанию есть **head** именем **master**. **head** — это ссылка на коммит. В каждом репозитории по умолчанию есть **head** с именем **master**.
**HEAD** — один выделенный **head**. Файл `.git/HEAD`. Родитель следующего коммита. **HEAD** — один выделенный **head**. Файл `.git/HEAD`. Родитель следующего коммита.

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package main package main

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package hogwarts package hogwarts

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package hotelbusiness package hotelbusiness

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package illegal package illegal

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package internal package internal

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package illegal package illegal

View file

@ -2,10 +2,10 @@
Обычные функции `json.Marshal` и `json.Unmarshal` работают с одним `json` объектом. Обычные функции `json.Marshal` и `json.Unmarshal` работают с одним `json` объектом.
Иногда возникает ситуация, что вместо одного объекта, вам нужно передать последовательность объектов Иногда возникает ситуация, что вместо одного объекта, вам нужно передать последовательность объектов,
разделённых пробельными символами. Например: `{"A": 1} {"B": 2} {"C": 3}`. Такую последовательность разделённых пробельными символами. Например: `{"A": 1} {"B": 2} {"C": 3}`. Такую последовательность
можно прочитать используя `json.Decoder`, и можно записать используя `json.Encoder`. можно прочитать используя `json.Decoder`, и можно записать используя `json.Encoder`.
Реализуйте функции `Marshal` и `Unmarshal`, которые работают со слайсом значений, и Реализуйте функции `Marshal` и `Unmarshal`, которые работают со слайсом значений и
совершают подобное преобразование. По аналогии с пакетом `json`, функция `Marshal` принимает совершают подобное преобразование. По аналогии с пакетом `json`, функция `Marshal` принимает
вторым аргументом слайс, а функция `Unmarshal` - указатель на слайс. вторым аргументом слайс, а функция `Unmarshal` - указатель на слайс.

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package jsonlist package jsonlist

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package jsonrpc package jsonrpc

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package keylock package keylock

View file

@ -70,7 +70,7 @@ func BenchmarkKeyLock_NoBusyWait(b *testing.B) {
cancel := make(chan struct{}) cancel := make(chan struct{})
defer close(cancel) defer close(cancel)
for i := 0; i < 10000; i++ { for i := 0; i < 1000; i++ {
go func() { go func() {
l.LockKeys(lockedKey, cancel) l.LockKeys(lockedKey, cancel)
}() }()

View file

@ -242,10 +242,10 @@
Кодировка utf8 Кодировка utf8
0xxxxxx runes 0127 0xxxxxxx runes 0127
11xxxxx 10xxxxxx 1282047 110xxxxx 10xxxxxx 1282047
110xxxx 10xxxxxx 10xxxxxx 204865535 1110xxxx 10xxxxxx 10xxxxxx 204865535
1110xxx 10xxxxxx 10xxxxxx 10xxxxxx 655360x10ffff 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 655360x10ffff
Разница между рунами и байтами Разница между рунами и байтами

View file

@ -17,7 +17,7 @@ Concurrency with Shared Memory
a := 1 a := 1
a += 1 a += 1
- Посылка значения из канал _happens_before_ получения этого значения из канала. - Посылка значения из канала _happens_before_ получения этого значения из канала.
- Два события A и B _происходят_одновременно_ (_are_concurrent_), если нельзя сказать что одно случилось раньше другого. - Два события A и B _происходят_одновременно_ (_are_concurrent_), если нельзя сказать что одно случилось раньше другого.
@ -312,18 +312,19 @@ Concurrency with Shared Memory
func (once *Once) Do(f func()) { func (once *Once) Do(f func()) {
once.mu.Lock() once.mu.Lock()
defer once.mu.Unlock() defer once.mu.Unlock()
if done { if once.done {
return return
} }
if running { if once.running {
once.cond.Wait() // releases and acquires mutex once.cond.Wait() // releases and acquires mutex
return return
} }
running = true
once.running = true
once.mu.Unlock() once.mu.Unlock()
f() f()
once.mu.Lock() once.mu.Lock()
done = true once.done = true
once.cond.Broadcast() once.cond.Broadcast()
} }

View file

@ -1,7 +1,10 @@
package hasql package hasql
import ( import (
"context"
"database/sql"
"log" "log"
"time"
_ "github.com/jackc/pgx/v4/stdlib" _ "github.com/jackc/pgx/v4/stdlib"
"golang.yandex/hasql" "golang.yandex/hasql"
@ -24,7 +27,7 @@ func Open() {
log.Fatal(err) log.Fatal(err)
} }
log.Println("Node address", node.Addr) log.Println("Node address", node.Addr())
ctx, cancel := context.WithTimeout(context.Background(), time.Second) ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel() defer cancel()

View file

@ -7,7 +7,7 @@ import (
) )
func Results(ctx context.Context, db *sql.DB) { func Results(ctx context.Context, db *sql.DB) {
rows, err := db.QueryContext(ctx, "SELECT id, name FROM users WHERE id = $1", 1) rows, err := db.QueryContext(ctx, "SELECT name FROM users WHERE id = $1", 1)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View file

@ -1,6 +1,7 @@
package redis package redis
import ( import (
"context"
"log" "log"
"time" "time"

19
ledger/README.md Normal file
View file

@ -0,0 +1,19 @@
# ledger
Реализуйте объект для хранения банковских счетов. Требуемый интерфейс находится в `model.go`.
- Функция `New` должна создавать таблицу в базе данных.
- Метод `CreateAccount` должен создавать новый счёт с заданным `id`.
- Метод `GetBalance` должен возвращать текущий баланс.
- Метод `Deposit` должен зачислять деньги на счёт.
- Метод `Withdraw` должен снимать деньги со счёта.
Если на счету недостаточно денег, метод должен возвращать ошибку `ledger.ErrNoMoney`.
- Метод `Transfer` должен переводить деньги со счёта `from` на счёт `to`.
Если на счету `from` недостаточно денег, метод должен возвращать ошибку `ledger.ErrNoMoney`.
Все операции должны быть атомарными. Для реализации некоторых методов
вам потребуется использовать транзакции и row-level локи. Ваша реализация не должна создавать дедлоки на уровне базы данных.
Мы рекомендуем использовать функциональность `SELECT FOR UPDATE`.
Комментарии по запуску postgres смотрите в задаче [dao](../dao/).

View file

@ -0,0 +1,10 @@
version: '3'
services:
pg:
image: postgres:latest
environment:
POSTGRES_DB: shad-go
POSTGRES_USER: gopher
POSTGRES_PASSWORD: pass
ports:
- 5432:5432

9
ledger/ledger.go Normal file
View file

@ -0,0 +1,9 @@
//go:build !solution
package ledger
import "context"
func New(ctx context.Context, dsn string) (Ledger, error) {
panic("not implemented")
}

135
ledger/ledger_test.go Normal file
View file

@ -0,0 +1,135 @@
package ledger_test
import (
"context"
"errors"
"fmt"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"gitlab.com/slon/shad-go/ledger"
"gitlab.com/slon/shad-go/pgfixture"
)
func TestLedger(t *testing.T) {
dsn := pgfixture.Start(t)
ctx := context.Background()
l0, err := ledger.New(ctx, dsn)
require.NoError(t, err)
t.Run("SimpleCommands", func(t *testing.T) {
checkBalance := func(account ledger.ID, amount ledger.Money) {
b, err := l0.GetBalance(ctx, account)
require.NoError(t, err)
require.Equal(t, amount, b)
}
require.NoError(t, l0.CreateAccount(ctx, "a0"))
checkBalance("a0", 0)
require.Error(t, l0.CreateAccount(ctx, "a0"))
require.NoError(t, l0.Deposit(ctx, "a0", ledger.Money(100)))
checkBalance("a0", 100)
require.NoError(t, l0.Withdraw(ctx, "a0", ledger.Money(50)))
checkBalance("a0", 50)
require.ErrorIs(t, l0.Withdraw(ctx, "a0", ledger.Money(100)), ledger.ErrNoMoney)
require.NoError(t, l0.CreateAccount(ctx, "a1"))
require.NoError(t, l0.Transfer(ctx, "a0", "a1", ledger.Money(40)))
checkBalance("a0", 10)
checkBalance("a1", 40)
require.ErrorIs(t, l0.Transfer(ctx, "a0", "a1", ledger.Money(50)), ledger.ErrNoMoney)
})
t.Run("Transactions", func(t *testing.T) {
const nAccounts = 10
const initialBalance = 5
var accounts []ledger.ID
for i := 0; i < nAccounts; i++ {
id := ledger.ID(fmt.Sprint(i))
accounts = append(accounts, id)
require.NoError(t, l0.CreateAccount(ctx, id))
require.NoError(t, l0.Deposit(ctx, id, initialBalance))
}
var wg sync.WaitGroup
done := make(chan struct{})
spawn := func(action func() error) {
wg.Add(1)
go func() {
defer wg.Done()
for {
select {
case <-done:
return
default:
if err := action(); err != nil {
if !errors.Is(err, ledger.ErrNoMoney) {
t.Errorf("operation failed: %v", err)
return
}
}
}
}
}()
}
for i := 0; i < nAccounts; i++ {
i := i
account := accounts[i]
next := accounts[(i+1)%len(accounts)]
prev := accounts[(i+len(accounts)-1)%len(accounts)]
spawn(func() error {
balance, err := l0.GetBalance(ctx, account)
if err != nil {
return err
}
if balance < 0 {
return fmt.Errorf("%q balance is negative", account)
}
return nil
})
spawn(func() error {
return l0.Transfer(ctx, account, next, 1)
})
spawn(func() error {
return l0.Transfer(ctx, account, prev, 1)
})
}
time.Sleep(time.Second * 10)
close(done)
wg.Wait()
var total ledger.Money
for i := 0; i < nAccounts; i++ {
amount, err := l0.GetBalance(ctx, accounts[i])
require.NoError(t, err)
total += amount
}
require.Equal(t, ledger.Money(initialBalance*nAccounts), total)
})
}

21
ledger/model.go Normal file
View file

@ -0,0 +1,21 @@
package ledger
import (
"context"
"errors"
)
type (
ID string
Money int64
)
var ErrNoMoney = errors.New("no money")
type Ledger interface {
CreateAccount(ctx context.Context, id ID) error
GetBalance(ctx context.Context, id ID) (Money, error)
Deposit(ctx context.Context, id ID, amount Money) error
Withdraw(ctx context.Context, id ID, amount Money) error
Transfer(ctx context.Context, from, to ID, amount Money) error
}

View file

@ -1,5 +1,4 @@
//go:build !change //go:build !change
// +build !change
package lrucache package lrucache

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package lrucache package lrucache

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package main package main

View file

@ -6,7 +6,7 @@
### Что нужно сделать? ### Что нужно сделать?
Нужно написать реализацию Once используя каналы. Нужно написать реализацию Once, используя каналы.
Использование пакета [sync](https://golang.org/pkg/sync) в этой задаче запрещено! Использование пакета [sync](https://golang.org/pkg/sync) в этой задаче запрещено!

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package once package once

View file

@ -1,5 +1,4 @@
//go:build !solution //go:build !solution
// +build !solution
package otp package otp

View file

@ -91,7 +91,7 @@ func TestReader(t *testing.T) {
r := NewReader(testCase.r, testCase.prng) r := NewReader(testCase.r, testCase.prng)
buf, err := ioutil.ReadAll(r) buf, err := ioutil.ReadAll(r)
require.Equal(t, testCase.err, err) require.ErrorIs(t, err, testCase.err)
require.Equal(t, testCase.result, buf) require.Equal(t, testCase.result, buf)
}) })
} }
@ -139,7 +139,7 @@ func TestWriterError(t *testing.T) {
n, err := w.Write(plaintext) n, err := w.Write(plaintext)
require.Equalf(t, plaintextBackup, plaintext, "Write must not modify the slice data, even temporarily.") require.Equalf(t, plaintextBackup, plaintext, "Write must not modify the slice data, even temporarily.")
require.Equal(t, err, iotest.ErrTimeout) require.ErrorIs(t, err, iotest.ErrTimeout)
require.Equal(t, 512, n) require.Equal(t, 512, n)
require.Equal(t, out.buf.Bytes(), ciphertext[:512]) require.Equal(t, out.buf.Bytes(), ciphertext[:512])
} }

Some files were not shown because too many files have changed in this diff Show more