diff --git a/.github/.sync b/.github/.sync new file mode 100644 index 0000000..e69de29 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..00fac7d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @OtusGolang/teachers-and-mentors diff --git a/.github/PULL_REQUEST_TEMPLATE/hw01_hello_otus.md b/.github/PULL_REQUEST_TEMPLATE/hw01_hello_otus.md new file mode 100644 index 0000000..a559a21 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw01_hello_otus.md @@ -0,0 +1,16 @@ +## Домашнее задание №1 «Hello, OTUS!» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) +- [ ] Я использовал `stringutil.Reverse`. +- [ ] Я сравнил объем кода с объемом авторского решения. [Зачем сверять объем кода?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D1%81%D0%B2%D0%B5%D1%80%D1%8F%D1%82%D1%8C-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BC-%D0%BA%D0%BE%D0%B4%D0%B0) + - main.go ~12 строк + +### Критерии оценки +- [ ] Пайплайн зелёный - 4 балла +- [ ] Используется `stringutil` - 4 балла +- [ ] Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw02_unpack_string.md b/.github/PULL_REQUEST_TEMPLATE/hw02_unpack_string.md new file mode 100644 index 0000000..997ff72 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw02_unpack_string.md @@ -0,0 +1,16 @@ +## Домашнее задание №2 «Распаковка строки» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) +- [ ] Я сравнил объем кода с объемом авторского решения. [Зачем сверять объем кода?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D1%81%D0%B2%D0%B5%D1%80%D1%8F%D1%82%D1%8C-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BC-%D0%BA%D0%BE%D0%B4%D0%B0) + - unpack.go ~45 строк для задания без звездочки + - unpack.go ~75 строк для задания со звездочкой + +### Критерии оценки +- [ ] Пайплайн зелёный - 4 балла +- [ ] Добавлены новые юнит-тесты - до 4 баллов +- [ ] Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw03_frequency_analysis.md b/.github/PULL_REQUEST_TEMPLATE/hw03_frequency_analysis.md new file mode 100644 index 0000000..21a0b2c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw03_frequency_analysis.md @@ -0,0 +1,13 @@ +## Домашнее задание №3 «Частотный анализ» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) + +### Критерии оценки +- [ ] Пайплайн зелёный - 4 балла +- [ ] Добавлены новые юнит-тесты - до 4 баллов +- [ ] Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw04_lru_cache.md b/.github/PULL_REQUEST_TEMPLATE/hw04_lru_cache.md new file mode 100644 index 0000000..0f0a42f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw04_lru_cache.md @@ -0,0 +1,16 @@ +## Домашнее задание №4 «LRU-кэш» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) +- [ ] Я проверил, что все операции выполняются за О(1). + +### Критерии оценки +- [ ] Пайплайн зелёный - 4 балла +- [ ] Добавлены новые юнит-тесты для списка - 1 балл +- [ ] Добавлены новые юнит-тесты для кэша (включая тест на логику +выталкивания из кэша редко используемых элементов) - до 3 баллов +- [ ] Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw05_parallel_execution.md b/.github/PULL_REQUEST_TEMPLATE/hw05_parallel_execution.md new file mode 100644 index 0000000..40870b1 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw05_parallel_execution.md @@ -0,0 +1,15 @@ +## Домашнее задание №5 «Параллельное исполнение» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) +- [ ] Я сравнил объем кода с объемом авторского решения. [Зачем сверять объем кода?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D1%81%D0%B2%D0%B5%D1%80%D1%8F%D1%82%D1%8C-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BC-%D0%BA%D0%BE%D0%B4%D0%B0) + - run.go ~55 строк + +### Критерии оценки +- [ ] Пайплайн зелёный - 4 балла +- [ ] Добавлены новые юнит-тесты - до 4 баллов +- [ ] Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw06_pipeline_execution.md b/.github/PULL_REQUEST_TEMPLATE/hw06_pipeline_execution.md new file mode 100644 index 0000000..295667a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw06_pipeline_execution.md @@ -0,0 +1,15 @@ +## Домашнее задание №6 «Пайплайн» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) +- [ ] Я сравнил объем кода с объемом авторского решения. [Зачем сверять объем кода?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D1%81%D0%B2%D0%B5%D1%80%D1%8F%D1%82%D1%8C-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BC-%D0%BA%D0%BE%D0%B4%D0%B0) + - pipeline.go ~55 строк + +### Критерии оценки +- [ ] CI-пайплайн зелёный - 5 баллов +- [ ] Добавлены новые юнит-тесты - до 2 баллов +- [ ] Понятность и чистота кода - до 3 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw07_file_copying.md b/.github/PULL_REQUEST_TEMPLATE/hw07_file_copying.md new file mode 100644 index 0000000..1752958 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw07_file_copying.md @@ -0,0 +1,14 @@ +## Домашнее задание №7 «Утилита для копирования файлов» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) +- [ ] Я провел ручное тестирование программы (например, запускал `test.sh`). + +### Критерии оценки +- [ ] Пайплайн зелёный - 4 балла +- [ ] Добавлены юнит-тесты - до 4 баллов +- [ ] Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw08_envdir_tool.md b/.github/PULL_REQUEST_TEMPLATE/hw08_envdir_tool.md new file mode 100644 index 0000000..fa69c3f --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw08_envdir_tool.md @@ -0,0 +1,14 @@ +## Домашнее задание №8 «Утилита envdir» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) +- [ ] Я провел ручное тестирование программы (например, запускал `test.sh`). + +### Критерии оценки +- [ ] Пайплайн зелёный - 4 балла +- [ ] Добавлены юнит-тесты - до 4 баллов +- [ ] Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw09_struct_validator.md b/.github/PULL_REQUEST_TEMPLATE/hw09_struct_validator.md new file mode 100644 index 0000000..cbe8afd --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw09_struct_validator.md @@ -0,0 +1,13 @@ +## Домашнее задание №9 «Валидатор структур» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) + +### Критерии оценки +- [ ] Пайплайн зелёный - 3 балла +- [ ] Добавлены юнит-тесты - до 4 баллов +- [ ] Понятность и чистота кода - до 3 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw10_program_optimization.md b/.github/PULL_REQUEST_TEMPLATE/hw10_program_optimization.md new file mode 100644 index 0000000..fdade3a --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw10_program_optimization.md @@ -0,0 +1,14 @@ +## Домашнее задание №10 «Оптимизация программы» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) +- [ ] Я написал бенчмарк и приложил результаты benchstat. + +### Критерии оценки +- [ ] Пайплайн зелёный и нет попытки «обмануть» систему - 4 балла +- [ ] Добавлены юнит-тесты - до 3 баллов +- [ ] Понятность и чистота кода - до 3 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw11_telnet_client.md b/.github/PULL_REQUEST_TEMPLATE/hw11_telnet_client.md new file mode 100644 index 0000000..0cd3a10 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw11_telnet_client.md @@ -0,0 +1,14 @@ +## Домашнее задание №11 «Клиент TELNET» + +### Чек-лист студента ([Что это?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0)) +- [ ] Я перечитал задание после решения. [Зачем перечитывать задание?](https://github.com/OtusGolang/home_work/wiki/%D0%9A%D0%BE%D0%BC%D0%BC%D0%B5%D0%BD%D1%82%D0%B0%D1%80%D0%B8%D0%B8-%D0%BA-%D1%87%D0%B5%D0%BA-%D0%BB%D0%B8%D1%81%D1%82%D1%83-%D1%81%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0#user-content-%D0%97%D0%B0%D1%87%D0%B5%D0%BC-%D0%BF%D0%B5%D1%80%D0%B5%D1%87%D0%B8%D1%82%D1%8B%D0%B2%D0%B0%D1%82%D1%8C-%D0%B7%D0%B0%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5) +- [ ] Я запустил `go mod tidy`. +- [ ] Я удалил `.sync` файл. [Зачем его удалять?](https://github.com/OtusGolang/home_work/wiki/%5B%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC%5D-%D0%9F%D1%80%D0%BE%D1%86%D0%B5%D1%81%D1%81-%D1%81%D0%B4%D0%B0%D1%87%D0%B8-%D0%94%D0%97#user-content-%D0%92%D0%B0%D1%80%D0%B8%D0%B0%D0%BD%D1%82-2) +- [ ] Я провел ручное тестирование программы (например, запускал `test.sh`). + +### Критерии оценки +- [ ] Пайплайн зелёный - 4 балла +- [ ] Добавлены юнит-тесты - до 2 баллов +- [ ] Понятность и чистота кода - до 4 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw12_calendar.md b/.github/PULL_REQUEST_TEMPLATE/hw12_calendar.md new file mode 100644 index 0000000..060d15e --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw12_calendar.md @@ -0,0 +1,16 @@ +## Домашнее задание №12 «Заготовка сервиса Календарь» + +### Критерии оценки +- [ ] Makefile заполнен и пайплайн зеленый - 1 балл +- [ ] Понятность и чистота кода (включая факт, что проект разбит +на пакеты по определенной логике) - до 2 баллов +- [ ] Реализовано конфигурирование сервиса - 1 балл +- [ ] Используется логгер и он настраивается из конфига - 1 балл +- [ ] Запускается простой HTTP-сервер, мидлвара удовлетворяет ТЗ - 1 балл +- [ ] Присутствуют юнит-тесты - 1 балл + +Реализовано хранилище: +- [ ] in-memory - 1 балл +- [ ] sql + миграции - 2 балла + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw13_calendar.md b/.github/PULL_REQUEST_TEMPLATE/hw13_calendar.md new file mode 100644 index 0000000..638cc01 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw13_calendar.md @@ -0,0 +1,10 @@ +## Домашнее задание №13 «API к Календарю» + +### Критерии оценки +- [ ] Makefile заполнен и пайплайн зеленый - 1 балл +- [ ] Реализовано GRPC API и `make generate` - 3 балла +- [ ] Реализовано HTTP API - 2 балла +- [ ] Написаны юнит-тесты на API - до 2 баллов +- [ ] Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw14_calendar.md b/.github/PULL_REQUEST_TEMPLATE/hw14_calendar.md new file mode 100644 index 0000000..e2854a6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw14_calendar.md @@ -0,0 +1,14 @@ +## Домашнее задание №14 «Кроликизация Календаря» + +### Критерии оценки +- [ ] Makefile заполнен и пайплайн зеленый - 1 балл +- [ ] Работа с RMQ выделена в отдельный пакет, код не дублируется - 1 балл +- [ ] Реализован Рассыльщик - 2 балла +- [ ] Можно собрать сервисы одной командой (`make build`) - 1 балл +- [ ] Понятность и чистота кода - до 2 баллов + +Реализован Планировщик: +- [ ] отсылает уведомления о выбранных событиях - 2 балла +- [ ] удаляет старые события - 1 балл + +#### Зачёт от 7 баллов diff --git a/.github/PULL_REQUEST_TEMPLATE/hw15_calendar.md b/.github/PULL_REQUEST_TEMPLATE/hw15_calendar.md new file mode 100644 index 0000000..2689cc4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/hw15_calendar.md @@ -0,0 +1,12 @@ +## Домашнее задание №15 «Докеризация и интеграционное тестирование Календаря» + +### Критерии оценки +- [ ] Проект полностью запускается и останавливается с помощью `make up` / `make down` - 3 балла +- [ ] Интеграционные тесты запускаются с помощью `make integration-tests`. Команда возвращает верный код ответа - 1 балл + +Интеграционные тесты покрывают бизнес сценарии: +- [ ] добавление события и обработка бизнес ошибок - 2 балла +- [ ] получение листинга событий на день/неделю/месяц - 2 балла +- [ ] отправка уведомлений - 2 балла + +#### Зачёт от 7 баллов diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..a623d0b --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,85 @@ +name: Otus homework tests + +on: + push: + branches: + - hw* + +env: + GO111MODULE: "on" + +jobs: + lint: + runs-on: ubuntu-latest + if: ${{ !contains(github.ref, 'calendar') }} + steps: + - name: Extract branch name + run: echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: ~1.22 + + - name: Check out code + uses: actions/checkout@v3 + + - name: Linters + uses: golangci/golangci-lint-action@v3 + with: + version: v1.57.2 + working-directory: ${{ env.BRANCH }} + + tests: + runs-on: ubuntu-latest + if: ${{ !contains(github.ref, 'calendar') }} + steps: + - name: Extract branch name + run: echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: ^1.22 + + - name: Check out code + uses: actions/checkout@v3 + + - name: Unit tests + run: go test -v -count=1 -race -timeout=1m ./... + working-directory: ${{ env.BRANCH }} + + - name: Optimization tests + run: go test -v -count=1 -timeout=1m -tags bench ./... + if: env.BRANCH == 'hw10_program_optimization' + working-directory: ${{ env.BRANCH }} + + - name: Bash tests + shell: bash + run: ./test.sh + if: contains('hw01_hello_otus hw07_file_copying hw08_envdir_tool hw11_telnet_client', env.BRANCH) + working-directory: ${{ env.BRANCH }} + + tests_by_makefile: + runs-on: ubuntu-latest + if: contains(github.ref, 'calendar') + steps: + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: ^1.22 + + - name: Check out code + uses: actions/checkout@v3 + + - name: make lint + run: make lint + working-directory: hw12_13_14_15_calendar + + - name: make build + run: make build + working-directory: hw12_13_14_15_calendar + + - name: make test + run: make test + working-directory: hw12_13_14_15_calendar diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e93927 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +### Go template +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +go-cp +go-envdir +go-telnet + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +### User defined +.idea/ +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +.vscode/ +*.bak diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..d38eca5 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,67 @@ +run: + tests: true + build-tags: + - bench + - !bench + +linters-settings: + funlen: + lines: 150 + statements: 80 + +issues: + exclude-rules: + - path: _test\.go + linters: + - errcheck + - dupl + - gocyclo + - gosec + +linters: + disable-all: true + enable: + - asciicheck + - depguard + - dogsled + - dupl + - bodyclose + - durationcheck + - errorlint + - exhaustive + - exportloopref + - funlen + - gci + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - gofmt + - gofumpt + - goheader + - goprintffuncname + - gosec + - gosimple + - govet + - importas + - ineffassign + - lll + - makezero + - misspell + - nestif + - nilerr + - noctx + - nolintlint + - prealloc + - predeclared + - revive + - staticcheck + - stylecheck + - tagliatelle + - thelper + - typecheck + - unconvert + - unparam + - unused + - whitespace diff --git a/.scripts/.sync b/.scripts/.sync new file mode 100644 index 0000000..e69de29 diff --git a/.scripts/lint.sh b/.scripts/lint.sh new file mode 100755 index 0000000..dba0737 --- /dev/null +++ b/.scripts/lint.sh @@ -0,0 +1,17 @@ +#!/bin/zsh + +sed -i.bak '/- deadcode/d' .golangci.yml +sed -i '' '/- unused/d' .golangci.yml +sed -i '' '/- structcheck/d' .golangci.yml + +for d in $(ls) +do + if [[ $d == hw* ]]; then + cd $d + echo "Lint ${d}..." + golangci-lint run ./... + cd .. + fi +done + +mv .golangci.yml.bak .golangci.yml diff --git a/.scripts/sync.sh b/.scripts/sync.sh new file mode 100755 index 0000000..6ab8c46 --- /dev/null +++ b/.scripts/sync.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +dst=$1 +if [[ ! -d "${dst}" ]]; then + echo "Usage: ./.scripts/sync.sh ." + echo "The destination dir should exist" + exit 1 +fi + +GLOBIGNORE=".:..:.git" +for f in *; do + [[ -d "${dst}/${f}" ]] && [[ ! -f "${dst}/${f}/.sync" ]] && continue + + echo "syncing ${f}..." + cp -R "${f}" "${dst}" +done diff --git a/.scripts/update.sh b/.scripts/update.sh new file mode 100755 index 0000000..69c6e78 --- /dev/null +++ b/.scripts/update.sh @@ -0,0 +1,12 @@ +#!/bin/zsh + +for d in $(ls) +do + if [[ $d == hw* ]]; then + cd $d + echo "Update deps in ${d}..." + go mod tidy + go get -t -u ./... + cd .. + fi +done diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2d3c253 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2020, Дмитрий Смаль, Антон Телышев + +Разрешается повторное распространение и использование как в виде исходного кода, так и в двоичной форме, +с изменениями или без, при соблюдении следующих условий: + +- При повторном распространении исходного кода должно оставаться указанное выше уведомление об авторском праве, + этот список условий и последующий отказ от гарантий. + +- При повторном распространении двоичного кода должна сохраняться указанная выше информация об авторском праве, + этот список условий и последующий отказ от гарантий в документации и/или в других материалах, поставляемых при распространении. + +- Ни название ООО «Отус онлайн-образование», ни имена её сотрудников и преподавателей не могут быть использованы в +качестве поддержки или продвижения продуктов, основанных на этом ПО без предварительного письменного разрешения. + +ЭТА ПРОГРАММА ПРЕДОСТАВЛЕНА ВЛАДЕЛЬЦАМИ АВТОРСКИХ ПРАВ И/ИЛИ ДРУГИМИ СТОРОНАМИ «КАК ОНА ЕСТЬ» БЕЗ КАКОГО-ЛИБО ВИДА ГАРАНТИЙ, +ВЫРАЖЕННЫХ ЯВНО ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ, ПОДРАЗУМЕВАЕМЫЕ ГАРАНТИИ КОММЕРЧЕСКОЙ ЦЕННОСТИ И ПРИГОДНОСТИ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ. +НИ В КОЕМ СЛУЧАЕ НИ ОДИН ВЛАДЕЛЕЦ АВТОРСКИХ ПРАВ И НИ ОДНО ДРУГОЕ ЛИЦО, КОТОРОЕ МОЖЕТ ИЗМЕНЯТЬ И/ИЛИ ПОВТОРНО РАСПРОСТРАНЯТЬ ПРОГРАММУ, +КАК БЫЛО СКАЗАНО ВЫШЕ, НЕ НЕСЁТ ОТВЕТСТВЕННОСТИ, ВКЛЮЧАЯ ЛЮБЫЕ ОБЩИЕ, СЛУЧАЙНЫЕ, СПЕЦИАЛЬНЫЕ ИЛИ ПОСЛЕДОВАВШИЕ УБЫТКИ, ВСЛЕДСТВИЕ ИСПОЛЬЗОВАНИЯ +ИЛИ НЕВОЗМОЖНОСТИ ИСПОЛЬЗОВАНИЯ ПРОГРАММЫ (ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ПОТЕРЕЙ ДАННЫХ, ИЛИ ДАННЫМИ, СТАВШИМИ НЕПРАВИЛЬНЫМИ, ИЛИ ПОТЕРЯМИ, +ПРИНЕСЕННЫМИ ИЗ-ЗА ВАС ИЛИ ТРЕТЬИХ ЛИЦ, ИЛИ ОТКАЗОМ ПРОГРАММЫ РАБОТАТЬ СОВМЕСТНО С ДРУГИМИ ПРОГРАММАМИ), ДАЖЕ ЕСЛИ ТАКОЙ ВЛАДЕЛЕЦ ИЛИ ДРУГОЕ +ЛИЦО БЫЛИ ИЗВЕЩЕНЫ О ВОЗМОЖНОСТИ ТАКИХ УБЫТКОВ. diff --git a/README.md b/README.md new file mode 100644 index 0000000..75815ab --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +## Домашние задания курса [OTUS «Разработчик Golang»](https://otus.ru/lessons/golang-professional/?utm_source=github&utm_medium=free&utm_campaign=otus) +1) [«Hello, OTUS!»](./hw01_hello_otus) +2) [«Распаковка строки»](./hw02_unpack_string) +3) [«Частотный анализ»](./hw03_frequency_analysis) +4) [«LRU-кэш»](./hw04_lru_cache) +5) [«Параллельное исполнение»](./hw05_parallel_execution) +6) [«Пайплайн»](./hw06_pipeline_execution) +7) [«Утилита для копирования файлов»](./hw07_file_copying) +8) [«Утилита envdir»](./hw08_envdir_tool) +9) [«Валидатор структур»](./hw09_struct_validator) +10) [«Оптимизация программы»](./hw10_program_optimization) +11) [«Клиент TELNET»](./hw11_telnet_client) +12) [«Заготовка сервиса Календарь»](./hw12_13_14_15_calendar/docs/12_README.md) +13) [«API к Календарю»](./hw12_13_14_15_calendar/docs/13_README.md) +14) [«Кроликизация Календаря»](./hw12_13_14_15_calendar/docs/14_README.md) +15) [«Докеризация и интеграционное тестирование Календаря»](./hw12_13_14_15_calendar/docs/15_README.md) +16) [«Проект»](https://github.com/OtusGolang/final_project) + +--- +[Инструкция по сдаче ДЗ](https://github.com/OtusGolang/home_work/wiki#%D0%A1%D1%82%D1%83%D0%B4%D0%B5%D0%BD%D1%82%D0%B0%D0%BC). + +--- +Используемая версия [golangci-lint](https://golangci-lint.run/usage/install/#other-ci): v1.57.2 +``` +$ golangci-lint version +golangci-lint has version 1.57.2 built with go1.22.1 from 77a8601a on 2024-03-28T19:01:11Z +``` + +--- +Авторы ДЗ: +- [Дмитрий Смаль](https://github.com/mialinx) +- [Антон Телышев](https://github.com/Antonboom) diff --git a/hw01_hello_otus/.sync b/hw01_hello_otus/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw01_hello_otus/README.md b/hw01_hello_otus/README.md new file mode 100644 index 0000000..25c783e --- /dev/null +++ b/hw01_hello_otus/README.md @@ -0,0 +1,22 @@ +## Домашнее задание №1 «Hello, OTUS!» + +Необходимо написать программу, печатающую в стандартный вывод перевернутую фразу +``` +Hello, OTUS! +``` + +Для переворота строки следует воспользоваться возможностями +[golang.org/x/example/hello/reverse](https://pkg.go.dev/golang.org/x/example/hello/reverse). + +Кроме этого необходимо исправить **go.mod** так, чтобы для данного модуля работала +команда `go get`, а полученный **go.sum** закоммитить. + +### Критерии оценки +- Пайплайн зелёный - 4 балла +- Используется `reverse` - 4 балла +- Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов + +### Подсказки +- `Reverse` diff --git a/hw01_hello_otus/go.mod b/hw01_hello_otus/go.mod new file mode 100644 index 0000000..bf7b900 --- /dev/null +++ b/hw01_hello_otus/go.mod @@ -0,0 +1,3 @@ +module github.com/fixme_my_friend/hw01_hello_otus + +go 1.22 diff --git a/hw01_hello_otus/main.go b/hw01_hello_otus/main.go new file mode 100644 index 0000000..1eca213 --- /dev/null +++ b/hw01_hello_otus/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + // Place your code here. +} diff --git a/hw01_hello_otus/test.sh b/hw01_hello_otus/test.sh new file mode 100755 index 0000000..8870d5d --- /dev/null +++ b/hw01_hello_otus/test.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -xeuo pipefail + +expected='!SUTO ,olleH' +result=$(go run main.go | sed 's/^ *//;s/ *$//') +[ "${result}" = "${expected}" ] || (echo -e "invalid output: ${result}" && exit 1) + +echo "PASS" diff --git a/hw02_unpack_string/.sync b/hw02_unpack_string/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw02_unpack_string/README.md b/hw02_unpack_string/README.md new file mode 100644 index 0000000..34102df --- /dev/null +++ b/hw02_unpack_string/README.md @@ -0,0 +1,41 @@ +## Домашнее задание №2 «Распаковка строки» + +Необходимо написать Go функцию, осуществляющую примитивную распаковку строки, +содержащую повторяющиеся символы/руны, например: +* "a4bc2d5e" => "aaaabccddddde" +* "abcd" => "abcd" +* "3abc" => "" (некорректная строка) +* "45" => "" (некорректная строка) +* "aaa10b" => "" (некорректная строка) +* "aaa0b" => "aab" +* "" => "" +* "d\n5abc" => "d\n\n\n\n\nabc" + +Как видно из примеров, разрешено использование цифр, но не чисел. + +В случае, если была передана некорректная строка, функция должна возвращать ошибку. +При необходимости можно выделять дополнительные функции / ошибки. + +**(*) Дополнительное задание: поддержка экранирования через `\`:** + +**(обратите внимание на косые кавычки)** +* \`qwe\4\5\` => "qwe45" +* \`qwe\45\` => "qwe44444" +* \`qwe\\\5\` => \`qwe\\\\\\\\\\` +* \`qw\ne\` => "" (некорректная строка) + +Как видно из примера, заэкранировать можно только цифру или слэш. + +### Критерии оценки +- Пайплайн зелёный - 4 балла +- Добавлены новые юнит-тесты - до 4 баллов +- Понятность и чистота кода - до 2 баллов +- Дополнительное задание на баллы не влияет + +#### Зачёт от 7 баллов + +### Подсказки +- https://golang.org/ref/spec#String_literals +- `strings.Builder` +- `strings.Repeat` +- `strconv.Atoi` diff --git a/hw02_unpack_string/go.mod b/hw02_unpack_string/go.mod new file mode 100644 index 0000000..3b20310 --- /dev/null +++ b/hw02_unpack_string/go.mod @@ -0,0 +1,11 @@ +module github.com/fixme_my_friend/hw02_unpack_string + +go 1.22 + +require github.com/stretchr/testify v1.7.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/hw02_unpack_string/go.sum b/hw02_unpack_string/go.sum new file mode 100644 index 0000000..c221f64 --- /dev/null +++ b/hw02_unpack_string/go.sum @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hw02_unpack_string/unpack.go b/hw02_unpack_string/unpack.go new file mode 100644 index 0000000..41f003d --- /dev/null +++ b/hw02_unpack_string/unpack.go @@ -0,0 +1,12 @@ +package hw02unpackstring + +import ( + "errors" +) + +var ErrInvalidString = errors.New("invalid string") + +func Unpack(_ string) (string, error) { + // Place your code here. + return "", nil +} diff --git a/hw02_unpack_string/unpack_test.go b/hw02_unpack_string/unpack_test.go new file mode 100644 index 0000000..9799e18 --- /dev/null +++ b/hw02_unpack_string/unpack_test.go @@ -0,0 +1,45 @@ +package hw02unpackstring + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestUnpack(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {input: "a4bc2d5e", expected: "aaaabccddddde"}, + {input: "abccd", expected: "abccd"}, + {input: "", expected: ""}, + {input: "aaa0b", expected: "aab"}, + // uncomment if task with asterisk completed + // {input: `qwe\4\5`, expected: `qwe45`}, + // {input: `qwe\45`, expected: `qwe44444`}, + // {input: `qwe\\5`, expected: `qwe\\\\\`}, + // {input: `qwe\\\3`, expected: `qwe\3`}, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.input, func(t *testing.T) { + result, err := Unpack(tc.input) + require.NoError(t, err) + require.Equal(t, tc.expected, result) + }) + } +} + +func TestUnpackInvalidString(t *testing.T) { + invalidStrings := []string{"3abc", "45", "aaa10b"} + for _, tc := range invalidStrings { + tc := tc + t.Run(tc, func(t *testing.T) { + _, err := Unpack(tc) + require.Truef(t, errors.Is(err, ErrInvalidString), "actual error %q", err) + }) + } +} diff --git a/hw03_frequency_analysis/.sync b/hw03_frequency_analysis/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw03_frequency_analysis/README.md b/hw03_frequency_analysis/README.md new file mode 100644 index 0000000..a1cb649 --- /dev/null +++ b/hw03_frequency_analysis/README.md @@ -0,0 +1,60 @@ +## Домашнее задание №3 «Частотный анализ» + +Необходимо написать Go функцию, принимающую на вход строку с текстом и +возвращающую слайс с 10-ю наиболее часто встречаемыми в тексте словами. + +Если слова имеют одинаковую частоту, то должны быть отсортированы **лексикографически**. + +* Словом считается набор символов, разделенных пробельными символами. + +* Если есть более 10 самых частотых слов (например 15 разных слов встречаются ровно 133 раза, +остальные < 100), то следует вернуть 10 лексикографически первых слов. + +* Словоформы не учитываем: "нога", "ногу", "ноги" - это разные слова. + +* Слово с большой и маленькой буквы считать за разные слова. "Нога" и "нога" - это разные слова. + +* Знаки препинания считать "буквами" слова или отдельными словами. +"-" (тире) - это отдельное слово. "нога," и "нога" - это разные слова. + +#### Пример +``` +cat and dog, one dog,two cats and one man +``` +Топ 7: +- `and` (2) +- `one` (2) +- `cat` (1) +- `cats` (1) +- `dog,` (1) +- `dog,two` (1) +- `man` (1) + +При необходимости можно выделять дополнительные функции / ошибки. + +**(*) Дополнительное задание: не учитывать регистр букв и знаки препинания по краям слова:** +* "Нога" и "нога" - это одинаковые слова, "нога!", "нога", "нога," и " 'нога' " - это одинаковые слова; +* "какой-то" и "какойто" - это разные слова. +* "dog,cat", "dog...cat", "dogcat" - разные слова +* "-------" это слово +* "-" словом не является + +### Критерии оценки +- Пайплайн зелёный - 4 балла +- Добавлены новые юнит-тесты - до 4 баллов +- Понятность и чистота кода - до 2 баллов +- Дополнительное задание на баллы не влияет + +#### Зачёт от 7 баллов + +### Подсказки +- `regexp.MustCompile` +- `strings.Split` +- `strings.Fields` +- `sort.Slice` + +### Частые ошибки +- `regexp.MustCompile` используется в функции, а не уровне пакета - это плохо по следующим причинам: + * производительность: нет смысла компилировать регулярку каждый раз при вызове функции; + * функция не должна паниковать! +- При выполнении задания со звёздочкой забывают, что тире не должно являться словом. diff --git a/hw03_frequency_analysis/go.mod b/hw03_frequency_analysis/go.mod new file mode 100644 index 0000000..31a9a38 --- /dev/null +++ b/hw03_frequency_analysis/go.mod @@ -0,0 +1,11 @@ +module github.com/fixme_my_friend/hw03_frequency_analysis + +go 1.22 + +require github.com/stretchr/testify v1.7.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/hw03_frequency_analysis/go.sum b/hw03_frequency_analysis/go.sum new file mode 100644 index 0000000..c221f64 --- /dev/null +++ b/hw03_frequency_analysis/go.sum @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hw03_frequency_analysis/top.go b/hw03_frequency_analysis/top.go new file mode 100644 index 0000000..aff6568 --- /dev/null +++ b/hw03_frequency_analysis/top.go @@ -0,0 +1,6 @@ +package hw03frequencyanalysis + +func Top10(_ string) []string { + // Place your code here. + return nil +} diff --git a/hw03_frequency_analysis/top_test.go b/hw03_frequency_analysis/top_test.go new file mode 100644 index 0000000..e28c5cb --- /dev/null +++ b/hw03_frequency_analysis/top_test.go @@ -0,0 +1,82 @@ +package hw03frequencyanalysis + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// Change to true if needed. +var taskWithAsteriskIsCompleted = false + +var text = `Как видите, он спускается по лестнице вслед за своим + другом Кристофером Робином, головой вниз, пересчитывая + ступеньки собственным затылком: бум-бум-бум. Другого способа + сходить с лестницы он пока не знает. Иногда ему, правда, + кажется, что можно бы найти какой-то другой способ, если бы он + только мог на минутку перестать бумкать и как следует + сосредоточиться. Но увы - сосредоточиться-то ему и некогда. + Как бы то ни было, вот он уже спустился и готов с вами + познакомиться. + - Винни-Пух. Очень приятно! + Вас, вероятно, удивляет, почему его так странно зовут, а + если вы знаете английский, то вы удивитесь еще больше. + Это необыкновенное имя подарил ему Кристофер Робин. Надо + вам сказать, что когда-то Кристофер Робин был знаком с одним + лебедем на пруду, которого он звал Пухом. Для лебедя это было + очень подходящее имя, потому что если ты зовешь лебедя + громко: "Пу-ух! Пу-ух!"- а он не откликается, то ты всегда + можешь сделать вид, что ты просто понарошку стрелял; а если ты + звал его тихо, то все подумают, что ты просто подул себе на + нос. Лебедь потом куда-то делся, а имя осталось, и Кристофер + Робин решил отдать его своему медвежонку, чтобы оно не пропало + зря. + А Винни - так звали самую лучшую, самую добрую медведицу + в зоологическом саду, которую очень-очень любил Кристофер + Робин. А она очень-очень любила его. Ее ли назвали Винни в + честь Пуха, или Пуха назвали в ее честь - теперь уже никто не + знает, даже папа Кристофера Робина. Когда-то он знал, а теперь + забыл. + Словом, теперь мишку зовут Винни-Пух, и вы знаете почему. + Иногда Винни-Пух любит вечерком во что-нибудь поиграть, а + иногда, особенно когда папа дома, он больше любит тихонько + посидеть у огня и послушать какую-нибудь интересную сказку. + В этот вечер...` + +func TestTop10(t *testing.T) { + t.Run("no words in empty string", func(t *testing.T) { + require.Len(t, Top10(""), 0) + }) + + t.Run("positive test", func(t *testing.T) { + if taskWithAsteriskIsCompleted { + expected := []string{ + "а", // 8 + "он", // 8 + "и", // 6 + "ты", // 5 + "что", // 5 + "в", // 4 + "его", // 4 + "если", // 4 + "кристофер", // 4 + "не", // 4 + } + require.Equal(t, expected, Top10(text)) + } else { + expected := []string{ + "он", // 8 + "а", // 6 + "и", // 6 + "ты", // 5 + "что", // 5 + "-", // 4 + "Кристофер", // 4 + "если", // 4 + "не", // 4 + "то", // 4 + } + require.Equal(t, expected, Top10(text)) + } + }) +} diff --git a/hw04_lru_cache/.sync b/hw04_lru_cache/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw04_lru_cache/README.md b/hw04_lru_cache/README.md new file mode 100644 index 0000000..0deca4e --- /dev/null +++ b/hw04_lru_cache/README.md @@ -0,0 +1,84 @@ +## Домашнее задание №4 «LRU-кэш» +Необходимо реализовать LRU-кэш на основе двусвязного списка. + +Задание состоит из двух частей, которые необходимо выполнять последовательно. + +### 1) Реализация двусвязного списка +Список имеет структуру вида +```text +nil <- (prev) front <-> ... <-> elem <-> ... <-> back (next) -> nil +``` + +Необходимо реализовать следующий интерфейс List: +- Len() int // длина списка +- Front() *ListItem // первый элемент списка +- Back() *ListItem // последний элемент списка +- PushFront(v interface{}) *ListItem // добавить значение в начало +- PushBack(v interface{}) *ListItem // добавить значение в конец +- Remove(i *ListItem) // удалить элемент +- MoveToFront(i *ListItem) // переместить элемент в начало + +**Считаем, что методы Remove и MoveToFront вызываются только от существующих в списке элементов.** + +Элемент списка ListItem: +- Value interface{} // значение +- Next *ListItem // следующий элемент +- Prev *ListItem // предыдущий элемент + +Сложность всех операций должна быть O(1), +т.е. не должно быть мест, где осуществляется полный обход списка. + +### 2) Реализация кэша на основе ранее написанного списка +Необходимо реализовать следующий интерфейс Cache: +- Set(key Key, value interface{}) bool // Добавить значение в кэш по ключу. +- Get(key Key) (interface{}, bool) // Получить значение из кэша по ключу. +- Clear() // Очистить кэш. + +Структура кэша: +- ёмкость (количество сохраняемых в кэше элементов) +- очередь \[последних используемых элементов\] на основе двусвязного списка +- словарь, отображающий ключ (строка) на элемент очереди + +Элемент кэша хранит в себе ключ, по которому он лежит в словаре, и само значение. +Для чего это нужно понятно из алгоритма работы кэша (см. ниже). + +Сложность операций `Set`/`Get` должна быть O(1), при желании `Clear` тоже можно сделать О(1). + +Алгоритм работы кэша: +- при добавлении элемента: + - если элемент присутствует в словаре, то обновить его значение и переместить элемент в начало очереди; + - если элемента нет в словаре, то добавить в словарь и в начало очереди + (при этом, если размер очереди больше ёмкости кэша, + то необходимо удалить последний элемент из очереди и его значение из словаря); + - возвращаемое значение - флаг, присутствовал ли элемент в кэше. +- при получении элемента: + - если элемент присутствует в словаре, то переместить элемент в начало очереди и вернуть его значение и true; + - если элемента нет в словаре, то вернуть nil и false + (работа с кешом похожа на работу с `map`) + +Ожидаются следующие тесты: +- на логику выталкивания элементов из-за размера очереди +(например: n = 3, добавили 4 элемента - 1й из кэша вытолкнулся); +- на логику выталкивания давно используемых элементов +(например: n = 3, добавили 3 элемента, обратились несколько раз к разным элементам: +изменили значение, получили значение и пр. - добавили 4й элемент, +из первой тройки вытолкнется тот элемент, что был затронут наиболее давно). + +**(*) Дополнительное задание: сделать кэш горутино-безопасным.** + +### Критерии оценки +- Пайплайн зелёный - 4 балла +- Добавлены новые юнит-тесты для списка - 1 балл +- Добавлены новые юнит-тесты для кэша (см. выше) - до 3 баллов +- Понятность и чистота кода - до 2 баллов +- Дополнительное задание на баллы не влияет + +#### Зачёт от 7 баллов + +### Подсказки +- https://en.wikipedia.org/wiki/Doubly_linked_list +- https://ru.bmstu.wiki/LRU_(Least_Recently_Used) +- `sync.Mutex` + +### Частые ошибки +1) В задании со звёздочкой забывают про синхронизацию в методе Clear кэша. diff --git a/hw04_lru_cache/cache.go b/hw04_lru_cache/cache.go new file mode 100644 index 0000000..a43d727 --- /dev/null +++ b/hw04_lru_cache/cache.go @@ -0,0 +1,25 @@ +package hw04lrucache + +type Key string + +type Cache interface { + Set(key Key, value interface{}) bool + Get(key Key) (interface{}, bool) + Clear() +} + +type lruCache struct { + Cache // Remove me after realization. + + capacity int + queue List + items map[Key]*ListItem +} + +func NewCache(capacity int) Cache { + return &lruCache{ + capacity: capacity, + queue: NewList(), + items: make(map[Key]*ListItem, capacity), + } +} diff --git a/hw04_lru_cache/cache_test.go b/hw04_lru_cache/cache_test.go new file mode 100644 index 0000000..2b43d4c --- /dev/null +++ b/hw04_lru_cache/cache_test.go @@ -0,0 +1,79 @@ +package hw04lrucache + +import ( + "math/rand" + "strconv" + "sync" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCache(t *testing.T) { + t.Run("empty cache", func(t *testing.T) { + c := NewCache(10) + + _, ok := c.Get("aaa") + require.False(t, ok) + + _, ok = c.Get("bbb") + require.False(t, ok) + }) + + t.Run("simple", func(t *testing.T) { + c := NewCache(5) + + wasInCache := c.Set("aaa", 100) + require.False(t, wasInCache) + + wasInCache = c.Set("bbb", 200) + require.False(t, wasInCache) + + val, ok := c.Get("aaa") + require.True(t, ok) + require.Equal(t, 100, val) + + val, ok = c.Get("bbb") + require.True(t, ok) + require.Equal(t, 200, val) + + wasInCache = c.Set("aaa", 300) + require.True(t, wasInCache) + + val, ok = c.Get("aaa") + require.True(t, ok) + require.Equal(t, 300, val) + + val, ok = c.Get("ccc") + require.False(t, ok) + require.Nil(t, val) + }) + + t.Run("purge logic", func(t *testing.T) { + // Write me + }) +} + +func TestCacheMultithreading(t *testing.T) { + t.Skip() // Remove me if task with asterisk completed. + + c := NewCache(10) + wg := &sync.WaitGroup{} + wg.Add(2) + + go func() { + defer wg.Done() + for i := 0; i < 1_000_000; i++ { + c.Set(Key(strconv.Itoa(i)), i) + } + }() + + go func() { + defer wg.Done() + for i := 0; i < 1_000_000; i++ { + c.Get(Key(strconv.Itoa(rand.Intn(1_000_000)))) + } + }() + + wg.Wait() +} diff --git a/hw04_lru_cache/go.mod b/hw04_lru_cache/go.mod new file mode 100644 index 0000000..d05121c --- /dev/null +++ b/hw04_lru_cache/go.mod @@ -0,0 +1,11 @@ +module github.com/fixme_my_friend/hw04_lru_cache + +go 1.22 + +require github.com/stretchr/testify v1.7.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/hw04_lru_cache/go.sum b/hw04_lru_cache/go.sum new file mode 100644 index 0000000..c221f64 --- /dev/null +++ b/hw04_lru_cache/go.sum @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hw04_lru_cache/list.go b/hw04_lru_cache/list.go new file mode 100644 index 0000000..928ebb5 --- /dev/null +++ b/hw04_lru_cache/list.go @@ -0,0 +1,26 @@ +package hw04lrucache + +type List interface { + Len() int + Front() *ListItem + Back() *ListItem + PushFront(v interface{}) *ListItem + PushBack(v interface{}) *ListItem + Remove(i *ListItem) + MoveToFront(i *ListItem) +} + +type ListItem struct { + Value interface{} + Next *ListItem + Prev *ListItem +} + +type list struct { + List // Remove me after realization. + // Place your code here. +} + +func NewList() List { + return new(list) +} diff --git a/hw04_lru_cache/list_test.go b/hw04_lru_cache/list_test.go new file mode 100644 index 0000000..e8c93b2 --- /dev/null +++ b/hw04_lru_cache/list_test.go @@ -0,0 +1,51 @@ +package hw04lrucache + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestList(t *testing.T) { + t.Run("empty list", func(t *testing.T) { + l := NewList() + + require.Equal(t, 0, l.Len()) + require.Nil(t, l.Front()) + require.Nil(t, l.Back()) + }) + + t.Run("complex", func(t *testing.T) { + l := NewList() + + l.PushFront(10) // [10] + l.PushBack(20) // [10, 20] + l.PushBack(30) // [10, 20, 30] + require.Equal(t, 3, l.Len()) + + middle := l.Front().Next // 20 + l.Remove(middle) // [10, 30] + require.Equal(t, 2, l.Len()) + + for i, v := range [...]int{40, 50, 60, 70, 80} { + if i%2 == 0 { + l.PushFront(v) + } else { + l.PushBack(v) + } + } // [80, 60, 40, 10, 30, 50, 70] + + require.Equal(t, 7, l.Len()) + require.Equal(t, 80, l.Front().Value) + require.Equal(t, 70, l.Back().Value) + + l.MoveToFront(l.Front()) // [80, 60, 40, 10, 30, 50, 70] + l.MoveToFront(l.Back()) // [70, 80, 60, 40, 10, 30, 50] + + elems := make([]int, 0, l.Len()) + for i := l.Front(); i != nil; i = i.Next { + elems = append(elems, i.Value.(int)) + } + require.Equal(t, []int{70, 80, 60, 40, 10, 30, 50}, elems) + }) +} diff --git a/hw05_parallel_execution/.sync b/hw05_parallel_execution/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw05_parallel_execution/README.md b/hw05_parallel_execution/README.md new file mode 100644 index 0000000..6c6901f --- /dev/null +++ b/hw05_parallel_execution/README.md @@ -0,0 +1,84 @@ +## Домашнее задание №5 «Параллельное исполнение» +Необходимо написать функцию для параллельного выполнения заданий в n параллельных горутинах: +* количество создаваемых горутин не должно зависеть от числа заданий, т.е. функция должна запускать n горутин для конкурентной обработки заданий и, возможно, еще несколько вспомогательных горутин; +* функция должна останавливать свою работу, если произошло m ошибок; +* после завершения работы функции (успешного или из-за превышения m) не должно оставаться работающих горутин. + +Нужно учесть, что задания могут выполняться разное время, а длина списка задач +`len(tasks)` может быть больше или меньше n. + +Значение m <= 0 трактуется на усмотрение программиста: +- или это знак игнорировать ошибки в принципе; +- или считать это как "максимум 0 ошибок", значит функция всегда будет возвращать +`ErrErrorsLimitExceeded`; +- на эту логику следует написать юнит-тест. + +Граничные случаи: +* если задачи работают без ошибок, то выполнятся `len(tasks)` задач, т.е. все задачи; +* если в первых выполненных m задачах (или вообще всех) происходят ошибки, то всего выполнится не более n+m задач. + + +**(*) Дополнительное задание: написать тест на concurrency без time.Sleep** + +Придумайте тест, который проверит concurrency другим способом. + +Текущий тест "tasks without errors" использует time.Sleep и подсчет времени выполнения, чтобы сделать вывод о конкурентности использования. Проблема тестов на слипчиках в том, что на CI часто не хватает CPU и подобные тесты работают нестабильно. + +Подсказка: используйте `require.Eventually`. + +--- +#### Пример +Имеем 10 задач, n=4 воркера, m=2 ошибки. +- Запускаем: +``` +--------------ok (узнал, что лимит превышен и остановился) +-----------err +-------err +--------------------ok +``` +Выполнится 4 задачи (2 успешно) <= 4 + 2, остальные задачи  не берем. + +- Другая ситуация, работающие воркеры успели еще взять задач: +``` +------ok--------ok (узнал, что лимит превышен и остановился) +-----------err +---err +--------ok-------ok +``` +Выполнится 6 задач (4 успешно) <= 4 + 2, остальные задачи не берем. + +- Ошибок не было: +``` +-------ok-----ok-----ok-----ok (1 воркер выполнил 4 задачи) +-----------ok-------------ok (2 воркер выполнил 2 задачи) +-----ok---------ok---------ok (3 воркер выполнил 3 задачи) +--------------------ok (4 воркер выполнил 1 задачу) +``` +Выполнится 10 задач (10 успешно): задач не осталось, воркеры остановились. + +--- + +При необходимости можно выделять дополнительные функции / ошибки. + +### Критерии оценки +- Пайплайн зелёный - 4 балла +- Добавлены новые юнит-тесты - до 4 баллов +- Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов + +### Подсказки +- https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem +- `sync.WaitGroup` +- `go test -v -race -count=100 .` + +### Частые ошибки +1) `racedetector` ругается на строчку с ассертом в тестах: +- простой случай: после выхода из `Run` остаются висячие горутины, отсюда и получаем `data race` - +ассерт в тестах неатомарно обращается к `runTasksCount`, в то время как зомби-горутины атомарно пытаюся её поменять. +- случай посложнее: один тест завершается успешно, но висячие горутины, им порожденные, аффектят ассерты в +последующих тестах. +2) Запускаются лишние горутины (инструкции `go`). Их количество за все время работы `Run` должно быть n (плюс, возможно, еще одна-две, если по другому не получается). В некоторых решениях ошибочно контроллируется количество одновременно работающих, а не общее количество. + +**Решение**: внимательно посмотреть места выхода из функции и гарантировать, что все порождённые вами горутины +завершились к этому моменту. diff --git a/hw05_parallel_execution/go.mod b/hw05_parallel_execution/go.mod new file mode 100644 index 0000000..f1aa31c --- /dev/null +++ b/hw05_parallel_execution/go.mod @@ -0,0 +1,16 @@ +module github.com/fixme_my_friend/hw05_parallel_execution + +go 1.22 + +require ( + github.com/stretchr/testify v1.7.0 + go.uber.org/goleak v1.1.10 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect + golang.org/x/tools v0.1.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/hw05_parallel_execution/go.sum b/hw05_parallel_execution/go.sum new file mode 100644 index 0000000..9ef2607 --- /dev/null +++ b/hw05_parallel_execution/go.sum @@ -0,0 +1,55 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hw05_parallel_execution/run.go b/hw05_parallel_execution/run.go new file mode 100644 index 0000000..9e3193f --- /dev/null +++ b/hw05_parallel_execution/run.go @@ -0,0 +1,15 @@ +package hw05parallelexecution + +import ( + "errors" +) + +var ErrErrorsLimitExceeded = errors.New("errors limit exceeded") + +type Task func() error + +// Run starts tasks in n goroutines and stops its work when receiving m errors from tasks. +func Run(tasks []Task, n, m int) error { + // Place your code here. + return nil +} diff --git a/hw05_parallel_execution/run_test.go b/hw05_parallel_execution/run_test.go new file mode 100644 index 0000000..a7069d7 --- /dev/null +++ b/hw05_parallel_execution/run_test.go @@ -0,0 +1,70 @@ +package hw05parallelexecution + +import ( + "errors" + "fmt" + "math/rand" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/require" + "go.uber.org/goleak" +) + +func TestRun(t *testing.T) { + defer goleak.VerifyNone(t) + + t.Run("if were errors in first M tasks, than finished not more N+M tasks", func(t *testing.T) { + tasksCount := 50 + tasks := make([]Task, 0, tasksCount) + + var runTasksCount int32 + + for i := 0; i < tasksCount; i++ { + err := fmt.Errorf("error from task %d", i) + tasks = append(tasks, func() error { + time.Sleep(time.Millisecond * time.Duration(rand.Intn(100))) + atomic.AddInt32(&runTasksCount, 1) + return err + }) + } + + workersCount := 10 + maxErrorsCount := 23 + err := Run(tasks, workersCount, maxErrorsCount) + + require.Truef(t, errors.Is(err, ErrErrorsLimitExceeded), "actual err - %v", err) + require.LessOrEqual(t, runTasksCount, int32(workersCount+maxErrorsCount), "extra tasks were started") + }) + + t.Run("tasks without errors", func(t *testing.T) { + tasksCount := 50 + tasks := make([]Task, 0, tasksCount) + + var runTasksCount int32 + var sumTime time.Duration + + for i := 0; i < tasksCount; i++ { + taskSleep := time.Millisecond * time.Duration(rand.Intn(100)) + sumTime += taskSleep + + tasks = append(tasks, func() error { + time.Sleep(taskSleep) + atomic.AddInt32(&runTasksCount, 1) + return nil + }) + } + + workersCount := 5 + maxErrorsCount := 1 + + start := time.Now() + err := Run(tasks, workersCount, maxErrorsCount) + elapsedTime := time.Since(start) + require.NoError(t, err) + + require.Equal(t, runTasksCount, int32(tasksCount), "not all tasks were completed") + require.LessOrEqual(t, int64(elapsedTime), int64(sumTime/2), "tasks were run sequentially?") + }) +} diff --git a/hw06_pipeline_execution/.sync b/hw06_pipeline_execution/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw06_pipeline_execution/README.md b/hw06_pipeline_execution/README.md new file mode 100644 index 0000000..c6ba9d8 --- /dev/null +++ b/hw06_pipeline_execution/README.md @@ -0,0 +1,38 @@ +## Домашнее задание №6 «Пайплайн» +Необходимо реализовать функцию для запуска конкуррентного пайплайна, состоящего из стейджей. + +Стейдж - функция, принимающая канал на чтение и отдающая канал на чтение, внутри в горутине берущая данные из входного канала, выполняющая полезную работу и отдающая результат в выходной канал: +```golang +func Stage(in <-chan interface{}) (out <-chan interface{}) { + out = make(chan interface{}) + go func() { /* Some work */ }() + return out +} +``` + +Особенность пайплайна в том, что обработка последующего элемента входных данных должна +происходить **без ожидания завершения всего пайплайна** для текущего элемента. + +Т.е. пайплан из 4 функций по 100 мс каждая для 5 входных элементов **должен выполняться +гораздо быстрее**, чем за 2 секунды (4 * 100 мс * 5). + +Также **должна быть реализована возможность остановить пайплайн** через +дополнительный сигнальный канал (`done`/`terminate`/etc.). + +При необходимости можно выделять дополнительные функции. + +**Нельзя менять сигнатуры исходных функций.** + +Для большего понимания см. тесты. + +### Критерии оценки +- CI-пайплайн зелёный - 5 баллов +- Добавлены новые юнит-тесты - до 2 баллов +- Проходит тест TestAllStageStop - 2 балла +- Понятность и чистота кода - до 1 баллов + +#### Зачёт от 7 баллов + +### Подсказки +- https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables +- `go test -v -race -count=100 .` diff --git a/hw06_pipeline_execution/go.mod b/hw06_pipeline_execution/go.mod new file mode 100644 index 0000000..af74e48 --- /dev/null +++ b/hw06_pipeline_execution/go.mod @@ -0,0 +1,11 @@ +module github.com/fixme_my_friend/hw06_pipeline_execution + +go 1.22 + +require github.com/stretchr/testify v1.7.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/hw06_pipeline_execution/go.sum b/hw06_pipeline_execution/go.sum new file mode 100644 index 0000000..c221f64 --- /dev/null +++ b/hw06_pipeline_execution/go.sum @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hw06_pipeline_execution/pipeline.go b/hw06_pipeline_execution/pipeline.go new file mode 100644 index 0000000..9044486 --- /dev/null +++ b/hw06_pipeline_execution/pipeline.go @@ -0,0 +1,14 @@ +package hw06pipelineexecution + +type ( + In = <-chan interface{} + Out = In + Bi = chan interface{} +) + +type Stage func(in In) (out Out) + +func ExecutePipeline(in In, done In, stages ...Stage) Out { + // Place your code here. + return nil +} diff --git a/hw06_pipeline_execution/pipeline_test.go b/hw06_pipeline_execution/pipeline_test.go new file mode 100644 index 0000000..1abdff7 --- /dev/null +++ b/hw06_pipeline_execution/pipeline_test.go @@ -0,0 +1,155 @@ +package hw06pipelineexecution + +import ( + "strconv" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +const ( + sleepPerStage = time.Millisecond * 100 + fault = sleepPerStage / 2 +) + +var isFullTesting = true + +func TestPipeline(t *testing.T) { + // Stage generator + g := func(_ string, f func(v interface{}) interface{}) Stage { + return func(in In) Out { + out := make(Bi) + go func() { + defer close(out) + for v := range in { + time.Sleep(sleepPerStage) + out <- f(v) + } + }() + return out + } + } + + stages := []Stage{ + g("Dummy", func(v interface{}) interface{} { return v }), + g("Multiplier (* 2)", func(v interface{}) interface{} { return v.(int) * 2 }), + g("Adder (+ 100)", func(v interface{}) interface{} { return v.(int) + 100 }), + g("Stringifier", func(v interface{}) interface{} { return strconv.Itoa(v.(int)) }), + } + + t.Run("simple case", func(t *testing.T) { + in := make(Bi) + data := []int{1, 2, 3, 4, 5} + + go func() { + for _, v := range data { + in <- v + } + close(in) + }() + + result := make([]string, 0, 10) + start := time.Now() + for s := range ExecutePipeline(in, nil, stages...) { + result = append(result, s.(string)) + } + elapsed := time.Since(start) + + require.Equal(t, []string{"102", "104", "106", "108", "110"}, result) + require.Less(t, + int64(elapsed), + // ~0.8s for processing 5 values in 4 stages (100ms every) concurrently + int64(sleepPerStage)*int64(len(stages)+len(data)-1)+int64(fault)) + }) + + t.Run("done case", func(t *testing.T) { + in := make(Bi) + done := make(Bi) + data := []int{1, 2, 3, 4, 5} + + // Abort after 200ms + abortDur := sleepPerStage * 2 + go func() { + <-time.After(abortDur) + close(done) + }() + + go func() { + for _, v := range data { + in <- v + } + close(in) + }() + + result := make([]string, 0, 10) + start := time.Now() + for s := range ExecutePipeline(in, done, stages...) { + result = append(result, s.(string)) + } + elapsed := time.Since(start) + + require.Len(t, result, 0) + require.Less(t, int64(elapsed), int64(abortDur)+int64(fault)) + }) +} + +func TestAllStageStop(t *testing.T) { + if !isFullTesting { + return + } + wg := sync.WaitGroup{} + // Stage generator + g := func(_ string, f func(v interface{}) interface{}) Stage { + return func(in In) Out { + out := make(Bi) + wg.Add(1) + go func() { + defer wg.Done() + defer close(out) + for v := range in { + time.Sleep(sleepPerStage) + out <- f(v) + } + }() + return out + } + } + + stages := []Stage{ + g("Dummy", func(v interface{}) interface{} { return v }), + g("Multiplier (* 2)", func(v interface{}) interface{} { return v.(int) * 2 }), + g("Adder (+ 100)", func(v interface{}) interface{} { return v.(int) + 100 }), + g("Stringifier", func(v interface{}) interface{} { return strconv.Itoa(v.(int)) }), + } + + t.Run("done case", func(t *testing.T) { + in := make(Bi) + done := make(Bi) + data := []int{1, 2, 3, 4, 5} + + // Abort after 200ms + abortDur := sleepPerStage * 2 + go func() { + <-time.After(abortDur) + close(done) + }() + + go func() { + for _, v := range data { + in <- v + } + close(in) + }() + + result := make([]string, 0, 10) + for s := range ExecutePipeline(in, done, stages...) { + result = append(result, s.(string)) + } + wg.Wait() + + require.Len(t, result, 0) + + }) +} diff --git a/hw07_file_copying/.sync b/hw07_file_copying/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw07_file_copying/README.md b/hw07_file_copying/README.md new file mode 100644 index 0000000..7dd9f06 --- /dev/null +++ b/hw07_file_copying/README.md @@ -0,0 +1,37 @@ +## Домашнее задание №7 «Утилита для копирования файлов» +Необходимо реализовать утилиту копирования файлов (упрощенный аналог `dd`). + +Тулза должна принимать следующие аргументы: +* путь к исходному файлу (`-from`); +* путь к копии (`-to`); +* отступ в источнике (`-offset`), по умолчанию - 0; +* количество копируемых байт (`-limit`), по умолчанию - 0 (весь файл из `-from`). + +Особенности: +* offset больше, чем размер файла - невалидная ситуация; +* limit больше, чем размер файла - валидная ситуация, копируется исходный файл до его EOF; +* программа может НЕ обрабатывать файлы, у которых неизвестна длина (например, /dev/urandom); + +Также необходимо выводить в консоль прогресс копирования в процентах (%), +допускается использовать для этого стороннюю библиотеку. + +Юнит-тесты могут использовать файлы из `testdata` (разрешено добавить свои, но запрещено удалять имеющиеся) +и должны чистить за собой создаваемые файлы (или работать в `/tmp`). + +При необходимости можно выделять дополнительные функции / ошибки. + +**(*) Дополнительное задание: реализовать прогресс-бар самостоятельно.** + +### Критерии оценки +- Пайплайн зелёный - 4 балла +- Добавлены юнит-тесты - до 4 баллов +- Понятность и чистота кода - до 2 баллов +- Дополнительное задание на баллы не влияет + +#### Зачёт от 7 баллов + +### Подсказки +- `github.com/cheggaaa/pb` +- `os.OpenFile`, `os.Create`, `os.FileMode` +- `io.CopyN` +- `os.CreateTemp` diff --git a/hw07_file_copying/copy.go b/hw07_file_copying/copy.go new file mode 100644 index 0000000..1ddf87b --- /dev/null +++ b/hw07_file_copying/copy.go @@ -0,0 +1,15 @@ +package main + +import ( + "errors" +) + +var ( + ErrUnsupportedFile = errors.New("unsupported file") + ErrOffsetExceedsFileSize = errors.New("offset exceeds file size") +) + +func Copy(fromPath, toPath string, offset, limit int64) error { + // Place your code here. + return nil +} diff --git a/hw07_file_copying/copy_test.go b/hw07_file_copying/copy_test.go new file mode 100644 index 0000000..e070942 --- /dev/null +++ b/hw07_file_copying/copy_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func TestCopy(t *testing.T) { + // Place your code here. +} diff --git a/hw07_file_copying/go.mod b/hw07_file_copying/go.mod new file mode 100644 index 0000000..adf581e --- /dev/null +++ b/hw07_file_copying/go.mod @@ -0,0 +1,3 @@ +module github.com/fixme_my_friend/hw07_file_copying + +go 1.22 diff --git a/hw07_file_copying/go.sum b/hw07_file_copying/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/hw07_file_copying/main.go b/hw07_file_copying/main.go new file mode 100644 index 0000000..515e844 --- /dev/null +++ b/hw07_file_copying/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "flag" +) + +var ( + from, to string + limit, offset int64 +) + +func init() { + flag.StringVar(&from, "from", "", "file to read from") + flag.StringVar(&to, "to", "", "file to write to") + flag.Int64Var(&limit, "limit", 0, "limit of bytes to copy") + flag.Int64Var(&offset, "offset", 0, "offset in input file") +} + +func main() { + flag.Parse() + // Place your code here. +} diff --git a/hw07_file_copying/test.sh b/hw07_file_copying/test.sh new file mode 100755 index 0000000..47e87ae --- /dev/null +++ b/hw07_file_copying/test.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -xeuo pipefail + +go build -o go-cp + +./go-cp -from testdata/input.txt -to out.txt +cmp out.txt testdata/out_offset0_limit0.txt + +./go-cp -from testdata/input.txt -to out.txt -limit 10 +cmp out.txt testdata/out_offset0_limit10.txt + +./go-cp -from testdata/input.txt -to out.txt -limit 1000 +cmp out.txt testdata/out_offset0_limit1000.txt + +./go-cp -from testdata/input.txt -to out.txt -limit 10000 +cmp out.txt testdata/out_offset0_limit10000.txt + +./go-cp -from testdata/input.txt -to out.txt -offset 100 -limit 1000 +cmp out.txt testdata/out_offset100_limit1000.txt + +./go-cp -from testdata/input.txt -to out.txt -offset 6000 -limit 1000 +cmp out.txt testdata/out_offset6000_limit1000.txt + +rm -f go-cp out.txt +echo "PASS" diff --git a/hw07_file_copying/testdata/input.txt b/hw07_file_copying/testdata/input.txt new file mode 100644 index 0000000..cfa3ab5 --- /dev/null +++ b/hw07_file_copying/testdata/input.txt @@ -0,0 +1,125 @@ +Go +Documents +Packages +The Project +Help +Blog +Play +Search + +Getting Started +Install the Go tools +Test your installation +Installing extra Go versions +Uninstalling Go +Getting help +Download the Go distribution +Download Go +Click here to visit the downloads page +Official binary distributions are available for the FreeBSD (release 10-STABLE and above), Linux, macOS (10.10 and above), and Windows operating systems and the 32-bit (386) and 64-bit (amd64) x86 processor architectures. + +If a binary distribution is not available for your combination of operating system and architecture, try installing from source or installing gccgo instead of gc. + +System requirements +Go binary distributions are available for these supported operating systems and architectures. Please ensure your system meets these requirements before proceeding. If your OS or architecture is not on the list, you may be able to install from source or use gccgo instead. + +Operating system Architectures Notes +FreeBSD 10.3 or later amd64, 386 Debian GNU/kFreeBSD not supported +Linux 2.6.23 or later with glibc amd64, 386, arm, arm64, +s390x, ppc64le CentOS/RHEL 5.x not supported. +Install from source for other libc. +macOS 10.10 or later amd64 use the clang or gcc† that comes with Xcode‡ for cgo support +Windows 7, Server 2008R2 or later amd64, 386 use MinGW (386) or MinGW-W64 (amd64) gcc†. +No need for cygwin or msys. +†A C compiler is required only if you plan to use cgo. +‡You only need to install the command line tools for Xcode. If you have already installed Xcode 4.3+, you can install it from the Components tab of the Downloads preferences panel. + +Install the Go tools +If you are upgrading from an older version of Go you must first remove the existing version. + +Linux, macOS, and FreeBSD tarballs +Download the archive and extract it into /usr/local, creating a Go tree in /usr/local/go. For example: + +tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz +Choose the archive file appropriate for your installation. For instance, if you are installing Go version 1.2.1 for 64-bit x86 on Linux, the archive you want is called go1.2.1.linux-amd64.tar.gz. + +(Typically these commands must be run as root or through sudo.) + +Add /usr/local/go/bin to the PATH environment variable. You can do this by adding this line to your /etc/profile (for a system-wide installation) or $HOME/.profile: + +export PATH=$PATH:/usr/local/go/bin +Note: changes made to a profile file may not apply until the next time you log into your computer. To apply the changes immediately, just run the shell commands directly or execute them from the profile using a command such as source $HOME/.profile. + +macOS package installer +Download the package file, open it, and follow the prompts to install the Go tools. The package installs the Go distribution to /usr/local/go. + +The package should put the /usr/local/go/bin directory in your PATH environment variable. You may need to restart any open Terminal sessions for the change to take effect. + +Windows +The Go project provides two installation options for Windows users (besides installing from source): a zip archive that requires you to set some environment variables and an MSI installer that configures your installation automatically. + +MSI installer +Open the MSI file and follow the prompts to install the Go tools. By default, the installer puts the Go distribution in c:\Go. + +The installer should put the c:\Go\bin directory in your PATH environment variable. You may need to restart any open command prompts for the change to take effect. + +Zip archive +Download the zip file and extract it into the directory of your choice (we suggest c:\Go). + +Add the bin subdirectory of your Go root (for example, c:\Go\bin) to your PATH environment variable. + +Setting environment variables under Windows +Under Windows, you may set environment variables through the "Environment Variables" button on the "Advanced" tab of the "System" control panel. Some versions of Windows provide this control panel through the "Advanced System Settings" option inside the "System" control panel. + +Test your installation +Check that Go is installed correctly by setting up a workspace and building a simple program, as follows. + +Create your workspace directory, $HOME/go. (If you'd like to use a different directory, you will need to set the GOPATH environment variable.) + +Next, make the directory src/hello inside your workspace, and in that directory create a file named hello.go that looks like: + +package main + +import "fmt" + +func main() { + fmt.Printf("hello, world\n") +} +Then build it with the go tool: + +$ cd $HOME/go/src/hello +$ go build +The command above will build an executable named hello in the directory alongside your source code. Execute it to see the greeting: + +$ ./hello +hello, world +If you see the "hello, world" message then your Go installation is working. + +You can run go install to install the binary into your workspace's bin directory or go clean -i to remove it. + +Before rushing off to write Go code please read the How to Write Go Code document, which describes some essential concepts about using the Go tools. + +Installing extra Go versions +It may be useful to have multiple Go versions installed on the same machine, for example, to ensure that a package's tests pass on multiple Go versions. Once you have one Go version installed, you can install another (such as 1.10.7) as follows: + +$ go get golang.org/dl/go1.10.7 +$ go1.10.7 download +The newly downloaded version can be used like go: + +$ go1.10.7 version +go version go1.10.7 linux/amd64 +All Go versions available via this method are listed on the download page. You can find where each of these extra Go versions is installed by looking at its GOROOT; for example, go1.10.7 env GOROOT. To uninstall a downloaded version, just remove its GOROOT directory and the goX.Y.Z binary. + +Uninstalling Go +To remove an existing Go installation from your system delete the go directory. This is usually /usr/local/go under Linux, macOS, and FreeBSD or c:\Go under Windows. + +You should also remove the Go bin directory from your PATH environment variable. Under Linux and FreeBSD you should edit /etc/profile or $HOME/.profile. If you installed Go with the macOS package then you should remove the /etc/paths.d/go file. Windows users should read the section about setting environment variables under Windows. + +Getting help +For help, see the list of Go mailing lists, forums, and places to chat. + +Report bugs either by running “go bug”, or manually at the Go issue tracker. + +The Go Gopher +Copyright Terms of Service Privacy Policy Report a website issue +Supported by Google diff --git a/hw07_file_copying/testdata/out_offset0_limit0.txt b/hw07_file_copying/testdata/out_offset0_limit0.txt new file mode 100644 index 0000000..cfa3ab5 --- /dev/null +++ b/hw07_file_copying/testdata/out_offset0_limit0.txt @@ -0,0 +1,125 @@ +Go +Documents +Packages +The Project +Help +Blog +Play +Search + +Getting Started +Install the Go tools +Test your installation +Installing extra Go versions +Uninstalling Go +Getting help +Download the Go distribution +Download Go +Click here to visit the downloads page +Official binary distributions are available for the FreeBSD (release 10-STABLE and above), Linux, macOS (10.10 and above), and Windows operating systems and the 32-bit (386) and 64-bit (amd64) x86 processor architectures. + +If a binary distribution is not available for your combination of operating system and architecture, try installing from source or installing gccgo instead of gc. + +System requirements +Go binary distributions are available for these supported operating systems and architectures. Please ensure your system meets these requirements before proceeding. If your OS or architecture is not on the list, you may be able to install from source or use gccgo instead. + +Operating system Architectures Notes +FreeBSD 10.3 or later amd64, 386 Debian GNU/kFreeBSD not supported +Linux 2.6.23 or later with glibc amd64, 386, arm, arm64, +s390x, ppc64le CentOS/RHEL 5.x not supported. +Install from source for other libc. +macOS 10.10 or later amd64 use the clang or gcc† that comes with Xcode‡ for cgo support +Windows 7, Server 2008R2 or later amd64, 386 use MinGW (386) or MinGW-W64 (amd64) gcc†. +No need for cygwin or msys. +†A C compiler is required only if you plan to use cgo. +‡You only need to install the command line tools for Xcode. If you have already installed Xcode 4.3+, you can install it from the Components tab of the Downloads preferences panel. + +Install the Go tools +If you are upgrading from an older version of Go you must first remove the existing version. + +Linux, macOS, and FreeBSD tarballs +Download the archive and extract it into /usr/local, creating a Go tree in /usr/local/go. For example: + +tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz +Choose the archive file appropriate for your installation. For instance, if you are installing Go version 1.2.1 for 64-bit x86 on Linux, the archive you want is called go1.2.1.linux-amd64.tar.gz. + +(Typically these commands must be run as root or through sudo.) + +Add /usr/local/go/bin to the PATH environment variable. You can do this by adding this line to your /etc/profile (for a system-wide installation) or $HOME/.profile: + +export PATH=$PATH:/usr/local/go/bin +Note: changes made to a profile file may not apply until the next time you log into your computer. To apply the changes immediately, just run the shell commands directly or execute them from the profile using a command such as source $HOME/.profile. + +macOS package installer +Download the package file, open it, and follow the prompts to install the Go tools. The package installs the Go distribution to /usr/local/go. + +The package should put the /usr/local/go/bin directory in your PATH environment variable. You may need to restart any open Terminal sessions for the change to take effect. + +Windows +The Go project provides two installation options for Windows users (besides installing from source): a zip archive that requires you to set some environment variables and an MSI installer that configures your installation automatically. + +MSI installer +Open the MSI file and follow the prompts to install the Go tools. By default, the installer puts the Go distribution in c:\Go. + +The installer should put the c:\Go\bin directory in your PATH environment variable. You may need to restart any open command prompts for the change to take effect. + +Zip archive +Download the zip file and extract it into the directory of your choice (we suggest c:\Go). + +Add the bin subdirectory of your Go root (for example, c:\Go\bin) to your PATH environment variable. + +Setting environment variables under Windows +Under Windows, you may set environment variables through the "Environment Variables" button on the "Advanced" tab of the "System" control panel. Some versions of Windows provide this control panel through the "Advanced System Settings" option inside the "System" control panel. + +Test your installation +Check that Go is installed correctly by setting up a workspace and building a simple program, as follows. + +Create your workspace directory, $HOME/go. (If you'd like to use a different directory, you will need to set the GOPATH environment variable.) + +Next, make the directory src/hello inside your workspace, and in that directory create a file named hello.go that looks like: + +package main + +import "fmt" + +func main() { + fmt.Printf("hello, world\n") +} +Then build it with the go tool: + +$ cd $HOME/go/src/hello +$ go build +The command above will build an executable named hello in the directory alongside your source code. Execute it to see the greeting: + +$ ./hello +hello, world +If you see the "hello, world" message then your Go installation is working. + +You can run go install to install the binary into your workspace's bin directory or go clean -i to remove it. + +Before rushing off to write Go code please read the How to Write Go Code document, which describes some essential concepts about using the Go tools. + +Installing extra Go versions +It may be useful to have multiple Go versions installed on the same machine, for example, to ensure that a package's tests pass on multiple Go versions. Once you have one Go version installed, you can install another (such as 1.10.7) as follows: + +$ go get golang.org/dl/go1.10.7 +$ go1.10.7 download +The newly downloaded version can be used like go: + +$ go1.10.7 version +go version go1.10.7 linux/amd64 +All Go versions available via this method are listed on the download page. You can find where each of these extra Go versions is installed by looking at its GOROOT; for example, go1.10.7 env GOROOT. To uninstall a downloaded version, just remove its GOROOT directory and the goX.Y.Z binary. + +Uninstalling Go +To remove an existing Go installation from your system delete the go directory. This is usually /usr/local/go under Linux, macOS, and FreeBSD or c:\Go under Windows. + +You should also remove the Go bin directory from your PATH environment variable. Under Linux and FreeBSD you should edit /etc/profile or $HOME/.profile. If you installed Go with the macOS package then you should remove the /etc/paths.d/go file. Windows users should read the section about setting environment variables under Windows. + +Getting help +For help, see the list of Go mailing lists, forums, and places to chat. + +Report bugs either by running “go bug”, or manually at the Go issue tracker. + +The Go Gopher +Copyright Terms of Service Privacy Policy Report a website issue +Supported by Google diff --git a/hw07_file_copying/testdata/out_offset0_limit10.txt b/hw07_file_copying/testdata/out_offset0_limit10.txt new file mode 100644 index 0000000..2f1e579 --- /dev/null +++ b/hw07_file_copying/testdata/out_offset0_limit10.txt @@ -0,0 +1,2 @@ +Go +Documen \ No newline at end of file diff --git a/hw07_file_copying/testdata/out_offset0_limit1000.txt b/hw07_file_copying/testdata/out_offset0_limit1000.txt new file mode 100644 index 0000000..a1510e7 --- /dev/null +++ b/hw07_file_copying/testdata/out_offset0_limit1000.txt @@ -0,0 +1,27 @@ +Go +Documents +Packages +The Project +Help +Blog +Play +Search + +Getting Started +Install the Go tools +Test your installation +Installing extra Go versions +Uninstalling Go +Getting help +Download the Go distribution +Download Go +Click here to visit the downloads page +Official binary distributions are available for the FreeBSD (release 10-STABLE and above), Linux, macOS (10.10 and above), and Windows operating systems and the 32-bit (386) and 64-bit (amd64) x86 processor architectures. + +If a binary distribution is not available for your combination of operating system and architecture, try installing from source or installing gccgo instead of gc. + +System requirements +Go binary distributions are available for these supported operating systems and architectures. Please ensure your system meets these requirements before proceeding. If your OS or architecture is not on the list, you may be able to install from source or use gccgo instead. + +Operating system Architectures Notes +FreeBSD 10.3 or later amd64 \ No newline at end of file diff --git a/hw07_file_copying/testdata/out_offset0_limit10000.txt b/hw07_file_copying/testdata/out_offset0_limit10000.txt new file mode 100644 index 0000000..cfa3ab5 --- /dev/null +++ b/hw07_file_copying/testdata/out_offset0_limit10000.txt @@ -0,0 +1,125 @@ +Go +Documents +Packages +The Project +Help +Blog +Play +Search + +Getting Started +Install the Go tools +Test your installation +Installing extra Go versions +Uninstalling Go +Getting help +Download the Go distribution +Download Go +Click here to visit the downloads page +Official binary distributions are available for the FreeBSD (release 10-STABLE and above), Linux, macOS (10.10 and above), and Windows operating systems and the 32-bit (386) and 64-bit (amd64) x86 processor architectures. + +If a binary distribution is not available for your combination of operating system and architecture, try installing from source or installing gccgo instead of gc. + +System requirements +Go binary distributions are available for these supported operating systems and architectures. Please ensure your system meets these requirements before proceeding. If your OS or architecture is not on the list, you may be able to install from source or use gccgo instead. + +Operating system Architectures Notes +FreeBSD 10.3 or later amd64, 386 Debian GNU/kFreeBSD not supported +Linux 2.6.23 or later with glibc amd64, 386, arm, arm64, +s390x, ppc64le CentOS/RHEL 5.x not supported. +Install from source for other libc. +macOS 10.10 or later amd64 use the clang or gcc† that comes with Xcode‡ for cgo support +Windows 7, Server 2008R2 or later amd64, 386 use MinGW (386) or MinGW-W64 (amd64) gcc†. +No need for cygwin or msys. +†A C compiler is required only if you plan to use cgo. +‡You only need to install the command line tools for Xcode. If you have already installed Xcode 4.3+, you can install it from the Components tab of the Downloads preferences panel. + +Install the Go tools +If you are upgrading from an older version of Go you must first remove the existing version. + +Linux, macOS, and FreeBSD tarballs +Download the archive and extract it into /usr/local, creating a Go tree in /usr/local/go. For example: + +tar -C /usr/local -xzf go$VERSION.$OS-$ARCH.tar.gz +Choose the archive file appropriate for your installation. For instance, if you are installing Go version 1.2.1 for 64-bit x86 on Linux, the archive you want is called go1.2.1.linux-amd64.tar.gz. + +(Typically these commands must be run as root or through sudo.) + +Add /usr/local/go/bin to the PATH environment variable. You can do this by adding this line to your /etc/profile (for a system-wide installation) or $HOME/.profile: + +export PATH=$PATH:/usr/local/go/bin +Note: changes made to a profile file may not apply until the next time you log into your computer. To apply the changes immediately, just run the shell commands directly or execute them from the profile using a command such as source $HOME/.profile. + +macOS package installer +Download the package file, open it, and follow the prompts to install the Go tools. The package installs the Go distribution to /usr/local/go. + +The package should put the /usr/local/go/bin directory in your PATH environment variable. You may need to restart any open Terminal sessions for the change to take effect. + +Windows +The Go project provides two installation options for Windows users (besides installing from source): a zip archive that requires you to set some environment variables and an MSI installer that configures your installation automatically. + +MSI installer +Open the MSI file and follow the prompts to install the Go tools. By default, the installer puts the Go distribution in c:\Go. + +The installer should put the c:\Go\bin directory in your PATH environment variable. You may need to restart any open command prompts for the change to take effect. + +Zip archive +Download the zip file and extract it into the directory of your choice (we suggest c:\Go). + +Add the bin subdirectory of your Go root (for example, c:\Go\bin) to your PATH environment variable. + +Setting environment variables under Windows +Under Windows, you may set environment variables through the "Environment Variables" button on the "Advanced" tab of the "System" control panel. Some versions of Windows provide this control panel through the "Advanced System Settings" option inside the "System" control panel. + +Test your installation +Check that Go is installed correctly by setting up a workspace and building a simple program, as follows. + +Create your workspace directory, $HOME/go. (If you'd like to use a different directory, you will need to set the GOPATH environment variable.) + +Next, make the directory src/hello inside your workspace, and in that directory create a file named hello.go that looks like: + +package main + +import "fmt" + +func main() { + fmt.Printf("hello, world\n") +} +Then build it with the go tool: + +$ cd $HOME/go/src/hello +$ go build +The command above will build an executable named hello in the directory alongside your source code. Execute it to see the greeting: + +$ ./hello +hello, world +If you see the "hello, world" message then your Go installation is working. + +You can run go install to install the binary into your workspace's bin directory or go clean -i to remove it. + +Before rushing off to write Go code please read the How to Write Go Code document, which describes some essential concepts about using the Go tools. + +Installing extra Go versions +It may be useful to have multiple Go versions installed on the same machine, for example, to ensure that a package's tests pass on multiple Go versions. Once you have one Go version installed, you can install another (such as 1.10.7) as follows: + +$ go get golang.org/dl/go1.10.7 +$ go1.10.7 download +The newly downloaded version can be used like go: + +$ go1.10.7 version +go version go1.10.7 linux/amd64 +All Go versions available via this method are listed on the download page. You can find where each of these extra Go versions is installed by looking at its GOROOT; for example, go1.10.7 env GOROOT. To uninstall a downloaded version, just remove its GOROOT directory and the goX.Y.Z binary. + +Uninstalling Go +To remove an existing Go installation from your system delete the go directory. This is usually /usr/local/go under Linux, macOS, and FreeBSD or c:\Go under Windows. + +You should also remove the Go bin directory from your PATH environment variable. Under Linux and FreeBSD you should edit /etc/profile or $HOME/.profile. If you installed Go with the macOS package then you should remove the /etc/paths.d/go file. Windows users should read the section about setting environment variables under Windows. + +Getting help +For help, see the list of Go mailing lists, forums, and places to chat. + +Report bugs either by running “go bug”, or manually at the Go issue tracker. + +The Go Gopher +Copyright Terms of Service Privacy Policy Report a website issue +Supported by Google diff --git a/hw07_file_copying/testdata/out_offset100_limit1000.txt b/hw07_file_copying/testdata/out_offset100_limit1000.txt new file mode 100644 index 0000000..b2bbb57 --- /dev/null +++ b/hw07_file_copying/testdata/out_offset100_limit1000.txt @@ -0,0 +1,18 @@ +our installation +Installing extra Go versions +Uninstalling Go +Getting help +Download the Go distribution +Download Go +Click here to visit the downloads page +Official binary distributions are available for the FreeBSD (release 10-STABLE and above), Linux, macOS (10.10 and above), and Windows operating systems and the 32-bit (386) and 64-bit (amd64) x86 processor architectures. + +If a binary distribution is not available for your combination of operating system and architecture, try installing from source or installing gccgo instead of gc. + +System requirements +Go binary distributions are available for these supported operating systems and architectures. Please ensure your system meets these requirements before proceeding. If your OS or architecture is not on the list, you may be able to install from source or use gccgo instead. + +Operating system Architectures Notes +FreeBSD 10.3 or later amd64, 386 Debian GNU/kFreeBSD not supported +Linux 2.6.23 or later with glibc amd64, 386, arm, arm64, +s39 \ No newline at end of file diff --git a/hw07_file_copying/testdata/out_offset6000_limit1000.txt b/hw07_file_copying/testdata/out_offset6000_limit1000.txt new file mode 100644 index 0000000..4afb4a5 --- /dev/null +++ b/hw07_file_copying/testdata/out_offset6000_limit1000.txt @@ -0,0 +1,12 @@ +nder Windows. + +You should also remove the Go bin directory from your PATH environment variable. Under Linux and FreeBSD you should edit /etc/profile or $HOME/.profile. If you installed Go with the macOS package then you should remove the /etc/paths.d/go file. Windows users should read the section about setting environment variables under Windows. + +Getting help +For help, see the list of Go mailing lists, forums, and places to chat. + +Report bugs either by running “go bug”, or manually at the Go issue tracker. + +The Go Gopher +Copyright Terms of Service Privacy Policy Report a website issue +Supported by Google diff --git a/hw08_envdir_tool/.sync b/hw08_envdir_tool/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw08_envdir_tool/README.md b/hw08_envdir_tool/README.md new file mode 100644 index 0000000..ec99ccb --- /dev/null +++ b/hw08_envdir_tool/README.md @@ -0,0 +1,48 @@ +## Домашнее задание №8 «Утилита envdir» + +Необходимо реализовать утилиту `envdir` на Go. + +Эта утилита позволяет запускать программы, получая переменные окружения из определенной директории: +- если директория содержит файл с именем `S`, первой строкой которого является `T`, то +`envdir` удаляет переменную среды с именем `S`, если таковая существует, а затем добавляет +переменную среды с именем `S` и значением `T`; +- имя `S` не должно содержать `=`; пробелы и табуляция в конце `T` удаляются; терминальные нули (`0x00`) заменяются на перевод строки (`\n`); +- если файл полностью пустой (длина - 0 байт), то `envdir` удаляет переменную окружения с именем `S`. + +--- +Пример использования: +```bash +$ go-envdir /path/to/env/dir command arg1 arg2 +``` +Если в директории `/path/to/env/dir` содержатся файлы: +* `FOO` с содержимым `123`; +* `BAR` с содержимым `value`, + +то вызов выше эквивалентен вызову +```bash +$ FOO=123 BAR=value command arg1 arg2 +``` +--- + +Также необходимо, чтобы: +* стандартные потоки ввода/вывода/ошибок пробрасывались в вызываемую программу; +* код выхода утилиты совпадал с кодом выхода программы. + +При необходимости можно выделять дополнительные функции / ошибки. + +Юнит-тесты могут использовать файлы из `testdata` или создавать свои директории / файлы, +которые **обязаны** подчищать после своего выполнения. + +### Критерии оценки +- Пайплайн зелёный - 4 балла +- Добавлены юнит-тесты - до 4 баллов +- Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов + +### Подсказки +- https://www.unix.com/man-page/debian/8/envdir/ +- `os.Args` +- `os.ReadDir` +- `bytes.Replace`, `strings.TrimRight` +- `exec.Command` diff --git a/hw08_envdir_tool/env_reader.go b/hw08_envdir_tool/env_reader.go new file mode 100644 index 0000000..92e9823 --- /dev/null +++ b/hw08_envdir_tool/env_reader.go @@ -0,0 +1,16 @@ +package main + +type Environment map[string]EnvValue + +// EnvValue helps to distinguish between empty files and files with the first empty line. +type EnvValue struct { + Value string + NeedRemove bool +} + +// ReadDir reads a specified directory and returns map of env variables. +// Variables represented as files where filename is name of variable, file first line is a value. +func ReadDir(dir string) (Environment, error) { + // Place your code here + return nil, nil +} diff --git a/hw08_envdir_tool/env_reader_test.go b/hw08_envdir_tool/env_reader_test.go new file mode 100644 index 0000000..7962c06 --- /dev/null +++ b/hw08_envdir_tool/env_reader_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func TestReadDir(t *testing.T) { + // Place your code here +} diff --git a/hw08_envdir_tool/executor.go b/hw08_envdir_tool/executor.go new file mode 100644 index 0000000..33589ea --- /dev/null +++ b/hw08_envdir_tool/executor.go @@ -0,0 +1,7 @@ +package main + +// RunCmd runs a command + arguments (cmd) with environment variables from env. +func RunCmd(cmd []string, env Environment) (returnCode int) { + // Place your code here. + return +} diff --git a/hw08_envdir_tool/executor_test.go b/hw08_envdir_tool/executor_test.go new file mode 100644 index 0000000..6402ce3 --- /dev/null +++ b/hw08_envdir_tool/executor_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func TestRunCmd(t *testing.T) { + // Place your code here +} diff --git a/hw08_envdir_tool/go.mod b/hw08_envdir_tool/go.mod new file mode 100644 index 0000000..d400b5f --- /dev/null +++ b/hw08_envdir_tool/go.mod @@ -0,0 +1,3 @@ +module github.com/fixme_my_friend/hw08_envdir_tool + +go 1.22 diff --git a/hw08_envdir_tool/main.go b/hw08_envdir_tool/main.go new file mode 100644 index 0000000..1eca213 --- /dev/null +++ b/hw08_envdir_tool/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + // Place your code here. +} diff --git a/hw08_envdir_tool/test.sh b/hw08_envdir_tool/test.sh new file mode 100755 index 0000000..35e1d36 --- /dev/null +++ b/hw08_envdir_tool/test.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -xeuo pipefail + +go build -o go-envdir + +export HELLO="SHOULD_REPLACE" +export FOO="SHOULD_REPLACE" +export UNSET="SHOULD_REMOVE" +export ADDED="from original env" +export EMPTY="SHOULD_BE_EMPTY" + +result=$(./go-envdir "$(pwd)/testdata/env" "/bin/bash" "$(pwd)/testdata/echo.sh" arg1=1 arg2=2) +expected='HELLO is ("hello") +BAR is (bar) +FOO is ( foo +with new line) +UNSET is () +ADDED is (from original env) +EMPTY is () +arguments are arg1=1 arg2=2' + +[ "${result}" = "${expected}" ] || (echo -e "invalid output: ${result}" && exit 1) + +rm -f go-envdir +echo "PASS" diff --git a/hw08_envdir_tool/testdata/echo.sh b/hw08_envdir_tool/testdata/echo.sh new file mode 100755 index 0000000..274b466 --- /dev/null +++ b/hw08_envdir_tool/testdata/echo.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +echo -e "HELLO is (${HELLO}) +BAR is (${BAR}) +FOO is (${FOO}) +UNSET is (${UNSET}) +ADDED is (${ADDED}) +EMPTY is (${EMPTY}) +arguments are $*" diff --git a/hw08_envdir_tool/testdata/env/BAR b/hw08_envdir_tool/testdata/env/BAR new file mode 100644 index 0000000..9c0135f --- /dev/null +++ b/hw08_envdir_tool/testdata/env/BAR @@ -0,0 +1,2 @@ +bar +PLEASE IGNORE SECOND LINE diff --git a/hw08_envdir_tool/testdata/env/EMPTY b/hw08_envdir_tool/testdata/env/EMPTY new file mode 100644 index 0000000..8d1c8b6 --- /dev/null +++ b/hw08_envdir_tool/testdata/env/EMPTY @@ -0,0 +1 @@ + diff --git a/hw08_envdir_tool/testdata/env/FOO b/hw08_envdir_tool/testdata/env/FOO new file mode 100644 index 0000000..669d05d Binary files /dev/null and b/hw08_envdir_tool/testdata/env/FOO differ diff --git a/hw08_envdir_tool/testdata/env/HELLO b/hw08_envdir_tool/testdata/env/HELLO new file mode 100644 index 0000000..84ed78b --- /dev/null +++ b/hw08_envdir_tool/testdata/env/HELLO @@ -0,0 +1 @@ +"hello" \ No newline at end of file diff --git a/hw08_envdir_tool/testdata/env/UNSET b/hw08_envdir_tool/testdata/env/UNSET new file mode 100644 index 0000000..e69de29 diff --git a/hw09_struct_validator/.sync b/hw09_struct_validator/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw09_struct_validator/README.md b/hw09_struct_validator/README.md new file mode 100644 index 0000000..d0f8b04 --- /dev/null +++ b/hw09_struct_validator/README.md @@ -0,0 +1,65 @@ +## Домашнее задание №9 «Валидатор структур» + +Необходимо реализовать функцию: +```golang +func Validate(v interface{}) error +``` +Она должна валидировать публичные поля входной структуры на основе структурного тэга `validate`. + +Функция может возвращать +- или программную ошибку, произошедшую во время валидации; +- или `ValidationErrors` - ошибку, являющуюся слайсом структур, содержащих имя поля и ошибку его валидации. + +Таким образом, нужно накопить все ошибки валидации, а не прерывать валидацию на первой ошибке. + +Если у поля нет структурных тэгов или нет тэга `validate`, то функция игнорирует его. + +Типы полей, которые обязательно должны поддерживаться: +- `int`, `[]int`; +- `string`, `[]string`. + +_При желании можно дополнительно поддержать любые другие типы (на ваше усмотрение)._ + +Необходимо реализовать следующие валидаторы: +- Для строк: + * `len:32` - длина строки должна быть ровно 32 символа; + * `regexp:\\d+` - согласно регулярному выражению строка должна состоять из цифр + (`\\` - экранирование слэша); + * `in:foo,bar` - строка должна входить в множество строк {"foo", "bar"}. +- Для чисел: + * `min:10` - число не может быть меньше 10; + * `max:20` - число не может быть больше 20; + * `in:256,1024` - число должно входить в множество чисел {256, 1024}; +- Для слайсов валидируется каждый элемент слайса. + +_При желании можно дополнительно добавить парочку новых правил (на ваше усмотрение)._ + +Допускается комбинация валидаторов по логическому "И" с помощью `|`, например: +* `min:0|max:10` - число должно находится в пределах [0, 10]; +* `regexp:\\d+|len:20` - строка должна состоять из цифр и иметь длину 20. + +**(\*) Дополнительное задание: поддержка валидации вложенных по композиции структур.** +```golang +type User struct { + m Meta `validate:"nested"` +} +``` + +### Критерии оценки +- Пайплайн зелёный - 3 балла +- Добавлены юнит-тесты - до 4 баллов +- Понятность и чистота кода - до 3 баллов +- Дополнительное задание на баллы не влияет + +#### Зачёт от 7 баллов + +### Подсказки +- `reflect.StructTag` +- `regexp.Compile` +- `errors.Is` + +### Частые ошибки +- Отсутствует проверка на то, что входной `interface{}` - структура. +- Нет разделения на программные ошибки (неверный тэг, регулярка и пр.) и ошибки валидации. +- Ошибки валидации не вынесены в отдельные переменные и не завраплены. +- Соответственно в тестах не используется `errors.Is` / `errors.As`. diff --git a/hw09_struct_validator/go.mod b/hw09_struct_validator/go.mod new file mode 100644 index 0000000..6699458 --- /dev/null +++ b/hw09_struct_validator/go.mod @@ -0,0 +1,3 @@ +module github.com/fixme_my_friend/hw09_struct_validator + +go 1.22 diff --git a/hw09_struct_validator/validator.go b/hw09_struct_validator/validator.go new file mode 100644 index 0000000..b85fbec --- /dev/null +++ b/hw09_struct_validator/validator.go @@ -0,0 +1,17 @@ +package hw09structvalidator + +type ValidationError struct { + Field string + Err error +} + +type ValidationErrors []ValidationError + +func (v ValidationErrors) Error() string { + panic("implement me") +} + +func Validate(v interface{}) error { + // Place your code here. + return nil +} diff --git a/hw09_struct_validator/validator_test.go b/hw09_struct_validator/validator_test.go new file mode 100644 index 0000000..dd20df1 --- /dev/null +++ b/hw09_struct_validator/validator_test.go @@ -0,0 +1,60 @@ +package hw09structvalidator + +import ( + "encoding/json" + "fmt" + "testing" +) + +type UserRole string + +// Test the function on different structures and other types. +type ( + User struct { + ID string `json:"id" validate:"len:36"` + Name string + Age int `validate:"min:18|max:50"` + Email string `validate:"regexp:^\\w+@\\w+\\.\\w+$"` + Role UserRole `validate:"in:admin,stuff"` + Phones []string `validate:"len:11"` + meta json.RawMessage //nolint:unused + } + + App struct { + Version string `validate:"len:5"` + } + + Token struct { + Header []byte + Payload []byte + Signature []byte + } + + Response struct { + Code int `validate:"in:200,404,500"` + Body string `json:"omitempty"` + } +) + +func TestValidate(t *testing.T) { + tests := []struct { + in interface{} + expectedErr error + }{ + { + // Place your code here. + }, + // ... + // Place your code here. + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("case %d", i), func(t *testing.T) { + tt := tt + t.Parallel() + + // Place your code here. + _ = tt + }) + } +} diff --git a/hw10_program_optimization/.sync b/hw10_program_optimization/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw10_program_optimization/README.md b/hw10_program_optimization/README.md new file mode 100644 index 0000000..d4d6818 --- /dev/null +++ b/hw10_program_optimization/README.md @@ -0,0 +1,52 @@ +## Домашнее задание №10 «Оптимизация программы» + +Вам дан исходный код функции `GetDomainStat(r io.Reader, domain string)`, которая: +* читает построчно из `r` пользовательские данные вида +```text +{"Id":1,"Name":"Howard Mendoza","Username":"0Oliver","Email":"aliquid_qui_ea@Browsedrive.gov","Phone":"6-866-899-36-79","Password":"InAQJvsq","Address":"Blackbird Place 25"} +{"Id":2,"Name":"Brian Olson","Username":"non_quia_id","Email":"FrancesEllis@Quinu.edu","Phone":"237-75-34","Password":"cmEPhX8","Address":"Butterfield Junction 74"} +{"Id":3,"Name":"Justin Oliver Jr. Sr.","Username":"oPerez","Email":"MelissaGutierrez@Twinte.gov","Phone":"106-05-18","Password":"f00GKr9i","Address":"Oak Valley Lane 19"} +``` +(осторожно, в отличие от конкретной строки файл целиком не является валидным JSON); +* подсчитывает количество email-доменов пользователей на основе домена первого уровня `domain`. + +Например, для данных, представленных выше: +```text +GetDomainStat(r, "com") // {} +GetDomainStat(r, "gov") // {"browsedrive": 1, "twinte": 1} +GetDomainStat(r, "edu") // {"quinu": 1} +``` + +Для большего понимания см. исходный код и тесты. + +**Необходимо оптимизировать программу таким образом, чтобы она проходила все тесты.** + +Нельзя: +- изменять сигнатуру функции `GetDomainStat`; +- удалять или изменять существующие юнит-тесты. + +Можно: +- писать любой новый необходимый код; +- удалять имеющийся лишний код (кроме функции `GetDomainStat`); +- использовать сторонние библиотеки по ускорению анмаршалинга JSON; +- добавлять юнит-тесты. + +**Обратите внимание на запуск TestGetDomainStat_Time_And_Memory** +```bash +go test -v -count=1 -timeout=30s -tags bench . +``` + +Здесь используется билд-тэг bench, чтобы отделить обычные тесты от тестов производительности. + +### Оформление пул-риквеста +В идеале к подобным пул-риквестам пишут бенчмарки и прикладывают результаты работы benchstat, чтобы сразу было видно, что стало лучше и насколько. + +### Критерии оценки +- Пайплайн зелёный и нет попытки «обмануть» систему - 4 балла +- Добавлены юнит-тесты - до 3 баллов +- Понятность и чистота кода - до 3 баллов + +### Частые ошибки +- Работа с сырыми байтами, нахождение позиции `"Email"` и пр. вместо ускорения анмаршалинга более поддерживаемыми и понятными средствами. + +#### Зачёт от 7 баллов diff --git a/hw10_program_optimization/go.mod b/hw10_program_optimization/go.mod new file mode 100644 index 0000000..8bccd17 --- /dev/null +++ b/hw10_program_optimization/go.mod @@ -0,0 +1,11 @@ +module github.com/fixme_my_friend/hw10_program_optimization + +go 1.22 + +require github.com/stretchr/testify v1.7.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/hw10_program_optimization/go.sum b/hw10_program_optimization/go.sum new file mode 100644 index 0000000..c221f64 --- /dev/null +++ b/hw10_program_optimization/go.sum @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hw10_program_optimization/stats.go b/hw10_program_optimization/stats.go new file mode 100644 index 0000000..affb108 --- /dev/null +++ b/hw10_program_optimization/stats.go @@ -0,0 +1,66 @@ +package hw10programoptimization + +import ( + "encoding/json" + "fmt" + "io" + "regexp" + "strings" +) + +type User struct { + ID int + Name string + Username string + Email string + Phone string + Password string + Address string +} + +type DomainStat map[string]int + +func GetDomainStat(r io.Reader, domain string) (DomainStat, error) { + u, err := getUsers(r) + if err != nil { + return nil, fmt.Errorf("get users error: %w", err) + } + return countDomains(u, domain) +} + +type users [100_000]User + +func getUsers(r io.Reader) (result users, err error) { + content, err := io.ReadAll(r) + if err != nil { + return + } + + lines := strings.Split(string(content), "\n") + for i, line := range lines { + var user User + if err = json.Unmarshal([]byte(line), &user); err != nil { + return + } + result[i] = user + } + return +} + +func countDomains(u users, domain string) (DomainStat, error) { + result := make(DomainStat) + + for _, user := range u { + matched, err := regexp.Match("\\."+domain, []byte(user.Email)) + if err != nil { + return nil, err + } + + if matched { + num := result[strings.ToLower(strings.SplitN(user.Email, "@", 2)[1])] + num++ + result[strings.ToLower(strings.SplitN(user.Email, "@", 2)[1])] = num + } + } + return result, nil +} diff --git a/hw10_program_optimization/stats_optimization_test.go b/hw10_program_optimization/stats_optimization_test.go new file mode 100644 index 0000000..58b826e --- /dev/null +++ b/hw10_program_optimization/stats_optimization_test.go @@ -0,0 +1,437 @@ +//go:build bench +// +build bench + +package hw10programoptimization + +import ( + "archive/zip" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +const ( + mb uint64 = 1 << 20 + memoryLimit uint64 = 30 * mb + + timeLimit = 300 * time.Millisecond +) + +// go test -v -count=1 -timeout=30s -tags bench . +func TestGetDomainStat_Time_And_Memory(t *testing.T) { + bench := func(b *testing.B) { + b.Helper() + b.StopTimer() + + r, err := zip.OpenReader("testdata/users.dat.zip") + require.NoError(t, err) + defer r.Close() + + require.Equal(t, 1, len(r.File)) + + data, err := r.File[0].Open() + require.NoError(t, err) + + b.StartTimer() + stat, err := GetDomainStat(data, "biz") + b.StopTimer() + require.NoError(t, err) + + require.Equal(t, expectedBizStat, stat) + } + + result := testing.Benchmark(bench) + mem := result.MemBytes + t.Logf("time used: %s / %s", result.T, timeLimit) + t.Logf("memory used: %dMb / %dMb", mem/mb, memoryLimit/mb) + + require.Less(t, int64(result.T), int64(timeLimit), "the program is too slow") + require.Less(t, mem, memoryLimit, "the program is too greedy") +} + +var expectedBizStat = DomainStat{ + "abata.biz": 25, + "abatz.biz": 25, + "agimba.biz": 28, + "agivu.biz": 17, + "aibox.biz": 31, + "ailane.biz": 23, + "aimbo.biz": 25, + "aimbu.biz": 36, + "ainyx.biz": 35, + "aivee.biz": 25, + "avamba.biz": 21, + "avamm.biz": 17, + "avavee.biz": 35, + "avaveo.biz": 30, + "babbleblab.biz": 29, + "babbleopia.biz": 36, + "babbleset.biz": 28, + "babblestorm.biz": 29, + "blognation.biz": 32, + "blogpad.biz": 34, + "blogspan.biz": 21, + "blogtag.biz": 23, + "blogtags.biz": 34, + "blogxs.biz": 35, + "bluejam.biz": 36, + "bluezoom.biz": 27, + "brainbox.biz": 30, + "brainlounge.biz": 38, + "brainsphere.biz": 31, + "brainverse.biz": 39, + "brightbean.biz": 23, + "brightdog.biz": 32, + "browseblab.biz": 31, + "browsebug.biz": 25, + "browsecat.biz": 34, + "browsedrive.biz": 24, + "browsetype.biz": 34, + "browsezoom.biz": 29, + "bubblebox.biz": 19, + "bubblemix.biz": 38, + "bubbletube.biz": 34, + "buzzbean.biz": 26, + "buzzdog.biz": 30, + "buzzshare.biz": 26, + "buzzster.biz": 28, + "camido.biz": 27, + "camimbo.biz": 36, + "centidel.biz": 32, + "centimia.biz": 17, + "centizu.biz": 18, + "chatterbridge.biz": 30, + "chatterpoint.biz": 32, + "cogibox.biz": 30, + "cogidoo.biz": 34, + "cogilith.biz": 24, + "dabfeed.biz": 26, + "dabjam.biz": 30, + "dablist.biz": 30, + "dabshots.biz": 33, + "dabtype.biz": 21, + "dabvine.biz": 26, + "dabz.biz": 19, + "dazzlesphere.biz": 24, + "demimbu.biz": 27, + "demivee.biz": 39, + "demizz.biz": 30, + "devbug.biz": 20, + "devcast.biz": 35, + "devify.biz": 27, + "devpoint.biz": 26, + "devpulse.biz": 27, + "devshare.biz": 30, + "digitube.biz": 30, + "divanoodle.biz": 33, + "divape.biz": 32, + "divavu.biz": 28, + "dynabox.biz": 66, + "dynava.biz": 21, + "dynazzy.biz": 29, + "eabox.biz": 28, + "eadel.biz": 25, + "eamia.biz": 18, + "eare.biz": 30, + "eayo.biz": 30, + "eazzy.biz": 27, + "edgeblab.biz": 29, + "edgeclub.biz": 29, + "edgeify.biz": 36, + "edgepulse.biz": 21, + "edgetag.biz": 24, + "edgewire.biz": 29, + "eidel.biz": 33, + "eimbee.biz": 22, + "einti.biz": 19, + "eire.biz": 28, + "fadeo.biz": 35, + "fanoodle.biz": 23, + "fatz.biz": 30, + "feedbug.biz": 29, + "feedfire.biz": 30, + "feedfish.biz": 35, + "feedmix.biz": 31, + "feednation.biz": 24, + "feedspan.biz": 28, + "fivebridge.biz": 20, + "fivechat.biz": 29, + "fiveclub.biz": 23, + "fivespan.biz": 27, + "flashdog.biz": 20, + "flashpoint.biz": 35, + "flashset.biz": 30, + "flashspan.biz": 32, + "flipbug.biz": 27, + "flipopia.biz": 30, + "flipstorm.biz": 21, + "fliptune.biz": 29, + "gabcube.biz": 29, + "gabspot.biz": 24, + "gabtune.biz": 29, + "gabtype.biz": 29, + "gabvine.biz": 24, + "geba.biz": 24, + "gevee.biz": 23, + "gigabox.biz": 28, + "gigaclub.biz": 25, + "gigashots.biz": 26, + "gigazoom.biz": 29, + "innojam.biz": 26, + "innotype.biz": 27, + "innoz.biz": 24, + "izio.biz": 26, + "jabberbean.biz": 28, + "jabbercube.biz": 31, + "jabbersphere.biz": 55, + "jabberstorm.biz": 22, + "jabbertype.biz": 27, + "jaloo.biz": 35, + "jamia.biz": 33, + "janyx.biz": 33, + "jatri.biz": 18, + "jaxbean.biz": 28, + "jaxnation.biz": 21, + "jaxspan.biz": 27, + "jaxworks.biz": 30, + "jayo.biz": 44, + "jazzy.biz": 32, + "jetpulse.biz": 25, + "jetwire.biz": 26, + "jumpxs.biz": 29, + "kamba.biz": 30, + "kanoodle.biz": 19, + "kare.biz": 30, + "katz.biz": 62, + "kaymbo.biz": 34, + "kayveo.biz": 22, + "kazio.biz": 21, + "kazu.biz": 16, + "kimia.biz": 25, + "kwideo.biz": 17, + "kwilith.biz": 25, + "kwimbee.biz": 34, + "kwinu.biz": 15, + "lajo.biz": 20, + "latz.biz": 24, + "layo.biz": 32, + "lazz.biz": 27, + "lazzy.biz": 26, + "leenti.biz": 26, + "leexo.biz": 32, + "linkbridge.biz": 38, + "linkbuzz.biz": 24, + "linklinks.biz": 31, + "linktype.biz": 31, + "livefish.biz": 31, + "livepath.biz": 23, + "livetube.biz": 53, + "livez.biz": 28, + "meedoo.biz": 23, + "meejo.biz": 24, + "meembee.biz": 26, + "meemm.biz": 23, + "meetz.biz": 33, + "meevee.biz": 62, + "meeveo.biz": 27, + "meezzy.biz": 24, + "miboo.biz": 26, + "midel.biz": 28, + "minyx.biz": 25, + "mita.biz": 29, + "mudo.biz": 36, + "muxo.biz": 25, + "mybuzz.biz": 32, + "mycat.biz": 32, + "mydeo.biz": 20, + "mydo.biz": 30, + "mymm.biz": 21, + "mynte.biz": 54, + "myworks.biz": 27, + "nlounge.biz": 25, + "npath.biz": 33, + "ntag.biz": 28, + "ntags.biz": 32, + "oba.biz": 22, + "oloo.biz": 19, + "omba.biz": 26, + "ooba.biz": 27, + "oodoo.biz": 30, + "oozz.biz": 22, + "oyoba.biz": 27, + "oyoloo.biz": 30, + "oyonder.biz": 29, + "oyondu.biz": 23, + "oyope.biz": 24, + "oyoyo.biz": 32, + "ozu.biz": 18, + "photobean.biz": 25, + "photobug.biz": 57, + "photofeed.biz": 25, + "photojam.biz": 35, + "photolist.biz": 19, + "photospace.biz": 33, + "pixoboo.biz": 14, + "pixonyx.biz": 30, + "pixope.biz": 32, + "plajo.biz": 32, + "plambee.biz": 29, + "podcat.biz": 31, + "quamba.biz": 31, + "quatz.biz": 54, + "quaxo.biz": 25, + "quimba.biz": 25, + "quimm.biz": 33, + "quinu.biz": 60, + "quire.biz": 25, + "realblab.biz": 32, + "realbridge.biz": 30, + "realbuzz.biz": 22, + "realcube.biz": 57, + "realfire.biz": 37, + "reallinks.biz": 25, + "realmix.biz": 27, + "realpoint.biz": 22, + "rhybox.biz": 30, + "rhycero.biz": 28, + "rhyloo.biz": 32, + "rhynoodle.biz": 25, + "rhynyx.biz": 17, + "rhyzio.biz": 36, + "riffpath.biz": 21, + "riffpedia.biz": 33, + "riffwire.biz": 31, + "roodel.biz": 29, + "roombo.biz": 29, + "roomm.biz": 32, + "rooxo.biz": 34, + "shufflebeat.biz": 32, + "shuffledrive.biz": 25, + "shufflester.biz": 26, + "shuffletag.biz": 23, + "skaboo.biz": 35, + "skajo.biz": 26, + "skalith.biz": 30, + "skiba.biz": 22, + "skibox.biz": 27, + "skidoo.biz": 24, + "skilith.biz": 29, + "skimia.biz": 45, + "skinder.biz": 25, + "skinix.biz": 23, + "skinte.biz": 39, + "skipfire.biz": 29, + "skippad.biz": 26, + "skipstorm.biz": 30, + "skiptube.biz": 26, + "skivee.biz": 34, + "skyba.biz": 40, + "skyble.biz": 32, + "skyndu.biz": 32, + "skynoodle.biz": 28, + "skyvu.biz": 34, + "snaptags.biz": 33, + "tagcat.biz": 33, + "tagchat.biz": 37, + "tagfeed.biz": 30, + "tagopia.biz": 17, + "tagpad.biz": 28, + "tagtune.biz": 22, + "talane.biz": 22, + "tambee.biz": 24, + "tanoodle.biz": 38, + "tavu.biz": 37, + "tazz.biz": 27, + "tazzy.biz": 28, + "tekfly.biz": 31, + "teklist.biz": 26, + "thoughtbeat.biz": 30, + "thoughtblab.biz": 24, + "thoughtbridge.biz": 30, + "thoughtmix.biz": 33, + "thoughtsphere.biz": 20, + "thoughtstorm.biz": 38, + "thoughtworks.biz": 24, + "topdrive.biz": 35, + "topicblab.biz": 32, + "topiclounge.biz": 21, + "topicshots.biz": 30, + "topicstorm.biz": 22, + "topicware.biz": 35, + "topiczoom.biz": 38, + "trilia.biz": 28, + "trilith.biz": 25, + "trudeo.biz": 29, + "trudoo.biz": 28, + "trunyx.biz": 33, + "trupe.biz": 34, + "twimbo.biz": 19, + "twimm.biz": 30, + "twinder.biz": 28, + "twinte.biz": 33, + "twitterbeat.biz": 33, + "twitterbridge.biz": 20, + "twitterlist.biz": 26, + "twitternation.biz": 22, + "twitterwire.biz": 21, + "twitterworks.biz": 39, + "twiyo.biz": 37, + "vidoo.biz": 28, + "vimbo.biz": 21, + "vinder.biz": 31, + "vinte.biz": 34, + "vipe.biz": 25, + "vitz.biz": 26, + "viva.biz": 30, + "voolia.biz": 34, + "voolith.biz": 26, + "voomm.biz": 61, + "voonder.biz": 32, + "voonix.biz": 32, + "voonte.biz": 26, + "voonyx.biz": 25, + "wikibox.biz": 27, + "wikido.biz": 21, + "wikivu.biz": 23, + "wikizz.biz": 61, + "wordify.biz": 28, + "wordpedia.biz": 25, + "wordtune.biz": 27, + "wordware.biz": 19, + "yabox.biz": 24, + "yacero.biz": 34, + "yadel.biz": 27, + "yakidoo.biz": 21, + "yakijo.biz": 29, + "yakitri.biz": 26, + "yambee.biz": 20, + "yamia.biz": 17, + "yata.biz": 25, + "yodel.biz": 26, + "yodo.biz": 21, + "yodoo.biz": 24, + "yombu.biz": 29, + "yotz.biz": 26, + "youbridge.biz": 40, + "youfeed.biz": 32, + "youopia.biz": 22, + "youspan.biz": 59, + "youtags.biz": 22, + "yoveo.biz": 31, + "yozio.biz": 33, + "zava.biz": 29, + "zazio.biz": 18, + "zoombeat.biz": 28, + "zoombox.biz": 30, + "zoomcast.biz": 38, + "zoomdog.biz": 29, + "zoomlounge.biz": 25, + "zoomzone.biz": 32, + "zoonder.biz": 29, + "zoonoodle.biz": 27, + "zooveo.biz": 22, + "zoovu.biz": 38, + "zooxo.biz": 33, + "zoozzy.biz": 23, +} diff --git a/hw10_program_optimization/stats_test.go b/hw10_program_optimization/stats_test.go new file mode 100644 index 0000000..f2c20a7 --- /dev/null +++ b/hw10_program_optimization/stats_test.go @@ -0,0 +1,39 @@ +// +build !bench + +package hw10programoptimization + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetDomainStat(t *testing.T) { + data := `{"Id":1,"Name":"Howard Mendoza","Username":"0Oliver","Email":"aliquid_qui_ea@Browsedrive.gov","Phone":"6-866-899-36-79","Password":"InAQJvsq","Address":"Blackbird Place 25"} +{"Id":2,"Name":"Jesse Vasquez","Username":"qRichardson","Email":"mLynch@broWsecat.com","Phone":"9-373-949-64-00","Password":"SiZLeNSGn","Address":"Fulton Hill 80"} +{"Id":3,"Name":"Clarence Olson","Username":"RachelAdams","Email":"RoseSmith@Browsecat.com","Phone":"988-48-97","Password":"71kuz3gA5w","Address":"Monterey Park 39"} +{"Id":4,"Name":"Gregory Reid","Username":"tButler","Email":"5Moore@Teklist.net","Phone":"520-04-16","Password":"r639qLNu","Address":"Sunfield Park 20"} +{"Id":5,"Name":"Janice Rose","Username":"KeithHart","Email":"nulla@Linktype.com","Phone":"146-91-01","Password":"acSBF5","Address":"Russell Trail 61"}` + + t.Run("find 'com'", func(t *testing.T) { + result, err := GetDomainStat(bytes.NewBufferString(data), "com") + require.NoError(t, err) + require.Equal(t, DomainStat{ + "browsecat.com": 2, + "linktype.com": 1, + }, result) + }) + + t.Run("find 'gov'", func(t *testing.T) { + result, err := GetDomainStat(bytes.NewBufferString(data), "gov") + require.NoError(t, err) + require.Equal(t, DomainStat{"browsedrive.gov": 1}, result) + }) + + t.Run("find 'unknown'", func(t *testing.T) { + result, err := GetDomainStat(bytes.NewBufferString(data), "unknown") + require.NoError(t, err) + require.Equal(t, DomainStat{}, result) + }) +} diff --git a/hw10_program_optimization/testdata/users.dat.zip b/hw10_program_optimization/testdata/users.dat.zip new file mode 100644 index 0000000..05b0158 Binary files /dev/null and b/hw10_program_optimization/testdata/users.dat.zip differ diff --git a/hw11_telnet_client/.sync b/hw11_telnet_client/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw11_telnet_client/README.md b/hw11_telnet_client/README.md new file mode 100644 index 0000000..08a6d08 --- /dev/null +++ b/hw11_telnet_client/README.md @@ -0,0 +1,109 @@ +## Домашнее задание №11 «Клиент TELNET» + +Необходимо реализовать крайне примитивный TELNET клиент +(без поддержки команд, опций и протокола в целом). + +Примеры вызовов: +```bash +$ go-telnet --timeout=10s host port +$ go-telnet mysite.ru 8080 +$ go-telnet --timeout=3s 1.1.1.1 123 +``` + +* Программа должна подключаться к указанному хосту (IP или доменное имя) и порту по протоколу TCP. +* После подключения STDIN программы должен записываться в сокет, +а данные, полученные из сокета, должны выводиться в STDOUT - всё происходит конкурентно. +* Опционально в программу можно передать таймаут на подключение к серверу +(через аргумент `--timeout`) - по умолчанию `10s`. +* При нажатии `Ctrl+D` программа должна закрывать сокет и завершаться с сообщением. +* При получении `SIGINT` программа должна завершать свою работу. +* Если сокет закрылся со стороны сервера, то при следующей попытке отправить сообщение программа +должна завершаться (допускается завершать программу после "неудачной" отправки нескольких сообщений). +* При подключении к несуществующему серверу, программа должна завершаться с ошибкой соединения/таймаута. + +При необходимости можно выделять дополнительные функции / ошибки. + +Примеры работы: + +1) Сервер обрывает соединение +```bash +$ nc -l localhost 4242 +Hello from NC +I'm telnet client +Bye, client! +^C +``` + +```bash +$ go-telnet --timeout=5s localhost 4242 +...Connected to localhost:4242 +Hello from NC +I'm telnet client +Bye, client! +Bye-bye +...Connection was closed by peer +``` + +Здесь сообщения +``` +Hello from NC +Bye, client! +``` +и операция Ctrl+C (Unix) относятся к `nc`, + +а сообщения +``` +I'm telnet client +Bye-bye +``` +относятся к `go-telnet`. + +2) Клиент завершает ввод +```bash +$ go-telnet localhost 4242 +...Connected to localhost:4242 +I +will be +back! +^D +...EOF +``` + +```bash +$ nc -l localhost 4242 +I +will be +back! +``` + +Здесь сообщения +``` +I +will be +back! +``` +и операция Ctrl+D (Unix) относятся к `go-telnet`, + +Сообщения +``` +...Connected to localhost:4242 +...Connection was closed by peer +...EOF +``` +являются служебными. +Они **выводятся в STDERR** и не тестируются `test.sh`, их формат на усмотрение автора. + +### Критерии оценки +- Пайплайн зелёный - 4 балла +- Добавлены юнит-тесты - до 2 баллов +- Понятность и чистота кода - до 4 баллов + +#### Зачёт от 7 баллов + +### Подсказки +- Для ручного тестирования рекомендуется использовать nc (как в `test.sh`). +- `flag.DurationVar` +- `net.JoinHostPort`, `net.DialTimeout` +- `bufio` / `io` +- `signal.NotifyContext` +- https://stackoverflow.com/questions/51317968/write-on-a-closed-net-conn-but-returned-nil-error diff --git a/hw11_telnet_client/go.mod b/hw11_telnet_client/go.mod new file mode 100644 index 0000000..839d5b8 --- /dev/null +++ b/hw11_telnet_client/go.mod @@ -0,0 +1,11 @@ +module github.com/fixme_my_friend/hw11_telnet_client + +go 1.22 + +require github.com/stretchr/testify v1.7.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect +) diff --git a/hw11_telnet_client/go.sum b/hw11_telnet_client/go.sum new file mode 100644 index 0000000..c221f64 --- /dev/null +++ b/hw11_telnet_client/go.sum @@ -0,0 +1,13 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hw11_telnet_client/main.go b/hw11_telnet_client/main.go new file mode 100644 index 0000000..307acaf --- /dev/null +++ b/hw11_telnet_client/main.go @@ -0,0 +1,6 @@ +package main + +func main() { + // Place your code here, + // P.S. Do not rush to throw context down, think think if it is useful with blocking operation? +} diff --git a/hw11_telnet_client/telnet.go b/hw11_telnet_client/telnet.go new file mode 100644 index 0000000..369caa1 --- /dev/null +++ b/hw11_telnet_client/telnet.go @@ -0,0 +1,21 @@ +package main + +import ( + "io" + "time" +) + +type TelnetClient interface { + Connect() error + io.Closer + Send() error + Receive() error +} + +func NewTelnetClient(address string, timeout time.Duration, in io.ReadCloser, out io.Writer) TelnetClient { + // Place your code here. + return nil +} + +// Place your code here. +// P.S. Author's solution takes no more than 50 lines. diff --git a/hw11_telnet_client/telnet_test.go b/hw11_telnet_client/telnet_test.go new file mode 100644 index 0000000..fb89f6d --- /dev/null +++ b/hw11_telnet_client/telnet_test.go @@ -0,0 +1,65 @@ +package main + +import ( + "bytes" + "io" + "net" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestTelnetClient(t *testing.T) { + t.Run("basic", func(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:") + require.NoError(t, err) + defer func() { require.NoError(t, l.Close()) }() + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + + in := &bytes.Buffer{} + out := &bytes.Buffer{} + + timeout, err := time.ParseDuration("10s") + require.NoError(t, err) + + client := NewTelnetClient(l.Addr().String(), timeout, io.NopCloser(in), out) + require.NoError(t, client.Connect()) + defer func() { require.NoError(t, client.Close()) }() + + in.WriteString("hello\n") + err = client.Send() + require.NoError(t, err) + + err = client.Receive() + require.NoError(t, err) + require.Equal(t, "world\n", out.String()) + }() + + go func() { + defer wg.Done() + + conn, err := l.Accept() + require.NoError(t, err) + require.NotNil(t, conn) + defer func() { require.NoError(t, conn.Close()) }() + + request := make([]byte, 1024) + n, err := conn.Read(request) + require.NoError(t, err) + require.Equal(t, "hello\n", string(request)[:n]) + + n, err = conn.Write([]byte("world\n")) + require.NoError(t, err) + require.NotEqual(t, 0, n) + }() + + wg.Wait() + }) +} diff --git a/hw11_telnet_client/test.sh b/hw11_telnet_client/test.sh new file mode 100755 index 0000000..422b594 --- /dev/null +++ b/hw11_telnet_client/test.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -xeuo pipefail + +go build -o go-telnet + +(echo -e "Hello\nFrom\nNC\n" && cat 2>/dev/null) | nc -l localhost 4242 >/tmp/nc.out & +NC_PID=$! + +sleep 1 +(echo -e "I\nam\nTELNET client\n" && cat 2>/dev/null) | ./go-telnet --timeout=5s localhost 4242 >/tmp/telnet.out & +TL_PID=$! + +sleep 5 +kill ${TL_PID} 2>/dev/null || true +kill ${NC_PID} 2>/dev/null || true + +function fileEquals() { + local fileData + fileData=$(cat "$1") + [ "${fileData}" = "${2}" ] || (echo -e "unexpected output, $1:\n${fileData}" && exit 1) +} + +expected_nc_out='I +am +TELNET client' +fileEquals /tmp/nc.out "${expected_nc_out}" + +expected_telnet_out='Hello +From +NC' +fileEquals /tmp/telnet.out "${expected_telnet_out}" + +rm -f go-telnet +echo "PASS" diff --git a/hw12_13_14_15_calendar/.gitignore b/hw12_13_14_15_calendar/.gitignore new file mode 100644 index 0000000..5191260 --- /dev/null +++ b/hw12_13_14_15_calendar/.gitignore @@ -0,0 +1,2 @@ +logs/ +bin/ diff --git a/hw12_13_14_15_calendar/.sync b/hw12_13_14_15_calendar/.sync new file mode 100644 index 0000000..e69de29 diff --git a/hw12_13_14_15_calendar/Makefile b/hw12_13_14_15_calendar/Makefile new file mode 100644 index 0000000..9d3c317 --- /dev/null +++ b/hw12_13_14_15_calendar/Makefile @@ -0,0 +1,34 @@ +BIN := "./bin/calendar" +DOCKER_IMG="calendar:develop" + +GIT_HASH := $(shell git log --format="%h" -n 1) +LDFLAGS := -X main.release="develop" -X main.buildDate=$(shell date -u +%Y-%m-%dT%H:%M:%S) -X main.gitHash=$(GIT_HASH) + +build: + go build -v -o $(BIN) -ldflags "$(LDFLAGS)" ./cmd/calendar + +run: build + $(BIN) -config ./configs/config.toml + +build-img: + docker build \ + --build-arg=LDFLAGS="$(LDFLAGS)" \ + -t $(DOCKER_IMG) \ + -f build/Dockerfile . + +run-img: build-img + docker run $(DOCKER_IMG) + +version: build + $(BIN) version + +test: + go test -race ./internal/... ./pkg/... + +install-lint-deps: + (which golangci-lint > /dev/null) || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.57.2 + +lint: install-lint-deps + golangci-lint run ./... + +.PHONY: build run build-img run-img version test lint diff --git a/hw12_13_14_15_calendar/README.md b/hw12_13_14_15_calendar/README.md new file mode 100644 index 0000000..3601931 --- /dev/null +++ b/hw12_13_14_15_calendar/README.md @@ -0,0 +1,13 @@ +#### Результатом выполнения следующих домашних заданий является сервис «Календарь»: +- [Домашнее задание №12 «Заготовка сервиса Календарь»](./docs/12_README.md) +- [Домашнее задание №13 «Внешние API от Календаря»](./docs/13_README.md) +- [Домашнее задание №14 «Кроликизация Календаря»](./docs/14_README.md) +- [Домашнее задание №15 «Докеризация и интеграционное тестирование Календаря»](./docs/15_README.md) + +#### Ветки при выполнении +- `hw12_calendar` (от `master`) -> Merge Request в `master` +- `hw13_calendar` (от `hw12_calendar`) -> Merge Request в `hw12_calendar` (если уже вмержена, то в `master`) +- `hw14_calendar` (от `hw13_calendar`) -> Merge Request в `hw13_calendar` (если уже вмержена, то в `master`) +- `hw15_calendar` (от `hw14_calendar`) -> Merge Request в `hw14_calendar` (если уже вмержена, то в `master`) + +**Домашнее задание не принимается, если не принято ДЗ, предшедствующее ему.** diff --git a/hw12_13_14_15_calendar/api/EventService.proto b/hw12_13_14_15_calendar/api/EventService.proto new file mode 100644 index 0000000..d188eff --- /dev/null +++ b/hw12_13_14_15_calendar/api/EventService.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +package event; + +message Event { + // TODO +} diff --git a/hw12_13_14_15_calendar/build/Dockerfile b/hw12_13_14_15_calendar/build/Dockerfile new file mode 100644 index 0000000..3c6fa2a --- /dev/null +++ b/hw12_13_14_15_calendar/build/Dockerfile @@ -0,0 +1,36 @@ +# Собираем в гошке +FROM golang:1.22 as build + +ENV BIN_FILE /opt/calendar/calendar-app +ENV CODE_DIR /go/src/ + +WORKDIR ${CODE_DIR} + +# Кэшируем слои с модулями +COPY go.mod . +COPY go.sum . +RUN go mod download + +COPY . ${CODE_DIR} + +# Собираем статический бинарник Go (без зависимостей на Си API), +# иначе он не будет работать в alpine образе. +ARG LDFLAGS +RUN CGO_ENABLED=0 go build \ + -ldflags "$LDFLAGS" \ + -o ${BIN_FILE} cmd/calendar/* + +# На выходе тонкий образ +FROM alpine:3.9 + +LABEL ORGANIZATION="OTUS Online Education" +LABEL SERVICE="calendar" +LABEL MAINTAINERS="student@otus.ru" + +ENV BIN_FILE "/opt/calendar/calendar-app" +COPY --from=build ${BIN_FILE} ${BIN_FILE} + +ENV CONFIG_FILE /etc/calendar/config.toml +COPY ./configs/config.toml ${CONFIG_FILE} + +CMD ${BIN_FILE} -config ${CONFIG_FILE} diff --git a/hw12_13_14_15_calendar/cmd/calendar/config.go b/hw12_13_14_15_calendar/cmd/calendar/config.go new file mode 100644 index 0000000..138275f --- /dev/null +++ b/hw12_13_14_15_calendar/cmd/calendar/config.go @@ -0,0 +1,20 @@ +package main + +// При желании конфигурацию можно вынести в internal/config. +// Организация конфига в main принуждает нас сужать API компонентов, использовать +// при их конструировании только необходимые параметры, а также уменьшает вероятность циклической зависимости. +type Config struct { + Logger LoggerConf + // TODO +} + +type LoggerConf struct { + Level string + // TODO +} + +func NewConfig() Config { + return Config{} +} + +// TODO diff --git a/hw12_13_14_15_calendar/cmd/calendar/main.go b/hw12_13_14_15_calendar/cmd/calendar/main.go new file mode 100644 index 0000000..9ec5b28 --- /dev/null +++ b/hw12_13_14_15_calendar/cmd/calendar/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "context" + "flag" + "os" + "os/signal" + "syscall" + "time" + + "github.com/fixme_my_friend/hw12_13_14_15_calendar/internal/app" + "github.com/fixme_my_friend/hw12_13_14_15_calendar/internal/logger" + internalhttp "github.com/fixme_my_friend/hw12_13_14_15_calendar/internal/server/http" + memorystorage "github.com/fixme_my_friend/hw12_13_14_15_calendar/internal/storage/memory" +) + +var configFile string + +func init() { + flag.StringVar(&configFile, "config", "/etc/calendar/config.toml", "Path to configuration file") +} + +func main() { + flag.Parse() + + if flag.Arg(0) == "version" { + printVersion() + return + } + + config := NewConfig() + logg := logger.New(config.Logger.Level) + + storage := memorystorage.New() + calendar := app.New(logg, storage) + + server := internalhttp.NewServer(logg, calendar) + + ctx, cancel := signal.NotifyContext(context.Background(), + syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) + defer cancel() + + go func() { + <-ctx.Done() + + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + + if err := server.Stop(ctx); err != nil { + logg.Error("failed to stop http server: " + err.Error()) + } + }() + + logg.Info("calendar is running...") + + if err := server.Start(ctx); err != nil { + logg.Error("failed to start http server: " + err.Error()) + cancel() + os.Exit(1) //nolint:gocritic + } +} diff --git a/hw12_13_14_15_calendar/cmd/calendar/version.go b/hw12_13_14_15_calendar/cmd/calendar/version.go new file mode 100644 index 0000000..7404eee --- /dev/null +++ b/hw12_13_14_15_calendar/cmd/calendar/version.go @@ -0,0 +1,27 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" +) + +var ( + release = "UNKNOWN" + buildDate = "UNKNOWN" + gitHash = "UNKNOWN" +) + +func printVersion() { + if err := json.NewEncoder(os.Stdout).Encode(struct { + Release string + BuildDate string + GitHash string + }{ + Release: release, + BuildDate: buildDate, + GitHash: gitHash, + }); err != nil { + fmt.Printf("error while decode version info: %v\n", err) + } +} diff --git a/hw12_13_14_15_calendar/configs/config.toml b/hw12_13_14_15_calendar/configs/config.toml new file mode 100644 index 0000000..5b33eb0 --- /dev/null +++ b/hw12_13_14_15_calendar/configs/config.toml @@ -0,0 +1,5 @@ +[logger] +level = "INFO" + +# TODO +# ... diff --git a/hw12_13_14_15_calendar/deployments/docker-compose.yaml b/hw12_13_14_15_calendar/deployments/docker-compose.yaml new file mode 100644 index 0000000..5db6fe9 --- /dev/null +++ b/hw12_13_14_15_calendar/deployments/docker-compose.yaml @@ -0,0 +1 @@ +version: "3" diff --git a/hw12_13_14_15_calendar/docs/12_README.md b/hw12_13_14_15_calendar/docs/12_README.md new file mode 100644 index 0000000..98e1a29 --- /dev/null +++ b/hw12_13_14_15_calendar/docs/12_README.md @@ -0,0 +1,160 @@ +## Домашнее задание №12 «Заготовка сервиса Календарь» +Необходимо реализовать скелет сервиса «Календарь», который будет дорабатываться в дальнейшем. + +Описание того, к чему мы должны придти, представлено [в техническом задании](./CALENDAR.MD). + +--- +В репозитории представлена заготовка сервиса, позволяющая понять, что вообще происходит и получить вектор для дальнейшей доработки. + +Этот код можно менять/удалять/добавлять каким-угодно способом по усмотрению разработчика. + +--- + +На данный момент сервис будет состоять из следующих логически выделенных частей: + +### 0) «Точка входа», запускающая сервис +Обычно располагается в `cmd/`. В `main()` происходит инициализация компонентов сервиса +(клиент к хранилищу, логгер, конфигурация и пр.), конструирование главного объекта-сервиса на их +основе и его запуск. Допускается использование https://github.com/spf13/cobra. + +### 1) Конфигурирование сервиса +При необходимости создать отдельный пакет, отвечающий за работу с конфигом. + +Сервис должен иметь конфиг, считываемый из файла. Для этого потребуется: +* обработка аргументов командной строки; +* чтение файла конфигурации формата json, yaml и пр. на выбор разработчика; +* заполнение программной структуры конфига из файла (Unmarshal и пр. способы); +* допустимо, если все действия выше за вас делает сторонний модуль. + +Конфигурация понадобится для инициализации различных компонентов системы, +её возможные поля будут рассмотрены далее. + +Соответственно сервис будет запускаться командой вида +```bash +./calendar --config=/path/to/config.yaml +``` +где `--config` - путь к файлу конфигурации. + +В репозитории должен присутствовать образец конфига. + +### 2) Логирование в сервисе +При необходимости создать отдельный пакет, отвечающий за работу с логером. + +Параметры, которыми необходимо инициализировать логгер: +* log_level - уровень логирования (error / warn / info / debug); +* любые другие на усмотрение разработчика. + +Логгер может быть как глобальной переменной, так и компонентом сервиса. + +### 3) Работа с хранилищем +Создать отдельный пакет, отвечающий за работу с хранилищем. + +Создать интерфейс хранилища событий, состоящий из методов для работы с ним: +* добавление события в хранилище; +* изменение события в хранилище; +* удаление события из хранилища; +* листинг событий; +* пр. на усмотрение разработчика. + +Описание сущности и методов представлено в [ТЗ](./CALENDAR.MD). + +Создать объекты ошибок, соответствующие бизнес ошибкам, которые необходимо выделить. +Например, `ErrDateBusy` - данное время уже занято другим событием. + +Создать две реализации интерфейса выше: +* **in-memory**: храним события в памяти (т.е. просто складываем объекты в словари/слайсы, не забывая про критические секции); +* **sql**: храним события в полноценной СУБД путем использования SQL-запросов в соответствующих методах. + +Вынести в конфиг параметр, отвечающий за то, какую из реализаций использовать при старте. + +Для работоспособности второй реализации необходимо: +* установить СУБД (например PostgreSQL) локально (или сразу через Docker, если знаете как); +* создать базу данных и пользователя для проекта календарь; +* реализовать схему данных (таблицы, индексы) в виде отдельного SQL или go-файла (файл миграции) +и сохранить его в репозиторий; +* применять миграции руками или на старте сервиса; +* вынести настройки подключения к БД в конфиг проекта. + +Полезные библиотеки: +* https://github.com/jmoiron/sqlx +* https://github.com/pressly/goose#go-migrations + +**Использовать ORM (например, gorm) не допускается**. + +Календарь должен использовать хранилище через интерфейс. + +### 4) Запуск простого HTTP-сервера +Запуск календаря должен стартовать HTTP-сервер. `host` и `port` сервера вынести в конфиг. + +На данном этапе сервер не должен быть связан с бизнес логикой приложения и должен иметь +только один "hello-world" endpoint ("/", "/hello", etc.). + +Информация об обработанном запросе должна выводиться в log-файл: +* IP клиента; +* дата и время запроса; +* метод, path и версия HTTP; +* код ответа; +* latency (время обработки запроса, посчитанное, например, с помощью middleware); +* user agent, если есть. + +Пример лога: +```text +66.249.65.3 [25/Feb/2020:19:11:24 +0600] GET /hello?q=1 HTTP/1.1 200 30 "Mozilla/5.0" +``` + +### 5) Юнит-тесты +Минимальный обязательный набор - тесты на **in-memory** реализацию хранилища (на основную логику, бизнес-ошибки и конкуррентно-безопасность). + +Остальные тесты на усмотрение разработчика. + +### 6) Makefile +Проект должен иметь в корне файлы go.mod и Makefile, последний должен описывать команды: +* `make build` - скомпилировать бинарный файл сервиса; +* [`make run`] - опционально, собрать и запустить сервис с конфигом по умолчанию; +* `make test` - запустить юнит-тесты (с флагом -race); +* `make lint` - запустить golangci-lint (при необходимости добавить свой `.golangci.yml`); +* [`make migrate`] - опционально, если миграции применяются руками; +* пр. на усмотрение разработчика + +### Об архитектуре +Проект должен следовать: +* https://github.com/golang-standards/project-layout +* https://golang.org/doc/effective_go.html#package-names +* https://rakyll.org/style-packages/ +* https://en.wikipedia.org/wiki/Dependency_injection + +Важно понять, что в Go нет серебряной пули по архитектуре. +Ссылки ниже могут дать полезные концепции, но не стоит слепо следовать им: +* https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1 +* https://www.ardanlabs.com/blog/2017/02/package-oriented-design.html +* https://github.com/marcusolsson/goddd +* чистая архитектура (clean architecture): + - https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html + - https://medium.com/@zhashkevych/чистая-архитектура-на-golang-cccbfdc95eba + +Используйте стандартный layout, соблюдайте направление зависимостей, выделяйте пакеты по концепции, +не забывайте про внедрение зависимостей через интерфейсы и у вас всё получится! + +Не забываем про стайл гайд, например: +* https://github.com/uber-go/guide/blob/master/style.md +* https://github.com/cristaloleg/go-advice + +### В данном ДЗ не нужно +* Реализовывать HTTP, GRPC и пр. API к микросервису. +* Писать .proto-файлы. + +Это всё будет позже. + +### Критерии оценки +- Makefile заполнен и пайплайн зеленый - 1 балл +- Понятность и чистота кода (включая факт, что проект разбит +на пакеты по определенной логике) - до 2 баллов +- Реализовано конфигурирование сервиса - 1 балл +- Используется логгер и он настраивается из конфига - 1 балл +- Реализовано хранилище: + - in-memory - 1 балл + - sql + миграции - 2 балла +- Запускается простой HTTP-сервер, мидлвара удовлетворяет ТЗ - 1 балл +- Присутствуют юнит-тесты - 1 балл + +#### Зачёт от 7 баллов diff --git a/hw12_13_14_15_calendar/docs/13_README.md b/hw12_13_14_15_calendar/docs/13_README.md new file mode 100644 index 0000000..1104f43 --- /dev/null +++ b/hw12_13_14_15_calendar/docs/13_README.md @@ -0,0 +1,36 @@ +## Домашнее задание №13 «API к Календарю» +Необходимо реализовать HTTP и GRPC API для сервиса календаря. + +Методы API в принципе идентичны методам хранилища и [описаны в ТЗ](./CALENDAR.MD). + +Для GRPC API необходимо: +* создать отдельную директорию для Protobuf спецификаций; +* создать Protobuf файлы с описанием всех методов API, объектов запросов и ответов ( +т.к. объект Event будет использоваться во многих ответах разумно выделить его в отдельный message); +* создать отдельный пакет для кода GRPC сервера; +* добавить в Makefile команду `generate`; `make generate` - вызывает `go generate`, которая в свою очередь +генерирует код GRPC сервера на основе Protobuf спецификаций; +* написать код, связывающий GRPC сервер с методами доменной области (бизнес логикой); +* логировать каждый запрос по аналогии с HTTP API. + +Для HTTP API необходимо: +* расширить "hello-world" сервер из [ДЗ №12](./12_README.md) до полноценного API; +* создать отдельный пакет для кода HTTP сервера; +* реализовать хэндлеры, при необходимости выделив структуры запросов и ответов; +* сохранить логирование запросов, реализованное в [ДЗ №12](./12_README.md). + +Общие требования: +* должны быть реализованы все методы; +* календарь не должен зависеть от кода серверов; +* сервера должны запускаться на портах, указанных в конфиге сервиса. + +**Можно использовать https://grpc-ecosystem.github.io/grpc-gateway/.** + +### Критерии оценки +- Makefile заполнен и пайплайн зеленый - 1 балл +- Реализовано GRPC API и `make generate` - 3 балла +- Реализовано HTTP API - 2 балла +- Написаны юнит-тесты на API - до 2 баллов +- Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов diff --git a/hw12_13_14_15_calendar/docs/14_README.md b/hw12_13_14_15_calendar/docs/14_README.md new file mode 100644 index 0000000..ffa1914 --- /dev/null +++ b/hw12_13_14_15_calendar/docs/14_README.md @@ -0,0 +1,48 @@ +## Домашнее задание №14 «Кроликизация Календаря» +Необходимо реализовать "напоминания" о событиях с помощью RabbitMQ (кролика). +Общая концепция описана в [техническом задании](./CALENDAR.MD). + +Порядок выполнения ДЗ: +* установить локально очередь сообщений RabbitMQ (или сразу через Docker, если знаете как); +* создать процесс Планировщик (`scheduler`), который периодически сканирует основную базу данных, +выбирая события о которых нужно напомнить: + - при запуске процесс должен подключаться к RabbitMQ и создавать все необходимые структуры + (топики и пр.) в ней; + - процесс должен выбирать сообытия для которых следует отправить уведомление (у события есть соотв. поле), + создавать для каждого Уведомление (описание сущности см. в [ТЗ](./CALENDAR.MD)), + сериализовать его (например, в JSON) и складывать в очередь; + - процесс должен очищать старые (произошедшие более 1 года назад) события. +* создать процесс Рассыльщик (`sender`), который читает сообщения из очереди и шлёт уведомления; +непосредственно отправку делать не нужно - достаточно логировать сообщения / выводить в STDOUT. +* настройки подключения к очереди, периодичность запуска и пр. настройки процессов вынести в конфиг проекта; +* работу с кроликом вынести в отдельный пакет, который будут использовать пакеты, реализующие процессы выше. + +Процессы не должны зависеть от конкретной реализации RMQ-клиента. + +В результате компиляции проекта (`make build`) должно получаться 3 отдельных исполняемых файла +(по одному на микросервис): +- API (`calendar`); +- Планировщик (`calendar_scheduler`); +- Рассыльщик (`calendar_sender`). + +Каждый из сервисов должен принимать путь файлу конфигурации: +```bash +./calendar --config=/path/to/calendar_config.yaml +./calendar_scheduler --config=/path/to/scheduler_config.yaml +./calendar_sender --config=/path/to/sender_config.yaml +``` + +После запуска RabbitMQ и PostgreSQL процессы `calendar_scheduler` и `calendar_sender` +должны запускаться без дополнительных действий. + +### Критерии оценки +- Makefile заполнен и пайплайн зеленый - 1 балл +- Работа с RMQ выделена в отдельный пакет, код не дублируется - 1 балл +- Реализован Планировщик: + - отсылает уведомления о выбранных событиях - 2 балла + - удаляет старые события - 1 балл +- Реализован Рассыльщик - 2 балла +- Можно собрать сервисы одной командой (`make build`) - 1 балл +- Понятность и чистота кода - до 2 баллов + +#### Зачёт от 7 баллов diff --git a/hw12_13_14_15_calendar/docs/15_README.md b/hw12_13_14_15_calendar/docs/15_README.md new file mode 100644 index 0000000..6d21de2 --- /dev/null +++ b/hw12_13_14_15_calendar/docs/15_README.md @@ -0,0 +1,53 @@ +## Домашнее задание №15 «Докеризация и интеграционное тестирование Календаря» + +Данное задание состоит из двух частей. + +Не забываем про https://github.com/golang-standards/project-layout. + +### 1) Докеризация сервиса +Необходимо: +* создать Dockerfile для каждого из процессов (Календарь, Рассыльщик, Планировщик); +* собрать образы и проверить их локальный запуск; +* создать docker-compose файл, который запускает PostgreSQL, RabbitMQ и все микросервисы вместе +(для "неродных" сервисов использовать официальные образы из Docker Hub); +* при желании доработать конфигурацию так, чтобы она поддерживала переменные окружения +(если вы используете библиотеку, то скорее всего она уже это умеет); в противном случае +придется "подкладывать" конфиг сервису с помощью Dockerfile / docker-compose - +при этом можно "заполнять" конфигурационный файл из переменных окружения, например +```bash +$ envsubst < config_template.json > config.json +``` +* если миграции выполняются руками, а не на старте сервиса, то также в docker-compose +должен запускаться one-shot скрипт, который делает это (применяет SQL миграции, +создавая структуру БД). +* порты серверов, предоставляющих API, пробросить на host. + +У преподавателя должна быть возможность запустить весь проект с помощью команды +`make up` (внутри `docker-compose up`) и погасить с помощью `make down`. + +HTTP API, например, после запуска должно быть доступно по URL **http://localhost:8888/**. + +### 2) Интеграционное тестирование +Необходимо: +* создать отдельный пакет для интеграционных тестов. +* реализовать интеграционные тесты на языке Go; при желании можно использовать +[godog](https://github.com/cucumber/godog) / [ginkgo](https://github.com/onsi/ginkgo), но +обязательным требованием это **не является**. +* создать docker-compose файл, поднимающий все сервисы проекта + контейнер с интеграционными тестами; +* расширить Makefile командой `integration-tests`, `make integration-tests` будет запускать интеграционные тесты; +**не стоит смешивать это с `make test`, иначе CI-пайплайн не пройдёт.** +* прикрепить в Merge Request вывод команды `make integration-tests`. + +Преподаватель может запустить интеграционные тесты с помощью команды `make integration-tests`: +- команда должна поднять окружение (`docker-compose`), прогнать тесты и подчистить окружение за собой; +- в случае успешного выполнения команда должна возвращать 0, иначе 1. + +### Критерии оценки +- Проект полностью запускается и останавливается с помощью `make up` / `make down` - 3 балла +- Интеграционные тесты запускаются с помощью `make integration-tests`. Команда возвращает верный код ответа - 1 балл +- Интеграционные тесты покрывают бизнес сценарии: + - добавление события и обработка бизнес ошибок - 2 балла + - получение листинга событий на день/неделю/месяц - 2 балла + - отправка уведомлений (необходимо доработать sender так, чтобы он информировал куда-то о статусе уведомления (БД/кролик)) - 2 балла + +#### Зачёт от 7 баллов diff --git a/hw12_13_14_15_calendar/docs/CALENDAR.MD b/hw12_13_14_15_calendar/docs/CALENDAR.MD new file mode 100644 index 0000000..b4c8f70 --- /dev/null +++ b/hw12_13_14_15_calendar/docs/CALENDAR.MD @@ -0,0 +1,96 @@ +## Техническое задание на сервис «Календарь» + +### Общее описание +Сервис "Календарь" представляет собой максимально упрощенный сервис для хранения календарных событий и отправки уведомлений. + +Сервис предполагает возможность: +* добавить/обновить событие; +* получить список событий на день/неделю/месяц; +* получить уведомление за N дней до события. + +Сервис НЕ предполагает: +* авторизации; +* разграничения доступа; +* web-интерфейса. + +### Архитектура +Полностью завершенный сервис состоит из 5 процессов. + +#### 1) API +API предоставляет собой GRPC и HTTP интерфейсы для пользователей. API реализуют основные методы сервиса. +Так как авторизация и разделение доступа выходят за рамки сервиса, мы предполагаем, что ID пользователя +просто передается через параметры / метаданные / заголовок запроса. + +#### 2) Планировщик +Планировщик - это фоновый процесс, который не взаимодействует с пользователем и выполняет периодические задания: +* выбор событий, требующих уведомления и отправка уведомлений в очередь рассыльщику; +* очистка старых (более 1 года назад) событий. + +#### 3) Рассыльщик +Рассыльщик - это фоновый процесс, занимающийся отправкой уведомлений. + +В рамках задания нет необходимости реальной отправки сообщений, достаточно, если рассыльщик просто будет +записывать их в лог / выводить в STDOUT. + +#### 4) СУБД +Реляционная СУБД (например, PostgreSQL) - хранит информацию о событиях. + +#### 5) Очередь сообщений +Очередь сообщений (RabbitMQ) - используется для передачи уведомлений от Планировщика Рассыльщику. + +### Описание сущностей +#### Событие +Событие - основная сущность, содержит в себе поля: +* ID - уникальный идентификатор события (можно воспользоваться UUID); +* Заголовок - короткий текст; +* Дата и время события; +* Длительность события (или дата и время окончания); +* Описание события - длинный текст, опционально; +* ID пользователя, владельца события; +* За сколько времени высылать уведомление, опционально. + +#### Уведомление +Уведомление - временная сущность, в БД не хранится, складывается в очередь для рассыльщика, содержит поля: +* ID события; +* Заголовок события; +* Дата события; +* Пользователь, которому отправлять. + +### Описание методов +* Создать (событие); +* Обновить (ID события, событие); +* Удалить (ID события); +* СписокСобытийНаДень (дата); +* СписокСобытийНаНеделю (дата начала недели); +* СписокСобытийНaМесяц (дата начала месяца). + +### Конфигурация, логирование, контейнеризация +Рекомендуется собирать проект в виде трех бинарных файлов, по одному на каждый микросервис. +Каждый из сервисов должен принимать путь файлу конфигурации: +```text +./calendar --config=/path/to/calendar_config.yaml +./calendar_scheduler --config=/path/to/scheduler_config.yaml +./calendar_sender --config=/path/to/sender_config.yaml +``` +Проект следует оформить в виде набора контейнеров для каждого из микросервисов. +Поскольку запуск будет осуществлять в контейнерах - логи нужно печатать в STDOUT. +Сервисы должны получать настройки (например, адрес и логин в СУБД) через переменные окружения. +При желании можно использовать конфиг viper, с поддержкой переменных окружения. + +Развертывание микросервиса должно осуществляться командой `docker-compose up` в директории с проектом. + +### Тестирование +Для проекта необходимо реализовать интеграционные тесты, т.е. тесты проверяющие работу на уровне API. +Интеграционные тесты можно создать с помощью BDD, а можно просто с помощью стандартного пакета `testing` +и запускать их в отдельном контейнере в том же compose окружении, что и остальные микросервисы. +В таком случае тесты смогут получать адрес работающего сервиса через переменные окружения, выполнять запросы к нему, +а также проверять состояние базы данных и пр. + +### Этапы разработки +Разработка разделена на следующие этапы (ДЗ): +* Разрабатываем скелет сервиса: конфигурирование, работа с базой, простой web-сервер. +* Добавляем HTTP и GRPC API. +* Выделяем Планировщик и Рассыльщик. Интегрируемся с очередью сообщений. +* Создаем Docker-образы, docker-compose конфигурации, пишем интеграционные тесты. + +Таким образом, на каждом этапе мы получаем осмысленный проект. diff --git a/hw12_13_14_15_calendar/go.mod b/hw12_13_14_15_calendar/go.mod new file mode 100644 index 0000000..4be1765 --- /dev/null +++ b/hw12_13_14_15_calendar/go.mod @@ -0,0 +1,3 @@ +module github.com/fixme_my_friend/hw12_13_14_15_calendar + +go 1.22 diff --git a/hw12_13_14_15_calendar/go.sum b/hw12_13_14_15_calendar/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/hw12_13_14_15_calendar/internal/app/app.go b/hw12_13_14_15_calendar/internal/app/app.go new file mode 100644 index 0000000..2682e4b --- /dev/null +++ b/hw12_13_14_15_calendar/internal/app/app.go @@ -0,0 +1,26 @@ +package app + +import ( + "context" +) + +type App struct { // TODO +} + +type Logger interface { // TODO +} + +type Storage interface { // TODO +} + +func New(logger Logger, storage Storage) *App { + return &App{} +} + +func (a *App) CreateEvent(ctx context.Context, id, title string) error { + // TODO + return nil + // return a.storage.CreateEvent(storage.Event{ID: id, Title: title}) +} + +// TODO diff --git a/hw12_13_14_15_calendar/internal/logger/logger.go b/hw12_13_14_15_calendar/internal/logger/logger.go new file mode 100644 index 0000000..718df35 --- /dev/null +++ b/hw12_13_14_15_calendar/internal/logger/logger.go @@ -0,0 +1,20 @@ +package logger + +import "fmt" + +type Logger struct { // TODO +} + +func New(level string) *Logger { + return &Logger{} +} + +func (l Logger) Info(msg string) { + fmt.Println(msg) +} + +func (l Logger) Error(msg string) { + // TODO +} + +// TODO diff --git a/hw12_13_14_15_calendar/internal/logger/logger_test.go b/hw12_13_14_15_calendar/internal/logger/logger_test.go new file mode 100644 index 0000000..34bb72c --- /dev/null +++ b/hw12_13_14_15_calendar/internal/logger/logger_test.go @@ -0,0 +1,7 @@ +package logger + +import "testing" + +func TestLogger(t *testing.T) { + // TODO +} diff --git a/hw12_13_14_15_calendar/internal/server/http/middleware.go b/hw12_13_14_15_calendar/internal/server/http/middleware.go new file mode 100644 index 0000000..14c0480 --- /dev/null +++ b/hw12_13_14_15_calendar/internal/server/http/middleware.go @@ -0,0 +1,11 @@ +package internalhttp + +import ( + "net/http" +) + +func loggingMiddleware(next http.Handler) http.Handler { //nolint:unused + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // TODO + }) +} diff --git a/hw12_13_14_15_calendar/internal/server/http/server.go b/hw12_13_14_15_calendar/internal/server/http/server.go new file mode 100644 index 0000000..25a95b9 --- /dev/null +++ b/hw12_13_14_15_calendar/internal/server/http/server.go @@ -0,0 +1,31 @@ +package internalhttp + +import ( + "context" +) + +type Server struct { // TODO +} + +type Logger interface { // TODO +} + +type Application interface { // TODO +} + +func NewServer(logger Logger, app Application) *Server { + return &Server{} +} + +func (s *Server) Start(ctx context.Context) error { + // TODO + <-ctx.Done() + return nil +} + +func (s *Server) Stop(ctx context.Context) error { + // TODO + return nil +} + +// TODO diff --git a/hw12_13_14_15_calendar/internal/storage/event.go b/hw12_13_14_15_calendar/internal/storage/event.go new file mode 100644 index 0000000..39b2d1a --- /dev/null +++ b/hw12_13_14_15_calendar/internal/storage/event.go @@ -0,0 +1,7 @@ +package storage + +type Event struct { + ID string + Title string + // TODO +} diff --git a/hw12_13_14_15_calendar/internal/storage/memory/storage.go b/hw12_13_14_15_calendar/internal/storage/memory/storage.go new file mode 100644 index 0000000..91b3f90 --- /dev/null +++ b/hw12_13_14_15_calendar/internal/storage/memory/storage.go @@ -0,0 +1,14 @@ +package memorystorage + +import "sync" + +type Storage struct { + // TODO + mu sync.RWMutex //nolint:unused +} + +func New() *Storage { + return &Storage{} +} + +// TODO diff --git a/hw12_13_14_15_calendar/internal/storage/memory/storage_test.go b/hw12_13_14_15_calendar/internal/storage/memory/storage_test.go new file mode 100644 index 0000000..7e303bc --- /dev/null +++ b/hw12_13_14_15_calendar/internal/storage/memory/storage_test.go @@ -0,0 +1,7 @@ +package memorystorage + +import "testing" + +func TestStorage(t *testing.T) { + // TODO +} diff --git a/hw12_13_14_15_calendar/internal/storage/sql/storage.go b/hw12_13_14_15_calendar/internal/storage/sql/storage.go new file mode 100644 index 0000000..35be281 --- /dev/null +++ b/hw12_13_14_15_calendar/internal/storage/sql/storage.go @@ -0,0 +1,20 @@ +package sqlstorage + +import "context" + +type Storage struct { // TODO +} + +func New() *Storage { + return &Storage{} +} + +func (s *Storage) Connect(ctx context.Context) error { + // TODO + return nil +} + +func (s *Storage) Close(ctx context.Context) error { + // TODO + return nil +} diff --git a/hw12_13_14_15_calendar/migrations/.gitkeep b/hw12_13_14_15_calendar/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/img/.sync b/img/.sync new file mode 100644 index 0000000..e69de29 diff --git a/img/approved_pr.png b/img/approved_pr.png new file mode 100644 index 0000000..6ee0cc7 Binary files /dev/null and b/img/approved_pr.png differ diff --git a/img/chat_pr.png b/img/chat_pr.png new file mode 100644 index 0000000..d950dac Binary files /dev/null and b/img/chat_pr.png differ diff --git a/img/master_protection_and_ci.png b/img/master_protection_and_ci.png new file mode 100644 index 0000000..c68f254 Binary files /dev/null and b/img/master_protection_and_ci.png differ diff --git a/img/otus_chat_button.png b/img/otus_chat_button.png new file mode 100644 index 0000000..786ba20 Binary files /dev/null and b/img/otus_chat_button.png differ diff --git a/img/pr_with_template.png b/img/pr_with_template.png new file mode 100644 index 0000000..d375096 Binary files /dev/null and b/img/pr_with_template.png differ diff --git a/img/pr_without_template.png b/img/pr_without_template.png new file mode 100644 index 0000000..40db34d Binary files /dev/null and b/img/pr_without_template.png differ