Skip to content

Commit dc65c3f

Browse files
authored
autotests for devops master course (#80)
1 parent 290776e commit dc65c3f

File tree

4 files changed

+270
-1
lines changed

4 files changed

+270
-1
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
SHELL=/bin/bash
22

3-
AUTOTESTS = gophermarttest metricstest devopstest shortenertest shortenertestbeta
3+
AUTOTESTS = gophermarttest metricstest devopstest shortenertest shortenertestbeta devopsmastertest
44
UTILS = random statictest shortenerstress
55

66
all: prep autotests utils perm

cmd/devopsmastertest/flags.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
)
6+
7+
// Доступные для тест-сьютов флаги командной строки
8+
var (
9+
flagTargetBinaryPath string // путь до бинарного файла проекта
10+
)
11+
12+
func init() {
13+
flag.StringVar(&flagTargetBinaryPath, "binary-path", "", "path to target script binary")
14+
}

cmd/devopsmastertest/lesson01_test.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math/rand"
7+
"net/http"
8+
"os"
9+
"os/signal"
10+
"strings"
11+
"sync"
12+
"syscall"
13+
"time"
14+
15+
"github.com/Yandex-Practicum/go-autotests/internal/fork"
16+
"github.com/stretchr/testify/suite"
17+
)
18+
19+
// Lesson01Suite является сьютом с тестами урока
20+
type Lesson01Suite struct {
21+
suite.Suite
22+
}
23+
24+
func (suite *Lesson01Suite) TestServerStats() {
25+
// проверяем наличие необходимых флагов
26+
suite.Require().NotEmpty(flagTargetBinaryPath, "-binary-path non-empty flag required")
27+
28+
// генерируем набор сценариев тестирования
29+
suite.T().Log("generating scenarios")
30+
respSet := newResponseSet()
31+
32+
maxRequests := len(respSet)
33+
var stats []serverStat
34+
var setOutputs []string
35+
for _, resp := range respSet {
36+
stats = append(stats, resp.stats)
37+
setOutputs = append(setOutputs, resp.expectedOutput...)
38+
}
39+
40+
suite.T().Log("creating handler")
41+
reqNotifier := make(chan int)
42+
handler := newFaultySrvHandler(stats, reqNotifier)
43+
44+
// запускаем сервер
45+
suite.T().Log("staring HTTP server")
46+
go func() {
47+
err := http.ListenAndServe("127.0.0.1:80", handler)
48+
if err != nil {
49+
suite.FailNowf("cannot start HTTP server", "error: %s", err)
50+
}
51+
}()
52+
53+
// запускаем бинарник скрипта
54+
suite.T().Log("creating process")
55+
scriptProc := fork.NewBackgroundProcess(context.Background(), flagTargetBinaryPath)
56+
57+
binctx, bincancel := context.WithTimeout(context.Background(), 10*time.Second)
58+
defer bincancel()
59+
60+
suite.T().Log("starting process")
61+
if err := scriptProc.Start(binctx); err != nil {
62+
suite.FailNowf("cannot start script process", "error: %s", err)
63+
return
64+
}
65+
66+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
67+
defer cancel()
68+
69+
sigChan := make(chan os.Signal, 1)
70+
signal.Notify(sigChan, syscall.SIGINT)
71+
72+
// ждем завершения
73+
suite.T().Log("waiting scenarios to complete")
74+
var requestsMade int
75+
func() {
76+
for {
77+
select {
78+
case <-sigChan:
79+
// получен сигнал завершения
80+
return
81+
case <-ctx.Done():
82+
// время вышло
83+
return
84+
case requestsMade = <-reqNotifier:
85+
suite.T().Logf("got request %d", requestsMade)
86+
if requestsMade == maxRequests {
87+
// все сценарии были обработаны
88+
return
89+
}
90+
}
91+
}
92+
}()
93+
94+
// останавливаем процесс скрипта
95+
suite.T().Log("stopping process")
96+
_, err := scriptProc.Stop(syscall.SIGINT, syscall.SIGKILL)
97+
if err != nil {
98+
suite.FailNowf("cannot stop script process", "error: %s", err)
99+
return
100+
}
101+
102+
// сравниваем вывод скрпта в консоль с ожидаемым выводом
103+
expectedOutput := strings.Join(setOutputs, "\n")
104+
if expectedOutput != "" {
105+
expectedOutput += "\n"
106+
}
107+
108+
suite.T().Log("checking results")
109+
stdout := scriptProc.Stdout(context.Background())
110+
suite.Assert().Equal(expectedOutput, string(stdout), "Вывод скрипта отличается от ожидаемого")
111+
}
112+
113+
func newFaultySrvHandler(stats []serverStat, notifier chan<- int) http.HandlerFunc {
114+
var mu sync.Mutex
115+
var receivedRequestsCount int
116+
return func(w http.ResponseWriter, r *http.Request) {
117+
// не даем делать запросы в многопоточном режиме, чтобы сохранить консистентность обработки/вывода результатов
118+
mu.Lock()
119+
defer mu.Unlock()
120+
121+
if receivedRequestsCount >= len(stats) {
122+
// отвечаем ошибкой если у нас кончились заготовленные ответы,
123+
// а запросы все еще приходят
124+
w.WriteHeader(http.StatusInternalServerError)
125+
return
126+
}
127+
body, err := stats[receivedRequestsCount].MarshalText()
128+
if err != nil {
129+
// почему-то не смогли закодировать данные сервера в строку
130+
w.WriteHeader(http.StatusInternalServerError)
131+
return
132+
}
133+
// отправляем ответ
134+
_, _ = w.Write(body)
135+
// увеличиваем счетчик принятых запросов
136+
receivedRequestsCount++
137+
// оповещаем тест о новом обработанном запросе
138+
notifier <- receivedRequestsCount
139+
}
140+
}
141+
142+
type responseSet []responsePair
143+
144+
type responsePair struct {
145+
stats serverStat
146+
expectedOutput []string
147+
}
148+
149+
type serverStat struct {
150+
CurrentLA int
151+
MemBytesAvailable int
152+
MemBytesUsed int
153+
DiskBytesAvailable int
154+
DiskBytesUsed int
155+
NetBandwidthAvailable int
156+
NetBandwidthUsed int
157+
}
158+
159+
func (s serverStat) MarshalText() ([]byte, error) {
160+
m := fmt.Sprintf("%d,%d,%d,%d,%d,%d,%d",
161+
s.CurrentLA,
162+
s.MemBytesAvailable,
163+
s.MemBytesUsed,
164+
s.DiskBytesAvailable,
165+
s.DiskBytesUsed,
166+
s.NetBandwidthAvailable,
167+
s.NetBandwidthUsed,
168+
)
169+
return []byte(m), nil
170+
}
171+
172+
const (
173+
unitB = 1
174+
unitKb = unitB * 1024
175+
unitMb = unitKb * 1024
176+
unitGb = unitMb * 1024
177+
178+
unitBps = 1.0
179+
unitKbps = unitBps * 1000
180+
unitMbps = unitKbps * 1000
181+
unitGbps = unitMbps * 1000
182+
)
183+
184+
func newResponseSet() (res responseSet) {
185+
src := rand.NewSource(time.Now().UnixNano())
186+
rnd := rand.New(src)
187+
188+
// изначальная конфигурация сервера
189+
memBytesAvailable := intInRange(rnd, 4*unitGb, 5*unitGb)
190+
diskBytesAvailable := intInRange(rnd, 256*unitGb, 512*unitGb)
191+
netBandwidthAvailable := intInRange(rnd, 1*unitGbps, 10*unitGbps)
192+
193+
{
194+
// сценарий: все в порядке
195+
res = append(res, responsePair{
196+
stats: serverStat{
197+
CurrentLA: intInRange(rnd, 0, 29),
198+
MemBytesAvailable: memBytesAvailable,
199+
MemBytesUsed: intInRange(rnd, memBytesAvailable/3, memBytesAvailable/2),
200+
DiskBytesAvailable: diskBytesAvailable,
201+
DiskBytesUsed: intInRange(rnd, diskBytesAvailable/5, diskBytesAvailable/3),
202+
NetBandwidthAvailable: netBandwidthAvailable,
203+
NetBandwidthUsed: intInRange(rnd, netBandwidthAvailable/8, netBandwidthAvailable/4),
204+
},
205+
expectedOutput: nil,
206+
})
207+
}
208+
209+
{
210+
// сценарий: слишком большое LA
211+
currentLA := intInRange(rnd, 30, 99)
212+
res = append(res, responsePair{
213+
stats: serverStat{
214+
CurrentLA: currentLA,
215+
MemBytesAvailable: memBytesAvailable,
216+
MemBytesUsed: intInRange(rnd, memBytesAvailable/3, memBytesAvailable/2),
217+
DiskBytesAvailable: diskBytesAvailable,
218+
DiskBytesUsed: intInRange(rnd, diskBytesAvailable/5, diskBytesAvailable/3),
219+
NetBandwidthAvailable: netBandwidthAvailable,
220+
NetBandwidthUsed: intInRange(rnd, netBandwidthAvailable/8, netBandwidthAvailable/4),
221+
},
222+
expectedOutput: []string{
223+
fmt.Sprintf("Load Average is too high: %d", currentLA),
224+
},
225+
})
226+
}
227+
228+
// встряхиваем набор сценариев
229+
rnd.Shuffle(len(res), func(i, j int) {
230+
res[i], res[j] = res[j], res[i]
231+
})
232+
return
233+
}
234+
235+
func intInRange(rnd *rand.Rand, min, max int) int {
236+
return rnd.Intn(max-min) + min
237+
}

cmd/devopsmastertest/main_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package main
2+
3+
//go:generate go test -c -o=../../bin/devopsreskill
4+
5+
import (
6+
"os"
7+
"testing"
8+
9+
"github.com/stretchr/testify/suite"
10+
)
11+
12+
func TestMain(m *testing.M) {
13+
os.Exit(m.Run())
14+
}
15+
16+
func TestLesson01(t *testing.T) {
17+
suite.Run(t, new(Lesson01Suite))
18+
}

0 commit comments

Comments
 (0)