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
start: 17-02-2022 18:00
deadline: 27-02-2022 23:59
deadline: 06-03-2022 23:59
tasks:
- task: hotelbusiness
score: 100

View file

@ -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

View file

@ -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:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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'а

View file

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

View file

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

View file

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

View file

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

View file

@ -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)

View file

@ -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 в блоге (см. ссылки).
## Ссылки
@ -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}]%
```

View file

@ -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!")
}

View file

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

View file

@ -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
}

View file

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

View file

@ -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
}

View file

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

View file

@ -1,5 +1,4 @@
//go:build !change
// +build !change
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
4. работа с картинками: https://golang.org/pkg/image/
Пример, создания простой png размера 5x5:
Пример создания простой png размера 5x5:
```go
package main

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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).

View file

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

View file

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

View file

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

View file

@ -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`, и ожидание этих
таймаутов происходит последовательно в одной горутине.

View file

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

View file

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

View file

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

View file

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

View file

@ -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
```

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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`. Родитель следующего коммита.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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` - указатель на слайс.

View file

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

View file

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

View file

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

View file

@ -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)
}()

View file

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

View file

@ -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()
}

View file

@ -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()

View file

@ -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)
}

View file

@ -1,6 +1,7 @@
package redis
import (
"context"
"log"
"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
// +build !change
package lrucache

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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