diff --git a/.gitignore b/.gitignore index 82ec5dd..330b20f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ vendor/ # Ignore IDE .idea/ +.vscode/ # Ignore artifacts cmd/shortenertest/shortenertest diff --git a/cmd/metricstest/fixtures_test.go b/cmd/metricstest/fixtures_test.go new file mode 100644 index 0000000..7509b37 --- /dev/null +++ b/cmd/metricstest/fixtures_test.go @@ -0,0 +1,325 @@ +package main + +import ( + "fmt" + "net" + "os" + "path/filepath" + "strconv" + "syscall" + "testing" + "time" + + "github.com/Yandex-Practicum/go-autotests/internal/fork" + "github.com/Yandex-Practicum/go-autotests/internal/random" + "github.com/go-resty/resty/v2" + "github.com/rekby/fixenv" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" +) + +const ( + startProcessTimeout = time.Second * 10 + checkPortInterval = time.Millisecond * 10 +) + +type Env struct { + fixenv.EnvT + assert.Assertions + Ctx context.Context + Require require.Assertions + + t *testing.T +} + +func New(t *testing.T) *Env { + ctx, ctxCancel := context.WithCancel(context.Background()) + t.Cleanup(ctxCancel) + + res := Env{ + EnvT: *fixenv.NewEnv(t), + Assertions: *assert.New(t), + Require: *require.New(t), + t: t, + Ctx: ctx, + } + return &res +} + +func (e *Env) Errorf(format string, args ...any) { + e.t.Helper() + e.t.Errorf(format, args...) +} + +func (e *Env) Fatalf(format string, args ...any) { + e.t.Helper() + e.T().Fatalf(format, args...) +} + +func (e *Env) Logf(format string, args ...any) { + e.t.Helper() + e.t.Logf(format, args...) +} + +func (e *Env) Test() *testing.T { + return e.t +} + +/// +/// В этих тестах используется библиотека fixenv. Она помогает создавать тестовое окружение. +/// Фикстура - это функция, которая может выполнять фоновую работу, возвращает значение и может выполнять очистку за собой после завершения теста. +/// Вызовы фикстуры идемпотентны, т.е. многократный вызов фикстуры с одними и теми же параметрами внутри одного окружения всегда будет возвращать один и тот же результат. +/// при этом фоновая работа выполняется только при первом вызове. +/// +/// Env - тестовое окружение, которое создаётся для каждого теста. +/// Подробнее о билиотеке можно почитать на странице https://github.com/rekby/fixenv +/// + +func ExistPath(e *Env, filePath string) string { + return fixenv.Cache(&e.EnvT, filePath, &fixenv.FixtureOptions{ + Scope: fixenv.ScopePackage, + }, func() (string, error) { + absFilePath, err := filepath.Abs(filePath) + if err != nil { + return "", err + } + e.Logf("Проверяю наличие файла: %q (%q)", absFilePath, filePath) + + _, err = os.Stat(filePath) + if err != nil { + return "", err + } + return filePath, nil + }) +} + +func AgentFilePath(e *Env) string { + return ExistPath(e, flagAgentBinaryPath) +} + +func AgentPollInterval(e *Env, setInterval ...time.Duration) time.Duration { + return fixenv.Cache(e, "", &fixenv.FixtureOptions{Scope: fixenv.ScopeTestAndSubtests}, func() (time.Duration, error) { + switch len(setInterval) { + case 0: + return time.Second, nil + case 1: + return setInterval[0], nil + default: + return 0, fmt.Errorf("В опциональном параметре можно передать максимум одно значение") + } + }) +} + +func AgentReportInterval(e *Env, setInterval ...time.Duration) time.Duration { + return fixenv.Cache(e, "", &fixenv.FixtureOptions{Scope: fixenv.ScopeTestAndSubtests}, func() (time.Duration, error) { + switch len(setInterval) { + case 0: + return 2 * time.Second, nil + case 1: + return setInterval[0], nil + default: + return 0, fmt.Errorf("В опциональном параметре можно передать максимум одно значение") + } + }) +} + +func ServerFilePath(e *Env) string { + return ExistPath(e, flagServerBinaryPath) +} + +func ConnectToServer(e *Env) *resty.Client { + return RestyClient(e, "http://"+ServerAddress(e)) +} + +func ServerAddress(e *Env) string { + return fixenv.Cache(e, "", nil, func() (string, error) { + res := fmt.Sprintf("%v:%v", ServerHost(e), ServerPort(e)) + e.Logf("Адрес сервера: %q", res) + return res, nil + }) +} + +func ServerHost(e *Env) string { + return "localhost" +} + +func ServerPort(e *Env, setPort ...int) int { + return fixenv.Cache(e, "", &fixenv.FixtureOptions{Scope: fixenv.ScopeTestAndSubtests}, func() (int, error) { + port := 0 + var err error + switch len(setPort) { + case 0: + e.Logf("Автоматический выбор серверного порта") + port, err = random.UnusedPort() + if err != nil { + return 0, err + } + case 1: + port = setPort[0] + e.Logf("Серверный порт задан вручную") + default: + return 0, fmt.Errorf("В опциональном параметре можно передать максимум одно значение") + } + e.Logf("Для сервера выбран порт: %v", port) + return port, err + }) +} + +func AgentSourcePath(e *Env) string { + return fixenv.Cache(e, nil, &fixenv.FixtureOptions{Scope: fixenv.ScopePackage}, func() (string, error) { + res := filepath.Join(TargetSourcePath(e), "cmd/agent") + e.Logf("Путь к исходникам агента: %q", res) + e.DirExists(res) + return res, nil + }) +} + +func ServerSourcePath(e *Env) string { + return fixenv.Cache(e, nil, &fixenv.FixtureOptions{Scope: fixenv.ScopePackage}, func() (string, error) { + res := filepath.Join(TargetSourcePath(e), "cmd/server") + e.Logf("Путь к исходникам сервера: %q", res) + e.DirExists(res) + return res, nil + }) +} + +func TargetSourcePath(e *Env) string { + return fixenv.Cache(e, nil, &fixenv.FixtureOptions{Scope: fixenv.ScopePackage}, func() (string, error) { + // project/cmd/server/server + startPath := flagServerBinaryPath + if startPath == "" { + startPath = flagAgentBinaryPath + } + + absPath, err := filepath.Abs(startPath) + e.NoError(err, "Не могу построить полный путь к начальному файлу %q") + + // project/cmd/server + absPath = filepath.Dir(absPath) + + // project/cmd + absPath = filepath.Dir(absPath) + + // project + absPath = filepath.Dir(absPath) + + e.Logf("Проверяю что %q (%q) - это папка", absPath, flagTargetSourcePath) + stat, err := os.Stat(absPath) + if err != nil { + e.Fatalf("Не могу получить информацию о папке", err) + return "", err + } + + if stat.IsDir() { + return absPath, nil + } + + return "", fmt.Errorf("%q - не папка", absPath) + }) +} + +func StartProcess(e *Env, name string, command string, args ...string) *fork.BackgroundProcess { + cacheKey := append([]string{name, command}, args...) + return fixenv.CacheWithCleanup(e, cacheKey, nil, func() (*fork.BackgroundProcess, fixenv.FixtureCleanupFunc, error) { + res := fork.NewBackgroundProcess(e.Ctx, command, fork.WithArgs(args...)) + + e.Logf("Запускаю %q: %q %#v", name, command, args) + err := res.Start(e.Ctx) + if err != nil { + return nil, nil, err + } + + cleanup := func() { + e.Logf("Останавливаю %q: %q %#v", name, command, args) + exitCode, stopErr := res.Stop(syscall.SIGINT, syscall.SIGKILL) + + stdOut := string(res.Stdout(context.Background())) + stdErr := string(res.Stderr(context.Background())) + + e.Logf("stdout:\n%v", stdOut) + e.Logf("stderr:\n%v", stdErr) + + if stopErr != nil { + e.Fatalf("Не получилось остановить процесс: %+v", stopErr) + } + if exitCode > 0 { + e.Logf("Ненулевой код возврата: %v", exitCode) + } + } + return res, cleanup, nil + }) +} + +func StartProcessWhichListenPort(e *Env, host string, port int, name string, command string, args ...string) *fork.BackgroundProcess { + cacheKey := append([]string{host, strconv.Itoa(port), name, command}, args...) + return fixenv.Cache(e, cacheKey, nil, func() (*fork.BackgroundProcess, error) { + process := StartProcess(e, name, command, args...) + address := fmt.Sprintf("%v:%v", host, port) + return process, waitOpenPort(e, address) + }) +} + +func ServerMock(e *Env, port int) *TestServerT { + return fixenv.CacheWithCleanup(e, port, nil, func() (*TestServerT, fixenv.FixtureCleanupFunc, error) { + endpoint := "localhost:" + strconv.Itoa(port) + res := NewTestServerT(e, endpoint) + + e.Logf("Запускаю мок сервер: %q", endpoint) + go func() { _ = res.Start() }() + + err := waitOpenPort(e, endpoint) + + return res, res.Stop, err + }) +} + +func RestyClient(e *Env, baseUrl string) *resty.Client { + return fixenv.Cache(e, baseUrl, nil, func() (*resty.Client, error) { + e.Logf("Создаётся клиент для %q", baseUrl) + return resty. + New(). + SetDebug(true). + SetBaseURL(baseUrl). + SetRedirectPolicy(resty.NoRedirectPolicy()). + SetLogger(restyLogger{e}), nil + }) +} + +type restyLogger struct { + e *Env +} + +func (l restyLogger) Errorf(format string, v ...interface{}) { + l.e.Logf("RESTY ERROR: "+format, v...) +} + +func (l restyLogger) Warnf(format string, v ...interface{}) { + l.e.Logf("resty warn: "+format, v...) +} + +func (l restyLogger) Debugf(format string, v ...interface{}) { + l.e.Logf("resty: "+format, v...) +} + +func waitOpenPort(e *Env, address string) error { + ctx, cancel := context.WithTimeout(e.Ctx, startProcessTimeout) + defer cancel() + + dialer := net.Dialer{} + e.Logf("Пробую подключиться на %q...", address) + + for { + time.Sleep(checkPortInterval) + conn, err := dialer.DialContext(ctx, "tcp", address) + if err == nil { + e.Logf("Закрываю успешное подключение") + err = conn.Close() + return err + } + if ctx.Err() != nil { + e.Fatalf("Ошибка подлючения: %+v", err) + return err + } + } +} diff --git a/cmd/metricstest/flags.go b/cmd/metricstest/flags.go index 41720da..883ed31 100644 --- a/cmd/metricstest/flags.go +++ b/cmd/metricstest/flags.go @@ -2,6 +2,15 @@ package main import ( "flag" + "strconv" + "time" +) + +const ( + serverDefaultHost = "localhost" + serverDefaultPort = 8080 + agentDefaultReportInterval = 10 * time.Second + agentDefaultPollInterval = 2 * time.Second ) var ( @@ -20,8 +29,8 @@ func init() { flag.StringVar(&flagAgentBinaryPath, "agent-binary-path", "", "path to target agent binary") flag.StringVar(&flagServerBinaryPath, "binary-path", "", "path to target server binary") flag.StringVar(&flagTargetSourcePath, "source-path", "", "path to target server source") - flag.StringVar(&flagServerHost, "server-host", "", "host of target address") - flag.StringVar(&flagServerPort, "server-port", "", "port of target address") + flag.StringVar(&flagServerHost, "server-host", serverDefaultHost, "host of target address") + flag.StringVar(&flagServerPort, "server-port", strconv.Itoa(serverDefaultPort), "port of target address") flag.StringVar(&flagServerBaseURL, "server-base-url", "", "base URL of target address") flag.StringVar(&flagFileStoragePath, "file-storage-path", "", "path to persistent file storage") flag.StringVar(&flagDatabaseDSN, "database-dsn", "", "connection string to database") diff --git a/cmd/metricstest/helper_server_test.go b/cmd/metricstest/helper_server_test.go new file mode 100644 index 0000000..9eb1965 --- /dev/null +++ b/cmd/metricstest/helper_server_test.go @@ -0,0 +1,143 @@ +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "strconv" + "sync" + + "github.com/gin-gonic/gin" +) + +type TestServerT struct { + e *Env + endpoint string + + httpServer http.Server + gin *gin.Engine + + m sync.Mutex + counters map[string][]int64 + gauges map[string][]float64 +} + +func NewTestServerT(e *Env, endpoint string) *TestServerT { + s := &TestServerT{ + e: e, + endpoint: endpoint, + counters: map[string][]int64{}, + gauges: map[string][]float64{}, + gin: gin.New(), + } + + s.httpServer.Addr = s.endpoint + s.httpServer.Handler = s.gin + + s.gin.RedirectTrailingSlash = false + s.gin.RedirectFixedPath = false + + s.gin.POST("/update/counter/:name/:value", s.storeCounter) + s.gin.POST("/update/gauge/:name/:value", s.storeGauge) + + s.gin.Any("/", s.unexpectedCall) + return s +} + +func (s *TestServerT) Start() error { + return s.httpServer.ListenAndServe() +} + +func (s *TestServerT) Stop() { + _ = s.httpServer.Shutdown(context.Background()) +} + +func (s *TestServerT) storeCounter(c *gin.Context) { + name := c.Param("name") + valS := c.Param("value") + val, err := strconv.ParseInt(valS, 10, 64) + if err != nil { + s.e.Errorf("Пришло значение counter не соответствующее формату. name: %q, value: %q, err: %+v", name, valS, err) + _ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("parse int value error: %w", err)) + return + } + + s.m.Lock() + defer s.m.Unlock() + s.counters[name] = append(s.counters[name], val) + s.e.Logf("Получено значение counter %q: %v", name, val) +} + +func (s *TestServerT) storeGauge(c *gin.Context) { + name := c.Param("name") + valS := c.Param("value") + + val, err := strconv.ParseFloat(valS, 64) + if err != nil { + s.e.Errorf("Пришло значение gauge не соответствующее формату. name: %q, value: %q, err: %+v", name, valS, err) + _ = c.AbortWithError(http.StatusBadRequest, fmt.Errorf("parse float value error: %w", err)) + return + } + + s.m.Lock() + defer s.m.Unlock() + s.gauges[name] = append(s.gauges[name], val) + s.e.Logf("Получено значение gauge %q: %f", name, val) +} + +func (s *TestServerT) unexpectedCall(c *gin.Context) { + c.AbortWithStatus(http.StatusBadRequest) + + s.e.Errorf("Пришёл неожиданный запрос, method: %q, uri: %q", c.Request.Method, c.Request.RequestURI) + content, err := io.ReadAll(c.Request.Body) + if err != nil { + s.e.Errorf("Не могу прочитать тело запроса:") + return + } + _ = c.Request.Body.Close() + s.e.Errorf("Тело запроса:\n%s", content) +} + +func (s *TestServerT) CheckReceiveValues(gauges []string, counters []string, min, max int) { + s.m.Lock() + defer s.m.Unlock() + + for _, name := range gauges { + values := s.gauges[name] + lenValues := len(values) + s.e.True(min <= lenValues && lenValues <= max, "Ожидается количество gauge значений от %v до %v, сейас получено: %v", min, max, lenValues) + } + + for _, name := range counters { + values := s.counters[name] + lenValues := len(values) + s.e.True(min <= lenValues && lenValues <= max, "Ожидается количество counter значений от %v до %v, сейас получено: %v", min, max, lenValues) + } +} + +func (s *TestServerT) GetLastCounter(name string) int64 { + s.m.Lock() + defer s.m.Unlock() + + val := s.counters[name] + if len(val) > 0 { + return val[len(val)-1] + } + + s.e.Errorf("Запрошено значение отсутствующего счётчика counter: %q", name) + return 0 +} + +func (s *TestServerT) GetLastGauge(name string) float64 { + s.m.Lock() + defer s.m.Unlock() + + val := s.gauges[name] + if len(val) > 0 { + return val[len(val)-1] + } + + s.e.Errorf("Запрошено значение отсутствующего счётчика counter: %q", name) + return 0 +} diff --git a/cmd/metricstest/iteration1_test.go b/cmd/metricstest/iteration1_test.go index 7f8b6ab..569a8fa 100644 --- a/cmd/metricstest/iteration1_test.go +++ b/cmd/metricstest/iteration1_test.go @@ -1,245 +1,117 @@ package main import ( - "context" - "errors" + "fmt" "net/http" - "os" - "syscall" - "time" - - "github.com/go-resty/resty/v2" - "github.com/stretchr/testify/suite" - - "github.com/Yandex-Practicum/go-autotests/internal/fork" + "testing" ) -type Iteration1Suite struct { - suite.Suite - - serverAddress string - serverProcess *fork.BackgroundProcess +func TestIteration1(t *testing.T) { + testServerIncrement1(t, StartDefaultServer) } -func (suite *Iteration1Suite) SetupSuite() { - // check required flags - suite.Require().NotEmpty(flagServerBinaryPath, "-binary-path non-empty flag required") - - suite.serverAddress = "http://localhost:8080" - - envs := append(os.Environ(), []string{ - "RESTORE=false", - }...) - p := fork.NewBackgroundProcess(context.Background(), flagServerBinaryPath, - fork.WithEnv(envs...), - ) - - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - - err := p.Start(ctx) - if err != nil { - suite.T().Errorf("Невозможно запустить процесс командой %s: %s. Переменные окружения: %+v", p, err, envs) - return - } - - port := "8080" - err = p.WaitPort(ctx, "tcp", port) - if err != nil { - suite.T().Errorf("Не удалось дождаться пока порт %s станет доступен для запроса: %s", port, err) - return - } - - suite.serverProcess = p +func StartDefaultServer(e *Env) string { + StartProcessWhichListenPort(e, serverDefaultHost, serverDefaultPort, "metric server", ServerFilePath(e)) + return fmt.Sprintf("http://%v:%v", serverDefaultHost, serverDefaultPort) } -func (suite *Iteration1Suite) TearDownSuite() { - if suite.serverProcess == nil { - return - } - - exitCode, err := suite.serverProcess.Stop(syscall.SIGINT, syscall.SIGKILL) - if err != nil { - if errors.Is(err, os.ErrProcessDone) { - return - } - suite.T().Logf("Не удалось остановить процесс с помощью сигнала ОС: %s", err) - return - } - - if exitCode > 0 { - suite.T().Logf("Процесс завершился с не нулевым статусом %d", exitCode) - } - - // try to read stdout/stderr - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - defer cancel() - - out := suite.serverProcess.Stderr(ctx) - if len(out) > 0 { - suite.T().Logf("Получен STDERR лог процесса:\n\n%s", string(out)) - } - out = suite.serverProcess.Stdout(ctx) - if len(out) > 0 { - suite.T().Logf("Получен STDOUT лог процесса:\n\n%s", string(out)) - } -} +// testServerIncrement1 тестирует функционал первого инкремента с кастомной функцией старта сервера. +// нужна, чтобы продолжать тестировать этот функционал в следующих инкрементах с переменой условий - другие порты, другие хранилища +func testServerIncrement1(t *testing.T, startServer func(e *Env) string) { + t.Run("TestCounterHandlers", func(t *testing.T) { + t.Run("ok", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + req := c.R() + resp, err := req.Post("update/counter/testGauge/100") + e.Require.NoError(err, "Ошибка при выполнении запроса") + e.Equal(http.StatusOK, resp.StatusCode(), "При добавлении нового значения сервер должен возвращать код 200 (http.StatusOK)") + + req = c.R() + resp, err = req.Post("update/counter/testGauge/101") + e.Require.NoError(err, "Ошибка при выполнении запроса") + e.Equal(http.StatusOK, resp.StatusCode(), "При обновлении значения сервер должен возвращать код 200 (http.StatusOK)") + }) + + t.Run("without-id", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + req := c.R() + + resp, err := req.Post("update/counter/testGauge/") + e.Require.NoError(err, "Ошибка при выполнении запроса") + e.Contains([]int{http.StatusBadRequest, http.StatusNotFound}, resp.StatusCode(), + "При попытке обновления значения без ID - сервер должен вернуть ошибку 400 или 404 (http.StatusBadRequest, http.StatusNotFound).") + }) + + t.Run("bad value", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + req := c.R() + + resp, err := req.Post("update/gauge/testGauge/bad-value") + e.Require.NoError(err, "Ошибка при выполнении запроса") + e.Equal(http.StatusBadRequest, resp.StatusCode(), "При получении неправильного значения сервер должен вернуть ошибку 400 (http.StatusBadRequest)") + }) -// TestHandlers проверяет -// сервер успешно стартует и открывет tcp порт 8080 на 127.0.0.1 -// обработку POST запросов вида: ?id=&value=&type= -// а так же негативкейсы, запросы в которых отсутствуют id, value и задан не корректный type -func (suite *Iteration1Suite) TestGaugeHandlers() { - // create HTTP client without redirects support - errRedirectBlocked := errors.New("HTTP redirect blocked") - redirPolicy := resty.RedirectPolicyFunc(func(_ *http.Request, _ []*http.Request) error { - return errRedirectBlocked }) - httpc := resty.New(). - SetHostURL(suite.serverAddress). - SetRedirectPolicy(redirPolicy) - - suite.Run("update", func() { - req := httpc.R() - resp, err := req.Post("update/gauge/testGauge/100") - - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением gauge") - - validStatus := suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } + t.Run("TestGaugeHandlers", func(t *testing.T) { + t.Run("ok", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + req := c.R() + resp, err := req.Post("update/gauge/testGauge/100") + e.Require.NoError(err, "Ошибка при выполнении запроса") + e.Equal(http.StatusOK, resp.StatusCode(), "При добавлении нового значения сервер должен возвращать код http.StatusOK (200)") + + req = c.R() + resp, err = req.Post("update/gauge/testGauge/101") + e.Require.NoError(err, "Ошибка при выполнении запроса") + e.Equal(http.StatusOK, resp.StatusCode(), "При обновлении значения сервер должен возвращать код http.StatusOK (200)") + }) + + t.Run("without-id", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + req := c.R() + + resp, err := req.Post("update/gauge/testGauge/") + e.Require.NoError(err, "Ошибка при выполнении запроса") + e.Contains([]int{http.StatusBadRequest, http.StatusNotFound}, resp.StatusCode(), + "При попытке обновления значения без ID - сервер должен вернуть ошибку http.StatusBadRequest или http.StatusNotFound (400, 404).") + }) + + t.Run("bad value", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + req := c.R() + + resp, err := req.Post("update/gauge/testGauge/bad-value") + e.Require.NoError(err, "Ошибка при выполнении запроса") + e.Equal(http.StatusBadRequest, resp.StatusCode(), "При получении неправильного значения сервер должен вернуть ошибку http.StatusBadRequest (400)") + }) }) - suite.Run("without id", func() { - req := httpc.R() - resp, err := req.Post("update/gauge/") - - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением gauge") + t.Run("unexpected path", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + req := c.R() - validStatus := suite.Assert().Equalf(http.StatusNotFound, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) + for _, path := range []string{"unknown-path", "unknown-path/gauge/testGauge/100"} { + resp, err := req.Post(path) + e.Require.NoError(err, "Ошибка при выполнении запроса") + e.Equal(http.StatusNotFound, resp.StatusCode(), "При получении запроса к неизвестному пути сервер должен возвращать ошибку http.StatusNotFound (404)") } }) - suite.Run("invalid value", func() { - req := httpc.R() - resp, err := req.Post("update/gauge/testGauge/none") - - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением gauge") - - validStatus := suite.Assert().Equalf(http.StatusBadRequest, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } - }) -} - -func (suite *Iteration1Suite) TestCounterHandlers() { - // create HTTP client without redirects support - errRedirectBlocked := errors.New("HTTP redirect blocked") - redirPolicy := resty.RedirectPolicyFunc(func(_ *http.Request, _ []*http.Request) error { - return errRedirectBlocked - }) - - httpc := resty.New(). - SetHostURL(suite.serverAddress). - SetRedirectPolicy(redirPolicy) - - suite.Run("update", func() { - req := httpc.R() - resp, err := req.Post("update/counter/testCounter/100") - - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением counter") - - validStatus := suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } - }) - - suite.Run("without id", func() { - req := httpc.R() - resp, err := req.Post("update/counter/") - - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением counter") - - validStatus := suite.Assert().Equalf(http.StatusNotFound, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } - }) - - suite.Run("invalid value", func() { - req := httpc.R() - resp, err := req.Post("update/counter/testCounter/none") - - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением counter") - - validStatus := suite.Assert().Equalf(http.StatusBadRequest, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } - }) -} - -func (suite *Iteration1Suite) TestUnknownHandlers() { - errRedirectBlocked := errors.New("HTTP redirect blocked") - redirPolicy := resty.RedirectPolicyFunc(func(_ *http.Request, _ []*http.Request) error { - return errRedirectBlocked - }) - - httpc := resty.New(). - SetHostURL(suite.serverAddress). - SetRedirectPolicy(redirPolicy) - - suite.Run("update invalid type", func() { - req := httpc.R() - resp, err := req.Post("update/unknown/testCounter/100") - - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с не корректным типом метрики") - - validStatus := suite.Assert().Equalf(http.StatusNotImplemented, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } - }) - - suite.Run("update invalid method", func() { - req := httpc.R() - resp, err := req.Post("updater/counter/testCounter/100") - - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с не корректным типом метрики") - - validStatus := suite.Assert().Equalf(http.StatusNotFound, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } + t.Run("unknown-metric-type", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + req := c.R() + resp, err := req.Post("update/unknown/testGauge/100") + e.Require.NoError(err, "Ошибка при выполнении запроса") + e.Contains([]int{http.StatusBadRequest, http.StatusNotFound}, resp.StatusCode(), + "При попытке обновления метрики неизвестного типа сервер должен вернуть ошибку http.StatusBadRequest или http.StatusNotFound (400, 404)") }) } diff --git a/cmd/metricstest/iteration2a_test.go b/cmd/metricstest/iteration2a_test.go index 6a15744..0250ed5 100644 --- a/cmd/metricstest/iteration2a_test.go +++ b/cmd/metricstest/iteration2a_test.go @@ -1,91 +1,52 @@ package main import ( - "context" - "errors" - "os" - "syscall" + "testing" "time" - - "github.com/stretchr/testify/suite" - - "github.com/Yandex-Practicum/go-autotests/internal/fork" ) -type Iteration2ASuite struct { - suite.Suite - - agentAddress string - agentProcess *fork.BackgroundProcess +func TestIteration2A(t *testing.T) { + e := New(t) + ServerPort(e, serverDefaultPort) + AgentReportInterval(e, agentDefaultReportInterval) + AgentPollInterval(e, agentDefaultPollInterval) + testAgentIncrement2(e, StartDefaultAgent) } -func (suite *Iteration2ASuite) SetupSuite() { - // check required flags - suite.Require().NotEmpty(flagAgentBinaryPath, "-agent-binary-path non-empty flag required") - - suite.agentAddress = "http://localhost:8080" +func StartDefaultAgent(e *Env) { + StartProcess(e, "agent", AgentFilePath(e)) +} - envs := append(os.Environ(), []string{ - "RESTORE=false", - }...) - p := fork.NewBackgroundProcess(context.Background(), flagAgentBinaryPath, - fork.WithEnv(envs...), - ) +func testAgentIncrement2(e *Env, startAgent func(e *Env)) { + e.Logf("Тестирование функционала второго инкремента") - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() + serverMock := ServerMock(e, ServerPort(e)) - err := p.Start(ctx) - if err != nil { - suite.T().Errorf("Невозможно запустить процесс командой %s: %s. Переменные окружения: %+v", p, err, envs) - return + gauges := []string{ + "Alloc", "BuckHashSys", "Frees", "GCCPUFraction", "GCSys", "HeapAlloc", "HeapIdle", "HeapInuse", "HeapObjects", "HeapReleased", "HeapSys", "LastGC", "Lookups", "MCacheInuse", "MCacheSys", "MSpanInuse", "MSpanSys", "Mallocs", "NextGC", + "NumForcedGC", "NumGC", "OtherSys", "PauseTotalNs", "StackInuse", "StackSys", "Sys", "TotalAlloc", "RandomValue", } - - port := "8080" - err = p.ListenPort(ctx, "tcp", port) - if err != nil { - suite.T().Errorf("Не удалось дождаться пока на порт %s начнут поступать данные: %s", port, err) - return + counters := []string{ + "PollCount", } - suite.agentProcess = p -} - -func (suite *Iteration2ASuite) TearDownSuite() { - if suite.agentProcess == nil { - return - } + startAgent(e) - exitCode, err := suite.agentProcess.Stop(syscall.SIGINT, syscall.SIGKILL) - if err != nil { - if errors.Is(err, os.ErrProcessDone) { - return - } - suite.T().Logf("Не удалось остановить процесс с помощью сигнала ОС: %s", err) - return - } + reportInterval := AgentReportInterval(e) + pollInterval := AgentPollInterval(e) + firstIterationTimeout := reportInterval + reportInterval/2 - if exitCode > 0 { - suite.T().Logf("Процесс завершился с не нулевым статусом %d", exitCode) - } + e.Logf("Жду %v", firstIterationTimeout) + time.Sleep(firstIterationTimeout) - // try to read stdout/stderr - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - defer cancel() + serverMock.CheckReceiveValues(gauges, counters, 1, 2) + firstRandom := serverMock.GetLastGauge("RandomValue") + e.InDelta(int(reportInterval/pollInterval), serverMock.GetLastCounter("PollCount"), 1) - out := suite.agentProcess.Stderr(ctx) - if len(out) > 0 { - suite.T().Logf("Получен STDERR лог процесса:\n\n%s", string(out)) - } - out = suite.agentProcess.Stdout(ctx) - if len(out) > 0 { - suite.T().Logf("Получен STDOUT лог процесса:\n\n%s", string(out)) - } -} + e.Logf("Жду ещё %v", reportInterval) + time.Sleep(reportInterval) -// TestAgent проверяет -// агент успешно стартует и передает какие-то данные по tcp, на 127.0.0.1:8080 -func (suite *Iteration2ASuite) TestAgent() { - suite.Run("receive data from agent", func() { - }) + serverMock.CheckReceiveValues(gauges, counters, 2, 3) + e.InDelta(reportInterval/pollInterval*2, serverMock.GetLastCounter("PollCount"), 1) + e.NotEqual(firstRandom, serverMock.GetLastGauge("RandomValue"), "Случайное значение не поменялось при повторной отправке") } diff --git a/cmd/metricstest/iteration2b_test.go b/cmd/metricstest/iteration2b_test.go index ae73252..17f9e2d 100644 --- a/cmd/metricstest/iteration2b_test.go +++ b/cmd/metricstest/iteration2b_test.go @@ -9,11 +9,87 @@ import ( "path/filepath" "regexp" "strings" + "testing" "time" "github.com/stretchr/testify/suite" ) +func TestIteration2B(t *testing.T) { + commonEnv := New(t) + table := []struct { + name, path string + }{ + { + "agent", + AgentSourcePath(commonEnv), + }, + { + "server", + ServerSourcePath(commonEnv), + }, + } + + for _, test := range table { + t.Run(test.name, func(t *testing.T) { + t.Run("TestFilesPresence", func(t *testing.T) { + e := New(t) + + sourcePath := test.path + + hasTestFile := false + err := filepath.WalkDir(sourcePath, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + // skip vendor directory + if d.Name() == "vendor" || d.Name() == ".git" { + return filepath.SkipDir + } + // dive into regular directory + return nil + } + + if strings.HasSuffix(d.Name(), "_test.go") { + hasTestFile = true + return filepath.SkipAll + } + + return nil + }) + + e.NoErrorf(err, "Неожиданная ошибка при поиске тестовых файлов по пути %s: %s", sourcePath, err) + + if !hasTestFile { + e.Errorf("Не найден ни один тестовый файл по пути %s", sourcePath) + } + }) + + t.Run("TestAgentCoverage", func(t *testing.T) { + e := New(t) + sourcePath := test.path + + coverRegex := regexp.MustCompile(`coverage: (\d+.\d)% of statements`) + + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + defer cancel() + + cmd := exec.CommandContext(ctx, "go", "test", "-cover", sourcePath) + cmd.Env = os.Environ() // pass parent envs + cmd.Dir = sourcePath + out, err := cmd.CombinedOutput() + e.NoError(err, "Невозможно получить результат выполнения команды: %s. Вывод:\n\n %s", cmd, out) + + matched := coverRegex.Match(out) + e.True(matched, "Отсутствует информация о покрытии кода тестами, команда: %s", cmd) + e.Logf("Вывод команды:\n\n%s", string(out)) + }) + }) + } +} + type Iteration2BSuite struct { suite.Suite @@ -66,20 +142,4 @@ func (suite *Iteration2BSuite) TestFilesPresence() { // TestServerCoverage attempts to obtain and parse coverage report using standard Go tooling func (suite *Iteration2BSuite) TestServerCoverage() { - sourcePath := strings.TrimRight(flagTargetSourcePath, "/") + "/..." - - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) - defer cancel() - - cmd := exec.CommandContext(ctx, "go", "test", "-cover", sourcePath) - cmd.Env = os.Environ() // pass parent envs - out, err := cmd.CombinedOutput() - suite.Assert().NoError(err, "Невозможно получить результат выполнения команды: %s. Вывод:\n\n %s", cmd, out) - - matched := suite.coverRegex.Match(out) - found := suite.Assert().True(matched, "Отсутствует информация о покрытии кода тестами, команда: %s", cmd) - - if !found { - suite.T().Logf("Вывод команды:\n\n%s", string(out)) - } } diff --git a/cmd/metricstest/iteration3a_test.go b/cmd/metricstest/iteration3a_test.go index 9b4459c..788b9fe 100644 --- a/cmd/metricstest/iteration3a_test.go +++ b/cmd/metricstest/iteration3a_test.go @@ -1,103 +1,126 @@ package main import ( - "errors" - - "github.com/stretchr/testify/suite" + "net/http" + "strconv" + "testing" ) -type Iteration3ASuite struct { - suite.Suite - - knownFrameworks []string +func TestIteration3A(t *testing.T) { + testServerIncrement3(t, StartDefaultServer) } -func (suite *Iteration3ASuite) SetupSuite() { - // check required flags - suite.Require().NotEmpty(flagTargetSourcePath, "-source-path non-empty flag required") - - suite.knownFrameworks = []string{ - "aahframework.org", - "confetti-framework.com", - "github.com/abahmed/gearbox", - "github.com/aerogo/aero", - "github.com/aisk/vox", - "github.com/ant0ine/go-json-rest", - "github.com/aofei/air", - "github.com/appist/appy", - "github.com/astaxie/beego", - "github.com/beatlabs/patron", - "github.com/bnkamalesh/webgo", - "github.com/buaazp/fasthttprouter", - "github.com/claygod/Bxog", - "github.com/claygod/microservice", - "github.com/dimfeld/httptreemux", - "github.com/dinever/golf", - "github.com/fulldump/golax", - "github.com/gernest/alien", - "github.com/gernest/utron", - "github.com/gin-gonic/gin", - "github.com/go-chi/chi", - "github.com/go-goyave/goyave", - "github.com/go-macaron/macaron", - "github.com/go-ozzo/ozzo-routing", - "github.com/go-playground/lars", - "github.com/go-playground/pure", - "github.com/go-zoo/bone", - "github.com/goa-go/goa", - "github.com/goadesign/goa", - "github.com/goanywhere/rex", - "github.com/gocraft/web", - "github.com/gofiber/fiber", - "github.com/goji/goji", - "github.com/gookit/rux", - "github.com/gorilla/mux", - "github.com/goroute/route", - "github.com/gotuna/gotuna", - "github.com/gowww/router", - "github.com/GuilhermeCaruso/bellt", - "github.com/hidevopsio/hiboot", - "github.com/husobee/vestigo", - "github.com/i-love-flamingo/flamingo", - "github.com/i-love-flamingo/flamingo-commerce", - "github.com/ivpusic/neo", - "github.com/julienschmidt/httprouter", - "github.com/labstack/echo", - "github.com/lunny/tango", - "github.com/mustafaakin/gongular", - "github.com/nbari/violetear", - "github.com/nsheremet/banjo", - "github.com/NYTimes/gizmo", - "github.com/paulbellamy/mango", - "github.com/rainycape/gondola", - "github.com/razonyang/fastrouter", - "github.com/rcrowley/go-tigertonic", - "github.com/resoursea/api", - "github.com/revel/revel", - "github.com/rs/xmux", - "github.com/twharmon/goweb", - "github.com/uadmin/uadmin", - "github.com/ungerik/go-rest", - "github.com/vardius/gorouter", - "github.com/VividCortex/siesta", - "github.com/xujiajun/gorouter", - "github.com/xxjwxc/ginrpc", - "github.com/yarf-framework/yarf", - "github.com/zpatrick/fireball", - "gobuffalo.io", - "rest-layer.io", - } -} +func testServerIncrement3(t *testing.T, startServer func(e *Env) string) { + t.Log("Тестирование функционала 3го спринта") + t.Run("TestCounter", func(t *testing.T) { + t.Run("ok", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + + resp, err := c.R().Post("update/counter/test1/1") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equal(http.StatusOK, resp.StatusCode(), "При добавлении нового значения сервер должен вернуть код 200 (http.StatusOK)") + + resp, err = c.R().Get("value/counter/test1") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equalf(http.StatusOK, resp.StatusCode(), "При получении известного значения метрики сервер должен вернуть код 200 (http.StatusOK)") + + val, err := strconv.Atoi(string(resp.Body())) + e.NoErrorf(err, "Ошибка при попытке распарсить значение %q", resp.Body()) + e.Equal(1, val, "Сервер вернул не то значение, которое было отправлено") + + resp, err = c.R().Post("update/counter/test1/2") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equal(http.StatusOK, resp.StatusCode(), "При добавлении нового значения сервер должен вернуть код 200 (http.StatusOK)") + + resp, err = c.R().Get("value/counter/test1") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + val, err = strconv.Atoi(string(resp.Body())) + e.NoErrorf(err, "Ошибка при попытке распарсить обновлённое значение %q", resp.Body()) + e.Equal(3, val, "Сервер должен был вернуть сумму отправленных значений: 1+2") + + // добавляем второе значение + resp, err = c.R().Post("update/counter/test2/4") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equal(http.StatusOK, resp.StatusCode(), "При добавлении нового значения сервер должен вернуть код 200 (http.StatusOK)") + + resp, err = c.R().Get("value/counter/test2") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equalf(http.StatusOK, resp.StatusCode(), "При получении известного значения метрики сервер должен вернуть код 200 (http.StatusOK)") + + val, err = strconv.Atoi(string(resp.Body())) + e.NoErrorf(err, "Ошибка при попытке распарсить значение %q", resp.Body()) + e.Equal(4, val, "Сервер вернул не то значение, которое было отправлено для второй метрики") + + resp, err = c.R().Get("value/counter/test1") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + val, err = strconv.Atoi(string(resp.Body())) + e.NoErrorf(err, "Ошибка при попытке распарсить обновлённое значение %q", resp.Body()) + e.Equal(3, val, "Сервер неправильно вернул значение первого счётчика") + }) + + t.Run("unknown-value", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + resp, err := c.R().Get("value/counter/unknown") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equal(http.StatusNotFound, resp.StatusCode(), "При запросе неизвестного значения должен возвращаться код 404 (http.StatusNotFound)") + }) + }) + + t.Run("TestGauge", func(t *testing.T) { + t.Run("ok", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + + resp, err := c.R().Post("update/gauge/test1/1.5") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equal(http.StatusOK, resp.StatusCode(), "При добавлении нового значения сервер должен вернуть код 200 (http.StatusOK)") + + resp, err = c.R().Get("value/gauge/test1") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equalf(http.StatusOK, resp.StatusCode(), "При получении известного значения метрики сервер должен вернуть код 200 (http.StatusOK)") + + val, err := strconv.ParseFloat(string(resp.Body()), 64) + e.NoErrorf(err, "Ошибка при попытке распарсить значение %q", resp.Body()) + e.InEpsilon(1.5, val, 0.1, "Сервер вернул не то значение, которое было отправлено") + + resp, err = c.R().Post("update/gauge/test1/2") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equal(http.StatusOK, resp.StatusCode(), "При добавлении нового значения сервер должен вернуть код 200 (http.StatusOK)") + + resp, err = c.R().Get("value/gauge/test1") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + val, err = strconv.ParseFloat(string(resp.Body()), 64) + e.NoErrorf(err, "Ошибка при попытке распарсить обновлённое значение %q", resp.Body()) + e.InEpsilon(2, val, 0.1, "Сервер вернул не то значение, которое было отправлено при обновлении существующего значения") + + // добавляем второе значение + resp, err = c.R().Post("update/gauge/test2/4") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equal(http.StatusOK, resp.StatusCode(), "При добавлении нового значения сервер должен вернуть код 200 (http.StatusOK)") + + resp, err = c.R().Get("value/gauge/test2") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equalf(http.StatusOK, resp.StatusCode(), "При получении известного значения метрики сервер должен вернуть код 200 (http.StatusOK)") + + val, err = strconv.ParseFloat(string(resp.Body()), 64) + e.NoErrorf(err, "Ошибка при попытке распарсить значение %q", resp.Body()) + e.InEpsilon(4, val, 0.1, "Сервер вернул не то значение, которое было отправлено для второй метрики") + + resp, err = c.R().Get("value/gauge/test1") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + val, err = strconv.ParseFloat(string(resp.Body()), 64) + e.NoErrorf(err, "Ошибка при попытке распарсить обновлённое значение %q", resp.Body()) + e.InEpsilon(2, val, 0.1, "Сервер неправильно вернул значение первого счётчика") + }) -// TestFrameworkUsage attempts to recursively find usage of known HTTP frameworks in given sources -func (suite *Iteration3ASuite) TestHTTPFrameworkUsage() { - err := usesKnownPackage(suite.T(), flagTargetSourcePath, suite.knownFrameworks) - if errors.Is(err, errUsageFound) { - return - } - if err == nil || errors.Is(err, errUsageNotFound) { - suite.T().Errorf("Не найдено использование хотя бы одного известного HTTP фреймворка по пути %q", flagTargetSourcePath) - return - } - suite.T().Errorf("Неожиданная ошибка при поиске использования фреймворка по пути %q, %v", flagTargetSourcePath, err) + t.Run("unknown-value", func(t *testing.T) { + e := New(t) + c := RestyClient(e, startServer(e)) + resp, err := c.R().Get("value/gauge/unknown") + e.NoError(err, "Ошибка при выполнении запроса к серверу") + e.Equal(http.StatusNotFound, resp.StatusCode(), "При запросе неизвестного значения должен возвращаться код 404 (http.StatusNotFound)") + }) + }) } diff --git a/cmd/metricstest/iteration3b_test.go b/cmd/metricstest/iteration3b_test.go index 640d241..858fcac 100644 --- a/cmd/metricstest/iteration3b_test.go +++ b/cmd/metricstest/iteration3b_test.go @@ -1,228 +1,93 @@ package main import ( - "context" "errors" - "fmt" - "math/rand" - "net/http" - "os" - "strconv" - "strings" - "syscall" - "time" - - "github.com/go-resty/resty/v2" - "github.com/stretchr/testify/suite" - - "github.com/Yandex-Practicum/go-autotests/internal/fork" + "testing" ) -type Iteration3BSuite struct { - suite.Suite - - serverAddress string - serverProcess *fork.BackgroundProcess -} - -func (suite *Iteration3BSuite) SetupSuite() { - // check required flags - suite.Require().NotEmpty(flagServerBinaryPath, "-binary-path non-empty flag required") - - suite.serverAddress = "http://localhost:8080" - - envs := append(os.Environ(), []string{ - "RESTORE=false", - }...) - p := fork.NewBackgroundProcess(context.Background(), flagServerBinaryPath, - fork.WithEnv(envs...), - ) - - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - - err := p.Start(ctx) - if err != nil { - suite.T().Errorf("Невозможно запустить процесс командой %s: %s. Переменные окружения: %+v", p, err, envs) - return - } - - port := "8080" - err = p.WaitPort(ctx, "tcp", port) - if err != nil { - suite.T().Errorf("Не удалось дождаться пока порт %s станет доступен для запроса: %s", port, err) - return +func TestIteration3B(t *testing.T) { + knownFrameworks := []string{ + "aahframework.org", + "confetti-framework.com", + "github.com/abahmed/gearbox", + "github.com/aerogo/aero", + "github.com/aisk/vox", + "github.com/ant0ine/go-json-rest", + "github.com/aofei/air", + "github.com/appist/appy", + "github.com/astaxie/beego", + "github.com/beatlabs/patron", + "github.com/bnkamalesh/webgo", + "github.com/buaazp/fasthttprouter", + "github.com/claygod/Bxog", + "github.com/claygod/microservice", + "github.com/dimfeld/httptreemux", + "github.com/dinever/golf", + "github.com/fulldump/golax", + "github.com/gernest/alien", + "github.com/gernest/utron", + "github.com/gin-gonic/gin", + "github.com/go-chi/chi", + "github.com/go-goyave/goyave", + "github.com/go-macaron/macaron", + "github.com/go-ozzo/ozzo-routing", + "github.com/go-playground/lars", + "github.com/go-playground/pure", + "github.com/go-zoo/bone", + "github.com/goa-go/goa", + "github.com/goadesign/goa", + "github.com/goanywhere/rex", + "github.com/gocraft/web", + "github.com/gofiber/fiber", + "github.com/goji/goji", + "github.com/gookit/rux", + "github.com/gorilla/mux", + "github.com/goroute/route", + "github.com/gotuna/gotuna", + "github.com/gowww/router", + "github.com/GuilhermeCaruso/bellt", + "github.com/hidevopsio/hiboot", + "github.com/husobee/vestigo", + "github.com/i-love-flamingo/flamingo", + "github.com/i-love-flamingo/flamingo-commerce", + "github.com/ivpusic/neo", + "github.com/julienschmidt/httprouter", + "github.com/labstack/echo", + "github.com/lunny/tango", + "github.com/mustafaakin/gongular", + "github.com/nbari/violetear", + "github.com/nsheremet/banjo", + "github.com/NYTimes/gizmo", + "github.com/paulbellamy/mango", + "github.com/rainycape/gondola", + "github.com/razonyang/fastrouter", + "github.com/rcrowley/go-tigertonic", + "github.com/resoursea/api", + "github.com/revel/revel", + "github.com/rs/xmux", + "github.com/twharmon/goweb", + "github.com/uadmin/uadmin", + "github.com/ungerik/go-rest", + "github.com/vardius/gorouter", + "github.com/VividCortex/siesta", + "github.com/xujiajun/gorouter", + "github.com/xxjwxc/ginrpc", + "github.com/yarf-framework/yarf", + "github.com/zpatrick/fireball", + "gobuffalo.io", + "rest-layer.io", } - suite.serverProcess = p -} + e := New(t) + serverSourcePath := ServerSourcePath(e) + err := usesKnownPackage(t, serverSourcePath, knownFrameworks) -func (suite *Iteration3BSuite) TearDownSuite() { - if suite.serverProcess == nil { + if errors.Is(err, errUsageFound) { return } - - exitCode, err := suite.serverProcess.Stop(syscall.SIGINT, syscall.SIGKILL) - if err != nil { - if errors.Is(err, os.ErrProcessDone) { - return - } - suite.T().Logf("Не удалось остановить процесс с помощью сигнала ОС: %s", err) + if err == nil || errors.Is(err, errUsageNotFound) { + e.Errorf("Не найдено использование хотя бы одного известного HTTP фреймворка по пути %q", serverSourcePath) return } - - if exitCode > 0 { - suite.T().Logf("Процесс завершился с не нулевым статусом %d", exitCode) - } - - // try to read stdout/stderr - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - defer cancel() - - out := suite.serverProcess.Stderr(ctx) - if len(out) > 0 { - suite.T().Logf("Получен STDERR лог процесса:\n\n%s", string(out)) - } - out = suite.serverProcess.Stdout(ctx) - if len(out) > 0 { - suite.T().Logf("Получен STDOUT лог процесса:\n\n%s", string(out)) - } -} - -func (suite *Iteration3BSuite) TestGauge() { - // create HTTP client without redirects support - errRedirectBlocked := errors.New("HTTP redirect blocked") - redirPolicy := resty.RedirectPolicyFunc(func(_ *http.Request, _ []*http.Request) error { - return errRedirectBlocked - }) - - httpc := resty.NewWithClient(&http.Client{ - Transport: &http.Transport{ - DisableCompression: true, - }, - }). - SetHostURL(suite.serverAddress). - SetRedirectPolicy(redirPolicy) - - count := 3 - suite.Run("update sequence", func() { - id := strconv.Itoa(rand.Intn(256)) - req := httpc.R() - for i := 0; i < count; i++ { - v := strings.TrimRight(fmt.Sprintf("%.3f", rand.Float64()*1000000), "0") - resp, err := req.Post("update/gauge/testSetGet" + id + "/" + v) - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением gauge") - - validStatus := suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } - - resp, err = req.Get("value/gauge/testSetGet" + id) - noRespErr = suite.Assert().NoError(err, "Ошибка при попытке сделать запрос для получения значения gauge") - validStatus = suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - equality := suite.Assert().Equalf(v, resp.String(), - "Несоответствие отправленного значения gauge (%s) полученному от сервера (%s), '%s %s'", v, resp.String(), req.Method, req.URL) - - if !noRespErr || !validStatus || !equality { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } - } - }) - - suite.Run("get unknown", func() { - id := strconv.Itoa(rand.Intn(256)) - req := httpc.R() - resp, err := req.Get("value/gauge/testUnknown" + id) - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос для получения значения gauge") - validStatus := suite.Assert().Equalf(http.StatusNotFound, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } - }) -} - -func (suite *Iteration3BSuite) TestCounter() { - // create HTTP client without redirects support - errRedirectBlocked := errors.New("HTTP redirect blocked") - redirPolicy := resty.RedirectPolicyFunc(func(_ *http.Request, _ []*http.Request) error { - return errRedirectBlocked - }) - - httpc := resty.NewWithClient(&http.Client{ - Transport: &http.Transport{ - DisableCompression: true, - }, - }). - SetHostURL(suite.serverAddress). - SetRedirectPolicy(redirPolicy) - - count := 3 - - suite.Run("update sequence", func() { - req := httpc.R() - id := strconv.Itoa(rand.Intn(256)) - resp, err := req.Get("value/counter/testSetGet" + id) - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос для получения значения counter") - - if !noRespErr { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - return - } - - a, _ := strconv.ParseInt(resp.String(), 0, 64) - - for i := 0; i < count; i++ { - v := rand.Intn(1024) - a += int64(v) - resp, err = req.Post("update/counter/testSetGet" + id + "/" + strconv.Itoa(v)) - - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос для обновления значения counter") - validStatus := suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - continue - } - - resp, err := req.Get("value/counter/testSetGet" + id) - noRespErr = suite.Assert().NoError(err, "Ошибка при попытке сделать запрос для получения значения counter") - validStatus = suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - equality := suite.Assert().Equalf(fmt.Sprintf("%d", a), resp.String(), - "Несоответствие отправленного значения counter (%d) полученному от сервера (%s), '%s %s'", a, resp.String(), req.Method, req.URL) - - if !noRespErr || !validStatus || !equality { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } - } - }) - - suite.Run("get unknown", func() { - id := strconv.Itoa(rand.Intn(256)) - req := httpc.R() - resp, err := req.Get("value/counter/testUnknown" + id) - noRespErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос для получения значения counter") - validStatus := suite.Assert().Equalf(http.StatusNotFound, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере '%s %s'", req.Method, req.URL) - - if !noRespErr || !validStatus { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - } - }) + e.Errorf("Неожиданная ошибка при поиске использования фреймворка по пути %q, %v", serverSourcePath, err) } diff --git a/cmd/metricstest/iteration4_test.go b/cmd/metricstest/iteration4_test.go index 28dd113..f8b6f50 100644 --- a/cmd/metricstest/iteration4_test.go +++ b/cmd/metricstest/iteration4_test.go @@ -1,425 +1,33 @@ package main import ( - "context" - "errors" - "math/rand" - "net/http" - "os" - "strconv" - "syscall" + "fmt" + "testing" "time" - - "github.com/go-resty/resty/v2" - "github.com/stretchr/testify/suite" - - "github.com/Yandex-Practicum/go-autotests/internal/fork" ) -type Iteration4Suite struct { - suite.Suite - - serverAddress string - serverPort string - serverProcess *fork.BackgroundProcess - serverArgs []string - agentProcess *fork.BackgroundProcess - agentArgs []string - // knownPgLibraries []string - - rnd *rand.Rand - envs []string -} - -func (suite *Iteration4Suite) SetupSuite() { - // check required flags - suite.Require().NotEmpty(flagTargetSourcePath, "-source-path non-empty flag required") - suite.Require().NotEmpty(flagServerBinaryPath, "-binary-path non-empty flag required") - suite.Require().NotEmpty(flagAgentBinaryPath, "-agent-binary-path non-empty flag required") - suite.Require().NotEmpty(flagServerPort, "-server-port non-empty flag required") - suite.Require().NotEmpty(flagFileStoragePath, "-file-storage-path non-empty flag required") - suite.Require().NotEmpty(flagDatabaseDSN, "-database-dsn non-empty flag required") - - suite.rnd = rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) - // suite.knownPgLibraries = []string{ - // "database/sql", - // "github.com/jackc/pgx", - // "github.com/lib/pq", - // } - suite.serverAddress = "http://localhost:" + flagServerPort - suite.serverPort = flagServerPort - - suite.envs = append(os.Environ(), []string{ - "STORE_INTERVAL=1s", - "RESTORE=true", - "DATABASE_DSN=" + flagDatabaseDSN, - }...) - - suite.agentArgs = []string{ - "-a=localhost:" + flagServerPort, - "-r=2s", - "-p=1s", - } - suite.serverArgs = []string{ - "-a=localhost:" + flagServerPort, - // "-s=5s", - "-r=false", - "-i=5m", - "-f=" + flagFileStoragePath, - } - - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - suite.agentUp(ctx, suite.envs, suite.agentArgs, flagServerPort) - suite.serverUp(ctx, suite.envs, suite.serverArgs, flagServerPort) -} - -func (suite *Iteration4Suite) serverUp(ctx context.Context, envs, args []string, port string) { - p := fork.NewBackgroundProcess(context.Background(), flagServerBinaryPath, - fork.WithEnv(envs...), - fork.WithArgs(args...), - ) - - err := p.Start(ctx) - if err != nil { - suite.T().Errorf("Невозможно запустить процесс командой %q: %s. Переменные окружения: %+v, флаги командной строки: %+v", p, err, envs, args) - return - } - - err = p.WaitPort(ctx, "tcp", port) - if err != nil { - suite.T().Errorf("Не удалось дождаться пока порт %s станет доступен для запроса: %s", port, err) - return - } - suite.serverProcess = p -} - -func (suite *Iteration4Suite) agentUp(ctx context.Context, envs, args []string, port string) { - p := fork.NewBackgroundProcess(context.Background(), flagAgentBinaryPath, - fork.WithEnv(envs...), - fork.WithArgs(args...), - ) - - err := p.Start(ctx) - if err != nil { - suite.T().Errorf("Невозможно запустить процесс командой %q: %s. Переменные окружения: %+v, флаги командной строки: %+v", p, err, envs, args) - return - } - - err = p.ListenPort(ctx, "tcp", port) - if err != nil { - suite.T().Errorf("Не удалось дождаться пока на порт %s начнут поступать данные: %s", port, err) - return - } - suite.agentProcess = p -} - -func (suite *Iteration4Suite) TearDownSuite() { - suite.agentShutdown() - suite.serverShutdown() -} - -func (suite *Iteration4Suite) serverShutdown() { - if suite.serverProcess == nil { - return - } - - exitCode, err := suite.serverProcess.Stop(syscall.SIGINT, syscall.SIGKILL) - if err != nil { - if errors.Is(err, os.ErrProcessDone) { - return - } - suite.T().Logf("Не удалось остановить процесс с помощью сигнала ОС: %s", err) - return - } - - if exitCode > 0 { - suite.T().Logf("Процесс завершился с не нулевым статусом %d", exitCode) - } - - // try to read stdout/stderr - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - defer cancel() - - out := suite.serverProcess.Stderr(ctx) - if len(out) > 0 { - suite.T().Logf("Получен STDERR лог процесса:\n\n%s", string(out)) - } - out = suite.serverProcess.Stdout(ctx) - if len(out) > 0 { - suite.T().Logf("Получен STDOUT лог процесса:\n\n%s", string(out)) - } -} - -func (suite *Iteration4Suite) agentShutdown() { - if suite.agentProcess == nil { - return - } - - exitCode, err := suite.agentProcess.Stop(syscall.SIGINT, syscall.SIGKILL) - if err != nil { - if errors.Is(err, os.ErrProcessDone) { - return - } - suite.T().Logf("Не удалось остановить процесс с помощью сигнала ОС: %s", err) - return - } - - if exitCode > 0 { - suite.T().Logf("Процесс завершился с не нулевым статусом %d", exitCode) - } - - // try to read stdout/stderr - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - defer cancel() - - out := suite.agentProcess.Stderr(ctx) - if len(out) > 0 { - suite.T().Logf("Получен STDERR лог процесса:\n\n%s", string(out)) - } - out = suite.agentProcess.Stdout(ctx) - if len(out) > 0 { - suite.T().Logf("Получен STDOUT лог процесса:\n\n%s", string(out)) - } -} - -func (suite *Iteration4Suite) TestCounterHandlers() { - // create HTTP client without redirects support - errRedirectBlocked := errors.New("HTTP redirect blocked") - redirPolicy := resty.RedirectPolicyFunc(func(_ *http.Request, _ []*http.Request) error { - return errRedirectBlocked +func TestIteration4(t *testing.T) { + _ = New(t) + t.Run("server", func(t *testing.T) { + testServerIncrement1(t, StartServerWithArgs) + testServerIncrement3(t, StartServerWithArgs) }) - httpc := resty.New(). - SetHostURL(suite.serverAddress). - SetRedirectPolicy(redirPolicy) - - id := "GetSet" + strconv.Itoa(suite.rnd.Intn(256)) - var storage int64 - - suite.Run("update", func() { - value1, value2 := int64(suite.rnd.Int31()), int64(suite.rnd.Int31()) - req := httpc.R(). - SetHeader("Content-Type", "application/json") - - // Вдруг на сервере уже есть значение, на всякий случай запросим. - var result Metrics - resp, err := req. - SetBody(&Metrics{ - ID: id, - MType: "counter", - }). - SetResult(&result). - Post("value/") - - dumpErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с получением значения counter") - var value0 int64 - switch resp.StatusCode() { - case http.StatusOK: - dumpErr = dumpErr && suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере %q: %q ", req.Method, req.URL) - dumpErr = dumpErr && suite.Assert().Containsf(resp.Header().Get("Content-Type"), "application/json", - "Заголовок ответа Content-Type содержит несоответствующее значение") - dumpErr = dumpErr && suite.NotNil(result.Delta, - "Получено не инициализированное значение Delta '%q %s'", req.Method, req.URL) - value0 = *result.Delta - case http.StatusNotFound: - default: - dumpErr = false - suite.T().Fatalf("Несоответствие статус кода %d ответа ожидаемому http.StatusNotFound или http.StatusOK в хендлере %q: %q", resp.StatusCode(), req.Method, req.URL) - return - } - - resp, err = req. - SetBody(&Metrics{ - ID: id, - MType: "counter", - Delta: &value1, - }). - Post("update/") - dumpErr = dumpErr && suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением counter") - dumpErr = dumpErr && suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере %q: %q ", req.Method, req.URL) - - resp, err = req. - SetBody(&Metrics{ - ID: id, - MType: "counter", - Delta: &value2, - }). - Post("update/") - dumpErr = dumpErr && suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением counter") - dumpErr = dumpErr && suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере %q: %q ", req.Method, req.URL) - - resp, err = req. - SetBody(&Metrics{ - ID: id, - MType: "counter", - }). - SetResult(&result). - Post("value/") - - dumpErr = dumpErr && suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с получением значения counter") - dumpErr = dumpErr && suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере %q: %q ", req.Method, req.URL) - dumpErr = dumpErr && suite.Assert().Containsf(resp.Header().Get("Content-Type"), "application/json", - "Заголовок ответа Content-Type содержит несоответствующее значение") - dumpErr = dumpErr && suite.NotNil(result.Delta, - "Несоответствие отправленного значения counter (%d) полученному от сервера (nil), '%q %s'", value0+value1+value2, req.Method, req.URL) - dumpErr = dumpErr && suite.Assert().Equalf(value0+value1+value2, *result.Delta, - "Несоответствие отправленного значения counter (%d) полученному от сервера (%d), '%q %s'", value0+value1+value2, *result.Delta, req.Method, req.URL) - if !dumpErr { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - dump = dumpResponse(resp.RawResponse, true) - suite.T().Logf("Оригинальный ответ:\n\n%s", dump) - } - - storage = value0 + value1 + value2 - }) - - suite.Run("restart server", func() { - time.Sleep(5 * time.Second) // relax time - suite.serverShutdown() - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - suite.serverUp(ctx, suite.envs, suite.serverArgs, suite.serverPort) - }) - - suite.Run("get", func() { - req := httpc.R(). - SetHeader("Content-Type", "application/json") - - // Вдруг на сервере уже есть значение, на всякий случай запросим. - var result Metrics - resp, err := req. - SetBody(&Metrics{ - ID: id, - MType: "counter", - }). - SetResult(&result). - Post("value/") - - dumpErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с получением значения counter") - - dumpErr = dumpErr && suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере %q: %q ", req.Method, req.URL) - dumpErr = dumpErr && suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере %q: %q ", req.Method, req.URL) - dumpErr = dumpErr && suite.Assert().Containsf(resp.Header().Get("Content-Type"), "application/json", - "Заголовок ответа Content-Type содержит несоответствующее значение") - dumpErr = dumpErr && suite.NotNil(result.Delta, - "Получено не инициализированное значение Delta '%q %s'", req.Method, req.URL) - dumpErr = dumpErr && suite.NotNil(result.Delta, - "Несоответствие ожидаемого значения counter (%d) полученному от сервера (nil), '%q %s'", storage, req.Method, req.URL) - dumpErr = dumpErr && suite.Assert().Equalf(storage, *result.Delta, - "Несоответствие ожидаемого значения counter (%d) полученному от сервера (%d), '%q %s'", storage, *result.Delta, req.Method, req.URL) - - if !dumpErr { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - dump = dumpResponse(resp.RawResponse, true) - suite.T().Logf("Оригинальный ответ:\n\n%s", dump) - } + t.Run("agent", func(t *testing.T) { + e := New(t) + testAgentIncrement2(e, StartAgentWithArgs) }) } -func (suite *Iteration4Suite) TestGaugeHandlers() { - errRedirectBlocked := errors.New("HTTP redirect blocked") - redirPolicy := resty.RedirectPolicyFunc(func(_ *http.Request, _ []*http.Request) error { - return errRedirectBlocked - }) - httpc := resty.New(). - SetHostURL(suite.serverAddress). - SetRedirectPolicy(redirPolicy) - - id := "GetSet" + strconv.Itoa(suite.rnd.Intn(256)) - var storage float64 - - suite.Run("update", func() { - value := suite.rnd.Float64() * 1e6 - req := httpc.R(). - SetHeader("Content-Type", "application/json") - - resp, err := req. - SetBody(&Metrics{ - ID: id, - MType: "gauge", - Value: &value, - }). - Post("update/") - dumpErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с обновлением gauge") - dumpErr = dumpErr && suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере %q: %q ", req.Method, req.URL) - - var result Metrics - resp, err = req. - SetBody(&Metrics{ - ID: id, - MType: "gauge", - }). - SetResult(&result). - Post("value/") - - dumpErr = dumpErr && suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с получением значения gauge") - dumpErr = dumpErr && suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере %q: %q ", req.Method, req.URL) - dumpErr = dumpErr && suite.Assert().Containsf(resp.Header().Get("Content-Type"), "application/json", - "Заголовок ответа Content-Type содержит несоответствующее значение") - dumpErr = dumpErr && suite.Assert().NotEqualf(nil, result.Value, - "Несоответствие отправленного значения gauge (%f) полученному от сервера (nil), '%q %s'", value, req.Method, req.URL) - dumpErr = dumpErr && suite.Assert().Equalf(value, *result.Value, - "Несоответствие отправленного значения gauge (%f) полученному от сервера (%f), '%q %s'", value, *result.Value, req.Method, req.URL) - - if !dumpErr { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - dump = dumpResponse(resp.RawResponse, true) - suite.T().Logf("Оригинальный ответ:\n\n%s", dump) - } - if result.Value != nil { - storage = *result.Value - } - }) - - suite.Run("restart server", func() { - time.Sleep(5 * time.Second) // relax time - suite.serverShutdown() - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - suite.serverUp(ctx, suite.envs, suite.serverArgs, suite.serverPort) - }) - - suite.Run("get", func() { - req := httpc.R(). - SetHeader("Content-Type", "application/json") - - // Вдруг на сервере уже есть значение, на всякий случай запросим. - var result Metrics - resp, err := req. - SetBody(&Metrics{ - ID: id, - MType: "gauge", - }). - SetResult(&result). - Post("value/") - - dumpErr := suite.Assert().NoError(err, "Ошибка при попытке сделать запрос с получением значения gauge") - dumpErr = dumpErr && suite.Assert().Equalf(http.StatusOK, resp.StatusCode(), - "Несоответствие статус кода ответа ожидаемому в хендлере %q: %q ", req.Method, req.URL) - dumpErr = dumpErr && suite.Assert().Containsf(resp.Header().Get("Content-Type"), "application/json", - "Заголовок ответа Content-Type содержит несоответствующее значение") - dumpErr = dumpErr && suite.Assert().NotEqualf(nil, result.Value, - "Несоответствие ожидаемого значения gauge (%f) полученному от сервера (nil), '%q %s'", storage, req.Method, req.URL) - dumpErr = dumpErr && suite.Assert().Equalf(storage, *result.Value, - "Несоответствие ожидаемого значения gauge (%f) полученному от сервера (%f), '%q %s'", storage, *result.Value, req.Method, req.URL) +func StartServerWithArgs(e *Env) string { + serverArg := "-a=" + ServerAddress(e) + StartProcessWhichListenPort(e, ServerHost(e), ServerPort(e), "server", ServerFilePath(e), serverArg) + return "http://" + ServerAddress(e) +} - if !dumpErr { - dump := dumpRequest(req.RawRequest, true) - suite.T().Logf("Оригинальный запрос:\n\n%s", dump) - dump = dumpResponse(resp.RawResponse, true) - suite.T().Logf("Оригинальный ответ:\n\n%s", dump) - } - }) +func StartAgentWithArgs(e *Env) { + serverArg := "-a=" + ServerAddress(e) + pollIntervalArg := fmt.Sprintf("-p=%v", int(AgentPollInterval(e)/time.Second)) + reportIntervalArg := fmt.Sprintf("-r=%v", int(AgentReportInterval(e)/time.Second)) + StartProcess(e, "agent", AgentFilePath(e), serverArg, pollIntervalArg, reportIntervalArg) } diff --git a/cmd/metricstest/main_test.go b/cmd/metricstest/main_test.go index d79ade2..64975d8 100644 --- a/cmd/metricstest/main_test.go +++ b/cmd/metricstest/main_test.go @@ -3,38 +3,22 @@ package main //go:generate go test -c -o=../../bin/metricstest import ( + "flag" "os" "testing" + "github.com/gin-gonic/gin" + "github.com/rekby/fixenv" "github.com/stretchr/testify/suite" ) func TestMain(m *testing.M) { - os.Exit(m.Run()) -} - -func TestIteration1(t *testing.T) { - suite.Run(t, new(Iteration1Suite)) -} - -func TestIteration2A(t *testing.T) { - suite.Run(t, new(Iteration2ASuite)) -} + flag.Parse() -func TestIteration2B(t *testing.T) { - suite.Run(t, new(Iteration2BSuite)) -} - -func TestIteration3A(t *testing.T) { - suite.Run(t, new(Iteration3ASuite)) -} + gin.SetMode(gin.ReleaseMode) -func TestIteration3B(t *testing.T) { - suite.Run(t, new(Iteration3BSuite)) -} - -func TestIteration4(t *testing.T) { - suite.Run(t, new(Iteration4Suite)) + fixenv.CreateMainTestEnv(nil) + os.Exit(m.Run()) } func TestIteration5(t *testing.T) { diff --git a/go.mod b/go.mod index 1f21f8f..1f1080a 100644 --- a/go.mod +++ b/go.mod @@ -8,27 +8,50 @@ require ( github.com/google/pprof v0.0.0-20220829040838-70bd9ae97f40 github.com/jackc/pgx v3.6.2+incompatible github.com/jingyugao/rowserrcheck v1.1.1 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 golang.org/x/tools v0.1.12 honnef.co/go/tools v0.3.3 ) +require ( + github.com/bytedance/sonic v1.8.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.11.2 // indirect + github.com/goccy/go-json v0.10.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.0.9 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.9 // indirect + golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + google.golang.org/protobuf v1.28.1 // indirect +) + require ( github.com/BurntSushi/toml v0.4.1 // indirect github.com/cockroachdb/apd v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gin-gonic/gin v1.9.0 github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 // indirect github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect github.com/lib/pq v1.10.7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rekby/fixenv v0.3.2 github.com/shopspring/decimal v1.3.1 // indirect golang.org/x/crypto v0.6.0 // indirect golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.7.0 // indirect + golang.org/x/net v0.7.0 golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index ab858e3..36ed824 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,35 @@ github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= +github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 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/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= +github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofrs/uuid v4.3.0+incompatible h1:CaSVZxm5B+7o45rtab4jC2G37WGYX1zQfuU2i6DSvnc= github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20220829040838-70bd9ae97f40 h1:ykKxL12NZd3JmWZnyqarJGsF73M9Xhtrik/FEtEeFRE= github.com/google/pprof v0.0.0-20220829040838-70bd9ae97f40/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= @@ -19,22 +40,50 @@ github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yI github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rekby/fixenv v0.3.2 h1:6AOdQ9Boaa/lOQJTY8GDmQRIhg3S3SD0mIEPkuDSkoQ= +github.com/rekby/fixenv v0.3.2/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 h1:kl4KhGNsJIbDHS9/4U9yQo1UcPQM0kOMJHn29EoH/Ro= github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= +github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 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.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= @@ -61,6 +110,7 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -78,7 +128,11 @@ golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -86,3 +140,4 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA= honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=