Merge branch 'master' of gitlab.com:slon/shad-go-private into treeiter
This commit is contained in:
commit
6613189930
157 changed files with 1264 additions and 198 deletions
|
@ -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
|
||||
start: 17-02-2022 18:00
|
||||
deadline: 27-02-2022 23:59
|
||||
deadline: 06-03-2022 23:59
|
||||
tasks:
|
||||
- task: hotelbusiness
|
||||
score: 100
|
||||
|
|
|
@ -43,6 +43,13 @@ linters-settings:
|
|||
stylecheck:
|
||||
# https://staticcheck.io/docs/options#checks
|
||||
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:
|
||||
disable-all: true
|
||||
|
|
|
@ -11,8 +11,8 @@ check:
|
|||
- golangci-lint run --build-tags private,solution ./...
|
||||
#- go test -v -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 ./...
|
||||
- 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 | 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) -tags private,solution -v -race ./...
|
||||
|
||||
rebuild-base-image:
|
||||
tags:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Курс по Го в ШАД
|
||||
|
||||
Для работы с кодом нужен go 1.17 или выше.
|
||||
Для работы с кодом нужен go 1.18 или выше.
|
||||
|
||||
```sh
|
||||
git clone https://gitlab.com/slon/shad-go.git
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
`Counter` используется для нахождения уникальных слов и подсчета вхождений каждого из них.
|
||||
Его интерфейс выглядит так:
|
||||
|
||||
* `Count(r io.Reader) error` — функция, которая подсчитывает количество вхождений для каждого слова в тексте.
|
||||
* `Count(r io.Reader) error` — функция, которая подсчитывает количество вхождений каждого слова в тексте.
|
||||
На вход подается io.Reader, в котором находится некоторый текст.
|
||||
Разделителями являются только переносы строк и пробелы.
|
||||
* `String() string` — преобразует мапу вида `{"слово": "количество вхождений"}` в форматированную строку.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package allocs
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution && !change
|
||||
// +build !solution,!change
|
||||
|
||||
package allocs
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package batcher
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build race
|
||||
// +build race
|
||||
|
||||
package batcher
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package slow
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package blowfish
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
FROM golang:1.17
|
||||
FROM golang:1.18
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
rsync libssl-dev \
|
||||
rsync libssl-dev postgresql sudo redis-server \
|
||||
&& 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 chmod +x /usr/local/bin/docker-compose
|
||||
RUN curl -fsSL https://get.docker.com | sh
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
## ciletters
|
||||
|
||||
В этой задаче вам предстоит познакомиться со стандартным пакетом [text/template](https://golang.org/pkg/text/template/),
|
||||
позволяющим генерировать текст в определенном формате.
|
||||
позволяющим генерировать текст в определенном формате, а также с пакетом [embed](https://pkg.go.dev/embed),
|
||||
позволяющим вкомпиливать в исполняемый файл строковые литералы.
|
||||
|
||||
### Легенда
|
||||
|
||||
|
@ -20,6 +21,8 @@
|
|||
Нужно реализовать функцию `MakeLetter` из файла [letter.go](./letter.go),
|
||||
которая по go объекту нотификации генерирует её текстовое представление.
|
||||
|
||||
Для этого нужно написать `text/template` шаблон, сохранить его в отдельный файл, а затем получить его содержимое в коде с помомщью `go:embed`.
|
||||
|
||||
#### Прокомментированный пример из теста
|
||||
```
|
||||
Your pipeline #194613 has failed! // 194613 -- это ID pipeline'а
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package ciletters
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package ciletters
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
### Что нужно сделать?
|
||||
|
||||
Нужно написать реализацию Cond используя каналы.
|
||||
Нужно написать реализацию Cond, используя каналы.
|
||||
|
||||
Использование пакета [sync](https://golang.org/pkg/sync) в этой задаче запрещено!
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package cond
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ func TestCondBroadcast(t *testing.T) {
|
|||
|
||||
// nolint
|
||||
func TestCondSignalStealing(t *testing.T) {
|
||||
for iters := 0; iters < 1000; iters++ {
|
||||
for iters := 0; iters < 20000; iters++ {
|
||||
var m sync.Mutex
|
||||
cond := New(&m)
|
||||
|
||||
|
|
|
@ -16,12 +16,18 @@ Package main можно не тестировать.
|
|||
Coverage 100% говорит ровно о том, что все строки кода выполнялись.
|
||||
Хорошие тесты, в первую очередь, тестируют функциональность.
|
||||
|
||||
Как посмотреть coverage:
|
||||
Как посмотреть общий coverage:
|
||||
```
|
||||
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:
|
||||
```
|
||||
✗ curl -i -X GET localhost:6029/
|
||||
✗ curl -i -X GET localhost:6029/
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 19 Mar 2020 21:46:02 GMT
|
||||
|
@ -66,7 +72,7 @@ Content-Length: 51
|
|||
|
||||
Получить todo по id:
|
||||
```
|
||||
✗ curl -i localhost:6029/todo/0
|
||||
✗ curl -i localhost:6029/todo/0
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 19 Mar 2020 21:44:17 GMT
|
||||
|
@ -77,7 +83,7 @@ Content-Length: 51
|
|||
|
||||
Получить все todo:
|
||||
```
|
||||
✗ curl -i -X GET localhost:6029/todo
|
||||
✗ curl -i -X GET localhost:6029/todo
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/json
|
||||
Date: Thu, 19 Mar 2020 21:44:37 GMT
|
||||
|
@ -85,3 +91,19 @@ Content-Length: 53
|
|||
|
||||
[{"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}]%
|
||||
```
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package app
|
||||
|
||||
|
@ -9,7 +8,6 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
|
@ -34,10 +32,11 @@ func (app *App) Start(port int) {
|
|||
|
||||
func (app *App) initRoutes() {
|
||||
app.router = mux.NewRouter()
|
||||
app.router.HandleFunc("/", app.status).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/create", app.addTodo).Methods("Post")
|
||||
app.router.HandleFunc("/", app.status).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]+}/finish", app.finishTodo).Methods("POST")
|
||||
app.router.HandleFunc("/todo/create", app.addTodo).Methods("POST")
|
||||
}
|
||||
|
||||
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) {
|
||||
id, err := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/todo/"))
|
||||
id, err := strconv.Atoi(mux.Vars(r)["id"])
|
||||
if err != nil {
|
||||
utils.BadRequest(w, "ID must be an int")
|
||||
return
|
||||
|
@ -95,6 +94,21 @@ func (app *App) getTodo(w http.ResponseWriter, r *http.Request) {
|
|||
_ = 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) {
|
||||
_ = utils.RespondJSON(w, http.StatusOK, "API is up and working!")
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package app
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package client
|
||||
|
||||
|
@ -69,3 +68,19 @@ func (c *Client) List() ([]*models.Todo, error) {
|
|||
err = json.NewDecoder(resp.Body).Decode(&todos)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package models
|
||||
|
||||
|
@ -12,6 +11,7 @@ type Storage interface {
|
|||
AddTodo(string, string) (*Todo, error)
|
||||
GetTodo(ID) (*Todo, error)
|
||||
GetAll() ([]*Todo, error)
|
||||
FinishTodo(ID) error
|
||||
}
|
||||
|
||||
type InMemoryStorage struct {
|
||||
|
@ -69,3 +69,16 @@ func (s *InMemoryStorage) GetAll() ([]*Todo, error) {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package models
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package utils
|
||||
|
||||
|
|
47
dao/README.md
Normal file
47
dao/README.md
Normal 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
9
dao/dao.go
Normal 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
55
dao/dao_test.go
Normal 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
10
dao/docker-compose.yaml
Normal 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
22
dao/model.go
Normal 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)
|
||||
}
|
||||
)
|
|
@ -94,7 +94,7 @@ go install ./digitalclock/...
|
|||
3. форматирование времени: https://gobyexample.com/time-formatting-parsing
|
||||
4. работа с картинками: https://golang.org/pkg/image/
|
||||
|
||||
Пример, создания простой png размера 5x5:
|
||||
Пример создания простой png размера 5x5:
|
||||
```go
|
||||
package main
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -66,8 +66,8 @@ type Job struct {
|
|||
## Архитектура системы
|
||||
|
||||
Наша система будет состоять из трех компонент.
|
||||
* Клиент - процесс запускающий сборку.
|
||||
* Воркер - процесс запускающий команды компиляции и тестирования.
|
||||
* Клиент - процесс, запускающий сборку.
|
||||
* Воркер - процесс, запускающий команды компиляции и тестирования.
|
||||
* Координатор - центральный процесс в системе, общается с клиентами и воркерами. Раздаёт задачи
|
||||
воркерам.
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ Client и Coordinator общаются через два вызова.
|
|||
|
||||
- `POST /build` - стартует новый билд.
|
||||
* Client посылает в Body запроса json c описанием сборки.
|
||||
* Coordinator стримит в body ответа json сообщения описывающие прогресс сборки.
|
||||
* Coordinator стримит в body ответа json сообщения, описывающие прогресс сборки.
|
||||
* Первым сообщением в ответе Coordinator присылает `buildID`.
|
||||
* _Тут можно было бы использовать websocket, но нас устраивает более простое решение._
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package api
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package api
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package api
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package api
|
||||
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
|
||||
`commit` помещает артефакт в кеш. `abort` отменяет запись артефакта, удаляя все данные.
|
||||
|
||||
После первого вызова `commit`, все последующие вызовы `commit` и `abort` должны ничего не делать.
|
||||
После первого вызова `commit` все последующие вызовы `commit` и `abort` должны ничего не делать.
|
||||
|
||||
Точно так же, после вызова `abort`, все последующие вызовы `commit` и `abort` должны ничего не делать.
|
||||
Точно так же, после вызова `abort` все последующие вызовы `commit` и `abort` должны ничего не делать.
|
||||
|
||||
Горутина может начать читать артефакт, позвав метод `Get`. Много горутин могут читать артефакт одновременно.
|
||||
Горутина должна позвать `unlock`, после того как она закончила работать с артефактом.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package artifact
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package artifact
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package artifact
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# build
|
||||
|
||||
Пакет `build` содержит описание графа сборки и набор хелпер-функций для работы с графом. Вам не нужно
|
||||
писать новый код в этом пакете, но нужно научиться пользоваться тем кодом который вам дан.
|
||||
писать новый код в этом пакете, но нужно научиться пользоваться тем кодом, который вам дан.
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
# client
|
||||
|
||||
Пакет `client` реализует клиента системы распределённой сборки. Клиент запускается локально, и имеет доступ к
|
||||
Пакет `client` реализует клиента системы распределённой сборки. Клиент запускается локально и имеет доступ к
|
||||
директории с исходным кодом.
|
||||
|
||||
Клиент получает на вход `build.Graph` и запускает сборку на координаторе.
|
||||
|
||||
После того, как координатор создал новую сборку, клиент заливает недостающие файлы и посылает сигнал о завершении стадии заливки.
|
||||
|
||||
После этого, клиент следит за прогрессом сборки, дожидается завершения и выходит.
|
||||
После этого клиент следит за прогрессом сборки, дожидается завершения и выходит.
|
||||
|
||||
Клиент тестируется интеграционными тестами из пакета `disttest`.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package client
|
||||
|
||||
|
|
1
distbuild/pkg/dist/coordinator.go
vendored
1
distbuild/pkg/dist/coordinator.go
vendored
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package dist
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
- Вызов `GET /file?id=123` должен возвращать содержимое файла с `id=123`.
|
||||
- Вызов `PUT /file?id=123` должен заливать содержимое файла с `id=123`.
|
||||
|
||||
**Обратите внимание:** Несколько клиентов могут начать заливать в кеш один и тот же набор файлов. В наивной реализации,
|
||||
**Обратите внимание:** Несколько клиентов могут начать заливать в кеш один и тот же набор файлов. В наивной реализации
|
||||
первый клиент залочит файл на запись, а следующие упадут с ошибкой. Ваш код должен обрабатывать эту ситуацию корректно,
|
||||
то есть последующие запросы должны дожидаться, пока первый запрос завершится. Для реализации этой логики
|
||||
поведения вам поможет пакет [singleflight](https://godoc.org/golang.org/x/sync/singleflight).
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package filecache
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package filecache
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package filecache
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
воркерами.
|
||||
|
||||
Для того, чтобы зачесть домашнее задание, достаточно реализовать упрощённый алгоритм планирования с
|
||||
одной глобальной очередью. Функция `ScheduleJob` должна помещать `job` в очередь, или возвращать ссылку на существующий
|
||||
одной глобальной очередью. Функция `ScheduleJob` должна помещать `job` в очередь или возвращать ссылку на существующий
|
||||
`pendingJob`. Функция `PickJob` должна извлекать первый элемент из очереди. Обратите внимание, что функция `PickJob`
|
||||
принимает контекст. Поскольку это блокирующая операция, она должна поддерживать отмены. Если вы забудете
|
||||
реализовать отмену в этом месте, интеграционные тесты будут зависать.
|
||||
|
@ -30,7 +30,7 @@
|
|||
1. Одна глобальная очередь.
|
||||
2. По две локальные очереди на воркер.
|
||||
|
||||
При запросе нового джоба воркер выбирает случайную джобу из трех очередей - глобальной, и двух локальных относящихся
|
||||
При запросе нового джоба воркер выбирает случайную джобу из трех очередей - глобальной и двух локальных, относящихся
|
||||
к этому воркеру. Случайная очередь выбирается одним вызовом `select {}`.
|
||||
|
||||
Ожидающий исполнения джоб всегда находится в первой локальной очереди воркеров, на которых есть
|
||||
|
@ -41,7 +41,7 @@
|
|||
из множества зависимостей этого джоба.
|
||||
|
||||
Определения первой и второй локальной очереди не зависят от того, в каком порядке в шедулер пришли джобы
|
||||
и информация о кеше артефактов. То есть, если джоб уже находится в глобальной очереди, и в этот момент приходит
|
||||
и информация о кеше артефактов. То есть, если джоб уже находится в глобальной очереди и в этот момент приходит
|
||||
информация, что этот джоб находится в кеше на воркере `W0`, то джоб должен быть добавлен
|
||||
в первую локальную очередь `W0`.
|
||||
|
||||
|
@ -51,5 +51,5 @@
|
|||
|
||||
Вместо реального времени, юниттесты шедулера используют библиотеку `clockwork`. Это накладывает ограничения
|
||||
на детали вашей реализации. Ожидание `CacheTimeout` и `DepTimeout` должно быть реализовано как `select {}` на
|
||||
канале, который вернула функция `TimeAfter`. Мы считаем что `CacheTimeout < DepTimeout`, и ожидание этих
|
||||
канале, который вернула функция `TimeAfter`. Мы считаем, что `CacheTimeout < DepTimeout`, и ожидание этих
|
||||
таймаутов происходит последовательно в одной горутине.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package scheduler
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# tarstream
|
||||
|
||||
Вам нужно уметь передавать директорию с артефактами между воркерами. Для этого, вам нужно
|
||||
Вам нужно уметь передавать директорию с артефактами между воркерами. Для этого вам нужно
|
||||
реализовать две операции:
|
||||
|
||||
```go
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package tarstream
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package worker
|
||||
|
||||
|
|
|
@ -9,8 +9,6 @@
|
|||
go test -v ./dockertest/... -count=1
|
||||
```
|
||||
|
||||
Только **после того, как тесты пройдут локально** можете запушить решение в систему.
|
||||
|
||||
### С чего начать?
|
||||
|
||||
<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 images
|
||||
```
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
студентам, но будут запускаться в момент проверки решения в
|
||||
тестирующей системе.
|
||||
- При посылке решения, на сервер отправляются все файлы внутри пакета.
|
||||
- При тестировании, используются изменённые файлы пакета, и
|
||||
- При тестировании, используются изменённые файлы пакета и
|
||||
оригинальные файлы тестов.
|
||||
- Файл пакета можно защитить от изменения, добавив `//go:build !change` в начало файла.
|
||||
В этом случае при тестировании посылки всегда будет использоваться оригинальная версия файла.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# dupcall
|
||||
|
||||
В этом задании нужно реализовать свою вариацию на тему singleflight.
|
||||
В этом задании нужно реализовать свою вариацию на тему singleflight. Использовать в реализации singleflight нельзя.
|
||||
|
||||
Объект `dupcall.Call` должен дедуплицировать вызовы дорогой функции, правильно обрабатывая отмену контекста.
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package dupcall
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package externalsort
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package externalsort
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package fileleak
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
В этой задаче нужно написать примитивный файрвол.
|
||||
|
||||
Файрвол - это прокси сервер, пропускающий через себя все запросы,
|
||||
Файрвол - это прокси сервер, пропускающий через себя все запросы
|
||||
и отвергающий некоторые из них по заданному набору правил.
|
||||
|
||||
Пример правил можно посмотреть в [example.yaml](./configs/example.yaml).
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -2,6 +2,27 @@
|
|||
|
||||
В этом задании нужно реализовать консольную утилиту для подсчёта статистик авторов 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 проекта обязательно принадлежат 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 по умолчанию
|
||||
|
||||
**--order-by** — ключ сортировки результатов, один из `lines` (дефолт), `commits`, `files`.
|
||||
**--order-by** — ключ сортировки результатов; один из `lines` (дефолт), `commits`, `files`.
|
||||
|
||||
По умолчанию результаты сортируются по убыванию ключа `(lines, commits, files)`.
|
||||
При равенстве ключей выше будет автор с лексикографически меньшим именем.
|
||||
|
@ -116,7 +137,7 @@ ferhat elmas,1,1,1
|
|||
|
||||
### Тесты
|
||||
|
||||
Команда для запуска тестов
|
||||
Команда для запуска тестов:
|
||||
```
|
||||
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 репозитории.
|
||||
Каждый интеграционный тест ссылается на какой-нибудь бандл.
|
||||
|
||||
Как создать bundle? Находясь в git репозитории выполнить
|
||||
Как создать свой bundle? Находясь в git репозитории выполнить
|
||||
```
|
||||
git bundle create my.bundle --all
|
||||
```
|
||||
|
@ -164,6 +185,7 @@ export PATH=$GOPATH/bin:$PATH
|
|||
|
||||
В небольших проектах нет ничего плохого в том, чтобы весь код лежал плоско в корне.
|
||||
Здесь же, для ознакомления предлагаем изучить общепринятый подход.
|
||||
В частности, писать реализацию в internal или pkg (в чём разница?).
|
||||
|
||||
#### Cli
|
||||
|
||||
|
@ -174,18 +196,7 @@ export PATH=$GOPATH/bin:$PATH
|
|||
В cobra используется библиотека [pflag](https://pkg.go.dev/github.com/spf13/pflag) для работы с флагами.
|
||||
Библиотеку можно использовать и отдельно от cobra.
|
||||
`pflag` может побольше, чем стандартный [flag](https://golang.org/pkg/flag/),
|
||||
в частости, в `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) (кроссплатформенную библиотеку для поиска домашней директории пользователя).
|
||||
В этой задаче эти библиотеки не нужны, поэтому их нет в зависимостях проекта.
|
||||
в частности, в `pflag` есть полезные для решаемой задачи флаги для работы с аргументами-массивами.
|
||||
|
||||
### Git ликбез
|
||||
|
||||
|
@ -207,7 +218,7 @@ go get -u github.com/spf13/cobra
|
|||
**branch** (ветка) — это не объект `Git Object Database`, а всего лишь файл в директории `.git/refs/heads/` с хэшом последнего для этой ветки коммита.
|
||||
То есть ветка — это указатель на коммит.
|
||||
|
||||
**head** — это ссылка на коммит. В каждом репозитории по умолчанию есть **head** именем **master**.
|
||||
**head** — это ссылка на коммит. В каждом репозитории по умолчанию есть **head** с именем **master**.
|
||||
|
||||
**HEAD** — один выделенный **head**. Файл `.git/HEAD`. Родитель следующего коммита.
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package hogwarts
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package hotelbusiness
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package illegal
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package internal
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package illegal
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
Обычные функции `json.Marshal` и `json.Unmarshal` работают с одним `json` объектом.
|
||||
|
||||
Иногда возникает ситуация, что вместо одного объекта, вам нужно передать последовательность объектов
|
||||
Иногда возникает ситуация, что вместо одного объекта, вам нужно передать последовательность объектов,
|
||||
разделённых пробельными символами. Например: `{"A": 1} {"B": 2} {"C": 3}`. Такую последовательность
|
||||
можно прочитать используя `json.Decoder`, и можно записать используя `json.Encoder`.
|
||||
|
||||
Реализуйте функции `Marshal` и `Unmarshal`, которые работают со слайсом значений, и
|
||||
Реализуйте функции `Marshal` и `Unmarshal`, которые работают со слайсом значений и
|
||||
совершают подобное преобразование. По аналогии с пакетом `json`, функция `Marshal` принимает
|
||||
вторым аргументом слайс, а функция `Unmarshal` - указатель на слайс.
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package jsonlist
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package jsonrpc
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package keylock
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ func BenchmarkKeyLock_NoBusyWait(b *testing.B) {
|
|||
|
||||
cancel := make(chan struct{})
|
||||
defer close(cancel)
|
||||
for i := 0; i < 10000; i++ {
|
||||
for i := 0; i < 1000; i++ {
|
||||
go func() {
|
||||
l.LockKeys(lockedKey, cancel)
|
||||
}()
|
||||
|
|
|
@ -242,10 +242,10 @@
|
|||
|
||||
Кодировка utf8
|
||||
|
||||
0xxxxxx runes 0−127
|
||||
11xxxxx 10xxxxxx 128−2047
|
||||
110xxxx 10xxxxxx 10xxxxxx 2048−65535
|
||||
1110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536−0x10ffff
|
||||
0xxxxxxx runes 0−127
|
||||
110xxxxx 10xxxxxx 128−2047
|
||||
1110xxxx 10xxxxxx 10xxxxxx 2048−65535
|
||||
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536−0x10ffff
|
||||
|
||||
Разница между рунами и байтами
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ Concurrency with Shared Memory
|
|||
a := 1
|
||||
a += 1
|
||||
|
||||
- Посылка значения из канал _happens_before_ получения этого значения из канала.
|
||||
- Посылка значения из канала _happens_before_ получения этого значения из канала.
|
||||
|
||||
- Два события A и B _происходят_одновременно_ (_are_concurrent_), если нельзя сказать что одно случилось раньше другого.
|
||||
|
||||
|
@ -312,18 +312,19 @@ Concurrency with Shared Memory
|
|||
func (once *Once) Do(f func()) {
|
||||
once.mu.Lock()
|
||||
defer once.mu.Unlock()
|
||||
if done {
|
||||
if once.done {
|
||||
return
|
||||
}
|
||||
if running {
|
||||
if once.running {
|
||||
once.cond.Wait() // releases and acquires mutex
|
||||
return
|
||||
}
|
||||
running = true
|
||||
|
||||
once.running = true
|
||||
once.mu.Unlock()
|
||||
f()
|
||||
once.mu.Lock()
|
||||
done = true
|
||||
once.done = true
|
||||
once.cond.Broadcast()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package hasql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
_ "github.com/jackc/pgx/v4/stdlib"
|
||||
"golang.yandex/hasql"
|
||||
|
@ -24,7 +27,7 @@ func Open() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Println("Node address", node.Addr)
|
||||
log.Println("Node address", node.Addr())
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
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 {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
|
|
19
ledger/README.md
Normal file
19
ledger/README.md
Normal 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/).
|
10
ledger/docker-compose.yaml
Normal file
10
ledger/docker-compose.yaml
Normal 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
9
ledger/ledger.go
Normal 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
135
ledger/ledger_test.go
Normal 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
21
ledger/model.go
Normal 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
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !change
|
||||
// +build !change
|
||||
|
||||
package lrucache
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package lrucache
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package main
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
### Что нужно сделать?
|
||||
|
||||
Нужно написать реализацию Once используя каналы.
|
||||
Нужно написать реализацию Once, используя каналы.
|
||||
|
||||
Использование пакета [sync](https://golang.org/pkg/sync) в этой задаче запрещено!
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package once
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//go:build !solution
|
||||
// +build !solution
|
||||
|
||||
package otp
|
||||
|
||||
|
|
|
@ -91,7 +91,7 @@ func TestReader(t *testing.T) {
|
|||
r := NewReader(testCase.r, testCase.prng)
|
||||
|
||||
buf, err := ioutil.ReadAll(r)
|
||||
require.Equal(t, testCase.err, err)
|
||||
require.ErrorIs(t, err, testCase.err)
|
||||
require.Equal(t, testCase.result, buf)
|
||||
})
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ func TestWriterError(t *testing.T) {
|
|||
n, err := w.Write(plaintext)
|
||||
|
||||
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, out.buf.Bytes(), ciphertext[:512])
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue