diff --git a/main.go b/main.go index 400ab88..37aa543 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "errors" "flag" "log" + "net/url" "os" "strconv" "strings" @@ -50,13 +51,41 @@ func main() { storages := initStorages(cfg) + testHandler := runner.NewConsoleHandler() fixturesLoader := initLoaders(storages, cfg) - runnerInstance := initRunner(cfg, fixturesLoader) + proxyURL, err := proxyURLFromEnv() + if err != nil { + log.Fatal(err) + } - addCheckers(runnerInstance, storages.db) + testsRunner := initRunner(cfg, fixturesLoader, testHandler, proxyURL) - run(runnerInstance, cfg) + consoleOutput := console_colored.NewOutput(cfg.Verbose) + testsRunner.AddOutput(consoleOutput) + + addCheckers(testsRunner, storages.db) + + var allureOutput *allure_report.AllureReportOutput + if cfg.Allure { + allureOutput = allure_report.NewOutput("Gonkey", "./allure-results") + testsRunner.AddOutput(allureOutput) + } + + err = testsRunner.Run() + if err != nil { + log.Fatal(err) + } + + if allureOutput != nil { + allureOutput.Finalize() + } + + summary := testHandler.Summary() + consoleOutput.ShowSummary(summary) + if !summary.Success { + os.Exit(1) + } } func initStorages(cfg config) storages { @@ -123,40 +152,21 @@ func addCheckers(r *runner.Runner, db *sql.DB) { } } -func run(r *runner.Runner, cfg config) { - consoleOutput := console_colored.NewOutput(cfg.Verbose) - r.AddOutput(consoleOutput) - - var allureOutput *allure_report.AllureReportOutput - if cfg.Allure { - allureOutput = allure_report.NewOutput("Gonkey", "./allure-results") - r.AddOutput(allureOutput) - } - - summary, err := r.Run() - if err != nil { - log.Fatal(err) - } - - consoleOutput.ShowSummary(summary) - - if allureOutput != nil { - allureOutput.Finalize() - } - - if !summary.Success { - os.Exit(1) - } -} - -func initRunner(cfg config, fixturesLoader fixtures.Loader) *runner.Runner { +func initRunner( + cfg config, + fixturesLoader fixtures.Loader, + handler *runner.ConsoleHandler, + proxyURL *url.URL, +) *runner.Runner { return runner.New( &runner.Config{ Host: cfg.Host, FixturesLoader: fixturesLoader, Variables: variables.New(), + HttpProxyURL: proxyURL, }, yaml_file.NewLoader(cfg.TestsLocation), + handler.HandleTest, ) } @@ -227,3 +237,14 @@ func parseAerospikeHost(dsn string) (address string, port int, namespace string) return } + +func proxyURLFromEnv() (*url.URL, error) { + if os.Getenv("HTTP_PROXY") != "" { + httpUrl, err := url.Parse(os.Getenv("HTTP_PROXY")) + if err != nil { + return nil, err + } + return httpUrl, nil + } + return nil, nil +} diff --git a/output/testing/testing.go b/output/testing/testing.go index 0f61089..7379f51 100644 --- a/output/testing/testing.go +++ b/output/testing/testing.go @@ -2,20 +2,16 @@ package testing import ( "bytes" - "testing" + "fmt" "text/template" "github.com/lamoda/gonkey/models" ) -type TestingOutput struct { - testing *testing.T -} +type TestingOutput struct{} -func NewOutput(t *testing.T) *TestingOutput { - return &TestingOutput{ - testing: t, - } +func NewOutput() *TestingOutput { + return &TestingOutput{} } func (o *TestingOutput) Process(t models.TestInterface, result *models.Result) error { @@ -24,7 +20,7 @@ func (o *TestingOutput) Process(t models.TestInterface, result *models.Result) e if err != nil { return err } - o.testing.Error(text) + fmt.Println(text) } return nil } diff --git a/runner/console_handler.go b/runner/console_handler.go new file mode 100644 index 0000000..880b393 --- /dev/null +++ b/runner/console_handler.go @@ -0,0 +1,47 @@ +package runner + +import ( + "errors" + + "github.com/lamoda/gonkey/models" +) + +type ConsoleHandler struct { + totalTests int + failedTests int + skippedTests int + brokenTests int +} + +func NewConsoleHandler() *ConsoleHandler { + return &ConsoleHandler{} +} + +func (h *ConsoleHandler) HandleTest(test models.TestInterface, executeTest testExecutor) error { + testResult, err := executeTest(test) + switch { + case err != nil && errors.Is(err, errTestSkipped): + h.skippedTests++ + case err != nil && errors.Is(err, errTestBroken): + h.brokenTests++ + case err != nil: + return err + } + + h.totalTests++ + if !testResult.Passed() { + h.failedTests++ + } + + return nil +} + +func (h *ConsoleHandler) Summary() *models.Summary { + return &models.Summary{ + Success: h.failedTests == 0, + Skipped: h.skippedTests, + Broken: h.brokenTests, + Failed: h.failedTests, + Total: h.totalTests, + } +} diff --git a/runner/console_handler_test.go b/runner/console_handler_test.go new file mode 100644 index 0000000..5515785 --- /dev/null +++ b/runner/console_handler_test.go @@ -0,0 +1,97 @@ +package runner + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/lamoda/gonkey/models" +) + +func TestConsoleHandler_HandleTest_Summary(t *testing.T) { + type result struct { + result *models.Result + err error + } + tests := []struct { + name string + testExecResults []result + wantErr bool + expectedSummary *models.Summary + }{ + { + name: "1 successful test", + testExecResults: []result{{result: &models.Result{Errors: nil}}}, + expectedSummary: &models.Summary{ + Success: true, + Failed: 0, + Total: 1, + }, + }, + { + name: "1 successful test, 1 broken test", + testExecResults: []result{ + {result: &models.Result{}, err: nil}, + {result: &models.Result{}, err: errTestBroken}, + }, + expectedSummary: &models.Summary{ + Success: true, + Failed: 0, + Broken: 1, + Total: 2, + }, + }, + { + name: "1 successful test, 1 skipped test", + testExecResults: []result{ + {result: &models.Result{}, err: nil}, + {result: &models.Result{}, err: errTestSkipped}, + }, + expectedSummary: &models.Summary{ + Success: true, + Skipped: 1, + Total: 2, + }, + }, + { + name: "1 successful test, 1 failed test", + testExecResults: []result{ + {result: &models.Result{}, err: nil}, + {result: &models.Result{Errors: []error{errors.New("some err")}}, err: nil}, + }, + expectedSummary: &models.Summary{ + Success: false, + Failed: 1, + Total: 2, + }, + }, + { + name: "test with unexpected error", + testExecResults: []result{ + {result: &models.Result{}, err: errors.New("unexpected error")}, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := NewConsoleHandler() + + for _, execResult := range tt.testExecResults { + executor := func(models.TestInterface) (*models.Result, error) { + return execResult.result, execResult.err + } + err := h.HandleTest(nil, executor) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + } + + require.Equal(t, tt.expectedSummary, h.Summary()) + }) + } +} diff --git a/runner/request.go b/runner/request.go index cb6b7f0..50e8d32 100644 --- a/runner/request.go +++ b/runner/request.go @@ -16,16 +16,10 @@ import ( "github.com/lamoda/gonkey/models" ) -func newClient() (*http.Client, error) { +func newClient(proxyURL *url.URL) (*http.Client, error) { transport := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - if os.Getenv("HTTP_PROXY") != "" { - proxyUrl, err := url.Parse(os.Getenv("HTTP_PROXY")) - if err != nil { - return nil, err - } - transport.Proxy = http.ProxyURL(proxyUrl) + Proxy: http.ProxyURL(proxyURL), } return &http.Client{ diff --git a/runner/runner.go b/runner/runner.go index 757762f..434218f 100644 --- a/runner/runner.go +++ b/runner/runner.go @@ -3,8 +3,9 @@ package runner import ( "errors" "fmt" - "io/ioutil" + "io" "net/http" + "net/url" "strings" "time" @@ -24,20 +25,33 @@ type Config struct { Mocks *mocks.Mocks MocksLoader *mocks.Loader Variables *variables.Variables + HttpProxyURL *url.URL } +type testExecutor func(models.TestInterface) (*models.Result, error) +type testHandler func(models.TestInterface, testExecutor) error + type Runner struct { - loader testloader.LoaderInterface - output []output.OutputInterface - checkers []checker.CheckerInterface + loader testloader.LoaderInterface + testExecutionHandler testHandler + output []output.OutputInterface + checkers []checker.CheckerInterface + client *http.Client config *Config } -func New(config *Config, loader testloader.LoaderInterface) *Runner { +func New(config *Config, loader testloader.LoaderInterface, handler testHandler) *Runner { + client, err := newClient(config.HttpProxyURL) + if err != nil { + panic(err) + } + return &Runner{ - config: config, - loader: loader, + config: config, + loader: loader, + testExecutionHandler: handler, + client: client, } } @@ -49,83 +63,47 @@ func (r *Runner) AddCheckers(c ...checker.CheckerInterface) { r.checkers = append(r.checkers, c...) } -func (r *Runner) Run() (*models.Summary, error) { - if r.loader == nil { - s := &models.Summary{ - Success: true, - Failed: 0, - Total: 0, - } - return s, nil - } - - loader, err := r.loader.Load() +func (r *Runner) Run() error { + tests, err := r.loader.Load() if err != nil { - return nil, err - } - - tests := []models.TestInterface{} - hasFocused := false - for test := range loader { - tests = append(tests, test) - if test.GetStatus() == "focus" { - hasFocused = true - } - } - - client, err := newClient() - if err != nil { - return nil, err + return err } - totalTests := 0 - failedTests := 0 - skippedTests := 0 - brokenTests := 0 - - for _, v := range tests { + hasFocused := checkHasFocused(tests) + for _, t := range tests { + test := t if hasFocused { - switch v.GetStatus() { + switch test.GetStatus() { case "focus": - v.SetStatus("") + test.SetStatus("") case "broken": // do nothing default: - v.SetStatus("skipped") + test.SetStatus("skipped") } } - testResult, err := r.executeTest(v, client) - switch { - case err != nil && errors.Is(err, errTestSkipped): - skippedTests++ - case err != nil && errors.Is(err, errTestBroken): - brokenTests++ - case err != nil: - // todo: populate error with test name. Currently it is not possible here to get test name. - return nil, err - } - - totalTests++ - if len(testResult.Errors) > 0 { - failedTests++ - } - for _, o := range r.output { - if err := o.Process(v, testResult); err != nil { + testExecutor := func(testInterface models.TestInterface) (*models.Result, error) { + testResult, err := r.executeTest(test) + if err != nil { return nil, err } + + for _, o := range r.output { + if err := o.Process(test, testResult); err != nil { + return nil, err + } + } + return testResult, nil + } + err := r.testExecutionHandler(test, testExecutor) + if err != nil { + return fmt.Errorf("test %s error: %s", test.GetName(), err) } - } - s := &models.Summary{ - Success: failedTests == 0, - Skipped: skippedTests, - Broken: brokenTests, - Failed: failedTests, - Total: totalTests, } - return s, nil + return nil } var ( @@ -133,7 +111,7 @@ var ( errTestBroken = errors.New("test was broken") ) -func (r *Runner) executeTest(v models.TestInterface, client *http.Client) (*models.Result, error) { +func (r *Runner) executeTest(v models.TestInterface) (*models.Result, error) { if v.GetStatus() != "" { if v.GetStatus() == "broken" { @@ -188,12 +166,12 @@ func (r *Runner) executeTest(v models.TestInterface, client *http.Client) (*mode return nil, err } - resp, err := client.Do(req) + resp, err := r.client.Do(req) if err != nil { return nil, err } - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) _ = resp.Body.Close() @@ -267,3 +245,12 @@ func (r *Runner) setVariablesFromResponse(t models.TestInterface, contentType, b return nil } + +func checkHasFocused(tests []models.TestInterface) bool { + for _, test := range tests { + if test.GetStatus() == "focus" { + return true + } + } + return false +} diff --git a/runner/runner_testing.go b/runner/runner_testing.go index e895988..4d7150c 100644 --- a/runner/runner_testing.go +++ b/runner/runner_testing.go @@ -2,7 +2,9 @@ package runner import ( "database/sql" + "errors" "net/http/httptest" + "net/url" "os" "testing" @@ -15,6 +17,7 @@ import ( "github.com/lamoda/gonkey/checker/response_header" "github.com/lamoda/gonkey/fixtures" "github.com/lamoda/gonkey/mocks" + "github.com/lamoda/gonkey/models" "github.com/lamoda/gonkey/output" "github.com/lamoda/gonkey/output/allure_report" testingOutput "github.com/lamoda/gonkey/output/testing" @@ -71,12 +74,21 @@ func RunWithTesting(t *testing.T, params *RunWithTestingParams) { }) } - runner := initRunner(params, mocksLoader, fixturesLoader) + var proxyURL *url.URL + if os.Getenv("HTTP_PROXY") != "" { + httpUrl, err := url.Parse(os.Getenv("HTTP_PROXY")) + if err != nil { + t.Fatal(err) + } + proxyURL = httpUrl + } + + runner := initRunner(t, params, mocksLoader, fixturesLoader, proxyURL) if params.OutputFunc != nil { runner.AddOutput(params.OutputFunc) } else { - runner.AddOutput(testingOutput.NewOutput(t)) + runner.AddOutput(testingOutput.NewOutput()) } if os.Getenv("GONKEY_ALLURE_DIR") != "" { @@ -87,16 +99,17 @@ func RunWithTesting(t *testing.T, params *RunWithTestingParams) { addCheckers(runner, params) - _, err := runner.Run() + err := runner.Run() if err != nil { t.Fatal(err) } } -func initRunner(params *RunWithTestingParams, mocksLoader *mocks.Loader, fixturesLoader fixtures.Loader) *Runner { +func initRunner(t *testing.T, params *RunWithTestingParams, mocksLoader *mocks.Loader, fixturesLoader fixtures.Loader, proxyURL *url.URL) *Runner { yamlLoader := yaml_file.NewLoader(params.TestsDir) yamlLoader.SetFileFilter(os.Getenv("GONKEY_FILE_FILTER")) + handler := testingHandler{t} runner := New( &Config{ Host: params.Server.URL, @@ -104,8 +117,10 @@ func initRunner(params *RunWithTestingParams, mocksLoader *mocks.Loader, fixture MocksLoader: mocksLoader, FixturesLoader: fixturesLoader, Variables: variables.New(), + HttpProxyURL: proxyURL, }, yamlLoader, + handler.HandleTest, ) return runner } @@ -120,3 +135,27 @@ func addCheckers(runner *Runner, params *RunWithTestingParams) { runner.AddCheckers(params.Checkers...) } + +type testingHandler struct { + t *testing.T +} + +func (h testingHandler) HandleTest(test models.TestInterface, executeTest testExecutor) error { + var returnErr error + h.t.Run(test.GetName(), func(t *testing.T) { + result, err := executeTest(test) + if err != nil { + returnErr = err + t.Fatal(err) + } + + if errors.Is(err, errTestSkipped) || errors.Is(err, errTestBroken) { + t.Skip() + } + + if !result.Passed() { + t.Fail() + } + }) + return returnErr +} diff --git a/testloader/loader.go b/testloader/loader.go index 3740b3f..746ab4c 100644 --- a/testloader/loader.go +++ b/testloader/loader.go @@ -5,5 +5,5 @@ import ( ) type LoaderInterface interface { - Load() (chan models.TestInterface, error) + Load() ([]models.TestInterface, error) } diff --git a/testloader/yaml_file/yaml_file.go b/testloader/yaml_file/yaml_file.go index 07a7718..9fd3bc2 100644 --- a/testloader/yaml_file/yaml_file.go +++ b/testloader/yaml_file/yaml_file.go @@ -19,19 +19,18 @@ func NewLoader(testsLocation string) *YamlFileLoader { } } -func (l *YamlFileLoader) Load() (chan models.TestInterface, error) { +func (l *YamlFileLoader) Load() ([]models.TestInterface, error) { fileTests, err := l.parseTestsWithCases(l.testsLocation) if err != nil { return nil, err } - ch := make(chan models.TestInterface) - go func() { - for i := range fileTests { - ch <- &fileTests[i] - } - close(ch) - }() - return ch, nil + + ret := make([]models.TestInterface, len(fileTests)) + for i, test := range fileTests { + test := test + ret[i] = &test + } + return ret, nil } func (l *YamlFileLoader) SetFileFilter(f string) {