diff --git a/ciletters/README.md b/ciletters/README.md new file mode 100644 index 0000000..1c942ac --- /dev/null +++ b/ciletters/README.md @@ -0,0 +1,62 @@ +## ciletters + +В этой задаче вам предстоит познакомиться со стандартным пакетом [text/template](https://golang.org/pkg/text/template/), +позволяющим генерировать текст в определенном формате. + +### Легенда + +В gitlab можно подписаться на различные события: успешный build, новый комментарий, решённое issue и др. +Вот так, например, выглядит нотификация о сломанном pipeline'е: + +![Image description](assets/notification.png) + +В задаче предлагается сгенерировать письмо подобного содержания в текстовом виде. + +Для генерации HTML в языке также имеется стандартный пакет [html/template](https://golang.org/pkg/html/template/). +Работа с ним аналогична работа с `text/template`, поэтому для простоты в задаче был выбран тестовый формат. + +### Что нужно сделать? + +Нужно реализовать функцию `MakeLetter` из файла [letter.go](./letter.go), +которая по go объекту нотификации генерирует её текстовое представление. + +#### Прокомментированный пример из теста +``` +Your pipeline #194613 has failed! // 194613 -- это ID pipeline'а + Project: go-spring-2021/gopher // Project состоит из ID группы и ID проекта + Branch: 🌿 master + Commit: 8967153e Solve urlfetch. // Первые 8 байт хэша коммита. + CommitAuthor: gopher + // Здесь происходит цикл по всем сломанным job'ам + Stage: test, Job grade // test -- это имя stage'а, а grade -- имя job'а + // Далее идут последние 10 строк лога gitlab runner'а + testtool: copying go.mod, go.sum and .golangci.yml + testtool: running tests + testtool: > go test -mod readonly -tags private -c -o /tmp/bincache730817117/5d83984f885e61c1 gitlab.com/slon/shad-go/sum + --- FAIL: TestSum (0.00s) + sum_test.go:19: 2 + 2 == 0 != 4 + sum_test.go:19: 9223372036854775807 + 1 == 0 != -9223372036854775808 + FAIL + testtool: task sum failed: test failed: exit status 1 + some tasks failed + ERROR: Job failed: exit code 1 +``` + +Объект нотификации описан в [notification.go](notification.go). +Обратите внимание на `// +build !change`. +Этот файл менять не нужно, и на сервере будет использоваться оригинальный вариант. + +В реализации нужно подогнать `text/template` шаблон под требуемый вывод. + +Вам могут понадобиться: +* условные блоки (`if/else`) +* range'и +* кастомные функции в шаблоне: https://golang.org/pkg/text/template/#FuncMap +* `'-'` для удаления пробелов + +### Проверка решения + +Для запуска тестов нужно выполнить следующую команду: +``` +go test -v ./ciletters/... +``` diff --git a/ciletters/assets/notification.png b/ciletters/assets/notification.png new file mode 100644 index 0000000..02584d7 Binary files /dev/null and b/ciletters/assets/notification.png differ diff --git a/ciletters/letter.go b/ciletters/letter.go new file mode 100644 index 0000000..d3424fc --- /dev/null +++ b/ciletters/letter.go @@ -0,0 +1,7 @@ +// +build !solution + +package ciletters + +func MakeLetter(n *Notification) (string, error) { + return "", nil +} diff --git a/ciletters/letter_test.go b/ciletters/letter_test.go new file mode 100644 index 0000000..4b994c3 --- /dev/null +++ b/ciletters/letter_test.go @@ -0,0 +1,227 @@ +package ciletters + +import ( + "crypto/rand" + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "gitlab.com/slon/shad-go/tools/testtool" +) + +type testCase struct { + name string + notification Notification + expected string +} + +func TestSpell(t *testing.T) { + const ( + testUser = "gopher" + gitlabGroupID = "go-spring-2021" + ) + + randomGitlabGroup := testtool.RandomName() + randomGitlabProject := testtool.RandomName() + randomBranch := testtool.RandomName() + randomTestUser := testtool.RandomName() + randomTriggerer := testtool.RandomName() + randomPipelineID := randomInt64(t) + randomJobIDS := []int64{randomInt64(t), randomInt64(t)} + randomJobNames := []string{testtool.RandomName(), testtool.RandomName()} + randomStages := []string{testtool.RandomName(), testtool.RandomName()} + randomHash := testtool.RandomName()[:8] + + for _, tc := range []testCase{ + { + name: "success", + notification: Notification{ + Project: GitlabProject{ + GroupID: gitlabGroupID, + ID: testUser, + }, + Branch: "master", + Commit: Commit{ + Hash: "2ff019bcb8f68d13d640e13351dad98edf7f1405", + Message: "Solve sum.", + Author: testUser, + }, + Pipeline: Pipeline{ + Status: PipelineStatusOK, + ID: 194555, + TriggeredBy: testUser, + }, + }, + expected: `Your pipeline #194555 passed! + Project: go-spring-2021/gopher + Branch: 🌿 master + Commit: 2ff019bc Solve sum. + CommitAuthor: gopher`, + }, + { + name: "job-failed", + notification: Notification{ + Project: GitlabProject{ + GroupID: gitlabGroupID, + ID: testUser, + }, + Branch: "master", + Commit: Commit{ + Hash: "8967153e8aa7b270af6447dae594eb87bdae8791", + Message: "Solve urlfetch.", + Author: testUser, + }, + Pipeline: Pipeline{ + Status: PipelineStatusFailed, + ID: 194613, + TriggeredBy: testUser, + FailedJobs: []Job{ + { + ID: 202538, + Name: "grade", + Stage: "test", + RunnerLog: `$ testtool grade +testtool: detected change in tasks [sum] +testtool: skipping task sum: not released yet +testtool: testing task sum +testtool: testing submission in /tmp/sum-281145206 +testtool: copying student repo +testtool: copying tests +testtool: copying !change files +testtool: copying testdata directory +testtool: copying go.mod, go.sum and .golangci.yml +testtool: running tests +testtool: > go test -mod readonly -tags private -c -o /tmp/bincache730817117/5d83984f885e61c1 gitlab.com/slon/shad-go/sum +--- FAIL: TestSum (0.00s) + sum_test.go:19: 2 + 2 == 0 != 4 + sum_test.go:19: 9223372036854775807 + 1 == 0 != -9223372036854775808 +FAIL +testtool: task sum failed: test failed: exit status 1 +some tasks failed +ERROR: Job failed: exit code 1`, + }, + }, + }, + }, + expected: `Your pipeline #194613 has failed! + Project: go-spring-2021/gopher + Branch: 🌿 master + Commit: 8967153e Solve urlfetch. + CommitAuthor: gopher + Stage: test, Job grade + testtool: copying go.mod, go.sum and .golangci.yml + testtool: running tests + testtool: > go test -mod readonly -tags private -c -o /tmp/bincache730817117/5d83984f885e61c1 gitlab.com/slon/shad-go/sum + --- FAIL: TestSum (0.00s) + sum_test.go:19: 2 + 2 == 0 != 4 + sum_test.go:19: 9223372036854775807 + 1 == 0 != -9223372036854775808 + FAIL + testtool: task sum failed: test failed: exit status 1 + some tasks failed + ERROR: Job failed: exit code 1 +`, + }, + { + name: "multiple-jobs-failed", + notification: Notification{ + Project: GitlabProject{ + GroupID: randomGitlabGroup, + ID: randomGitlabProject, + }, + Branch: randomBranch, + Commit: Commit{ + Hash: randomHash, + Message: "Solve digitalclock.", + Author: randomTestUser, + }, + Pipeline: Pipeline{ + Status: PipelineStatusFailed, + ID: randomPipelineID, + TriggeredBy: randomTriggerer, + FailedJobs: []Job{ + { + ID: randomJobIDS[0], + Name: randomJobNames[0], + Stage: randomStages[0], + RunnerLog: `$ testtool grade +testtool: detected change in tasks [sum] +testtool: skipping task sum: not released yet +testtool: testing task sum +testtool: testing submission in /tmp/sum-281145206 +testtool: copying student repo +testtool: copying tests +testtool: copying !change files +testtool: copying testdata directory +testtool: copying go.mod, go.sum and .golangci.yml +testtool: running tests +testtool: > go test -mod readonly -tags private -c -o /tmp/bincache730817117/5d83984f885e61c1 gitlab.com/slon/shad-go/sum +--- FAIL: TestSum (0.00s) + sum_test.go:19: 2 + 2 == 0 != 4 + sum_test.go:19: 9223372036854775807 + 1 == 0 != -9223372036854775808 +FAIL +testtool: task sum failed: test failed: exit status 1 +some tasks failed +ERROR: Job failed: exit code 1`, + }, + { + ID: randomJobIDS[1], + Name: randomJobNames[1], + Stage: randomStages[1], + RunnerLog: `--- FAIL: TestSum (0.00s) + sum_test.go:19: 2 + 2 == 0 != 4 + sum_test.go:19: 9223372036854775807 + 1 == 0 != -9223372036854775808 +FAIL +testtool: task sum failed: test failed: exit status 1 +some tasks failed +ERROR: Job failed: exit code 1`, + }, + }, + }, + }, + expected: fmt.Sprintf(`Your pipeline #%d has failed! + Project: %v/%v + Branch: 🌿 %v + Commit: %v Solve digitalclock. + CommitAuthor: %v + Stage: %v, Job %v + testtool: copying go.mod, go.sum and .golangci.yml + testtool: running tests + testtool: > go test -mod readonly -tags private -c -o /tmp/bincache730817117/5d83984f885e61c1 gitlab.com/slon/shad-go/sum + --- FAIL: TestSum (0.00s) + sum_test.go:19: 2 + 2 == 0 != 4 + sum_test.go:19: 9223372036854775807 + 1 == 0 != -9223372036854775808 + FAIL + testtool: task sum failed: test failed: exit status 1 + some tasks failed + ERROR: Job failed: exit code 1 + + Stage: %v, Job %v + --- FAIL: TestSum (0.00s) + sum_test.go:19: 2 + 2 == 0 != 4 + sum_test.go:19: 9223372036854775807 + 1 == 0 != -9223372036854775808 + FAIL + testtool: task sum failed: test failed: exit status 1 + some tasks failed + ERROR: Job failed: exit code 1 +`, randomPipelineID, randomGitlabGroup, randomGitlabProject, randomBranch, randomHash, randomTestUser, + randomStages[0], randomJobNames[0], randomStages[1], randomJobNames[1]), + }, + } { + t.Run(tc.name, func(t *testing.T) { + letter, err := MakeLetter(&tc.notification) + require.NoError(t, err) + require.Equal(t, tc.expected, letter) + }) + } +} + +func randomInt64(t *testing.T) int64 { + t.Helper() + + nBig, err := rand.Int(rand.Reader, big.NewInt(1e6)) + require.NoError(t, err) + + return nBig.Int64() +} diff --git a/ciletters/notification.go b/ciletters/notification.go new file mode 100644 index 0000000..131aa5b --- /dev/null +++ b/ciletters/notification.go @@ -0,0 +1,43 @@ +// +build !change + +package ciletters + +type Notification struct { + Project GitlabProject + Branch string + Commit Commit + Pipeline Pipeline +} + +type GitlabProject struct { + GroupID string + ID string +} + +type Commit struct { + // Hash is a 20-byte SHA-1 encoded in hex. + Hash string + Message string + Author string +} + +type PipelineStatus string + +const ( + PipelineStatusOK PipelineStatus = "ok" + PipelineStatusFailed PipelineStatus = "failed" +) + +type Pipeline struct { + Status PipelineStatus + ID int64 + TriggeredBy string + FailedJobs []Job +} + +type Job struct { + ID int64 + Name string + Stage string + RunnerLog string +}