On branch main
new file: main_dc/yalarba/api_yal/cmd/testrunner/README.md new file: main_dc/yalarba/api_yal/cmd/testrunner/main.go new file: main_dc/yalarba/api_yal/cmd/testrunner/runner.go deleted: main_dc/yalarba/api_yal/test/intergration/auth_integration_test.go deleted: main_dc/yalarba/api_yal/test/intergration/objects_integration_test.go deleted: main_dc/yalarba/api_yal/test/intergration/setup_test.go deleted: main_dc/yalarba/api_yal/test/setup_test.go new file: main_dc/yalarba/api_yal/tests/integration/account_test.go new file: main_dc/yalarba/api_yal/tests/integration/appeal_test.go new file: main_dc/yalarba/api_yal/tests/integration/auth_test.go new file: main_dc/yalarba/api_yal/tests/integration/comment_test.go new file: main_dc/yalarba/api_yal/tests/integration/feedback_test.go new file: main_dc/yalarba/api_yal/tests/integration/object_test.go new file: main_dc/yalarba/api_yal/tests/integration/rating_test.go new file: main_dc/yalarba/api_yal/tests/testutils/client.go new file: main_dc/yalarba/api_yal/tests/testutils/fixtures.go new file: main_dc/yalarba/api_yal/tests/testutils/setup.go write tests
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
# Создаем директорию для тестового раннера
|
||||
mkdir -p cmd/testrunner
|
||||
|
||||
# Сохраняем файл main.go в cmd/testrunner/
|
||||
|
||||
# Запуск в обычном режиме
|
||||
go run cmd/testrunner/main.go
|
||||
|
||||
# Запуск с параметрами
|
||||
go run cmd/testrunner/main.go -suite auth -verbose
|
||||
go run cmd/testrunner/main.go -suite all -coverage
|
||||
go run cmd/testrunner/main.go -suite all -parallel
|
||||
|
||||
# Интерактивный режим
|
||||
go run cmd/testrunner/main.go interactive
|
||||
|
||||
# Запуск бенчмарков
|
||||
go run cmd/testrunner/main.go bench
|
||||
|
||||
# Мониторинг сервера
|
||||
go run cmd/testrunner/main.go watch
|
||||
|
||||
# Сборка для текущей платформы
|
||||
go build -o testrunner.exe cmd/testrunner/main.go
|
||||
|
||||
# Запуск собранного бинарника
|
||||
./testrunner.exe -suite all -verbose
|
||||
|
||||
# Кроссплатформенная сборка
|
||||
# Windows
|
||||
GOOS=windows GOARCH=amd64 go build -o testrunner-windows.exe cmd/testrunner/main.go
|
||||
|
||||
# Linux
|
||||
GOOS=linux GOARCH=amd64 go build -o testrunner-linux cmd/testrunner/main.go
|
||||
|
||||
# macOS
|
||||
GOOS=darwin GOARCH=amd64 go build -o testrunner-macos cmd/testrunner/main.go
|
||||
@@ -0,0 +1,574 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Конфигурация
|
||||
type Config struct {
|
||||
ServerURL string
|
||||
ServerPort string
|
||||
TestTimeout time.Duration
|
||||
Verbose bool
|
||||
Parallel bool
|
||||
Coverage bool
|
||||
TestSuite string
|
||||
}
|
||||
|
||||
// Цвета для вывода
|
||||
type Colors struct {
|
||||
Reset string
|
||||
Red string
|
||||
Green string
|
||||
Yellow string
|
||||
Blue string
|
||||
Cyan string
|
||||
}
|
||||
|
||||
var colors Colors
|
||||
|
||||
func init() {
|
||||
// Определяем поддержку цветов
|
||||
if runtime.GOOS == "windows" {
|
||||
// На Windows включаем ANSI colors для новых версий
|
||||
colors = Colors{
|
||||
Reset: "\033[0m",
|
||||
Red: "\033[31m",
|
||||
Green: "\033[32m",
|
||||
Yellow: "\033[33m",
|
||||
Blue: "\033[34m",
|
||||
Cyan: "\033[36m",
|
||||
}
|
||||
} else {
|
||||
colors = Colors{
|
||||
Reset: "\033[0m",
|
||||
Red: "\033[31m",
|
||||
Green: "\033[32m",
|
||||
Yellow: "\033[33m",
|
||||
Blue: "\033[34m",
|
||||
Cyan: "\033[36m",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Тестовый набор
|
||||
type TestSuite struct {
|
||||
Name string
|
||||
Pattern string
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// Результат теста
|
||||
type TestResult struct {
|
||||
Suite string
|
||||
Passed bool
|
||||
Duration time.Duration
|
||||
Output string
|
||||
Error error
|
||||
Coverage float64
|
||||
}
|
||||
|
||||
func main() {
|
||||
config := parseFlags()
|
||||
|
||||
printBanner()
|
||||
|
||||
// Проверяем сервер
|
||||
if !checkServer(config) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Очищаем кэш тестов
|
||||
cleanTestCache()
|
||||
|
||||
// Запускаем тесты
|
||||
results := runTests(config)
|
||||
|
||||
// Выводим результаты
|
||||
printResults(results)
|
||||
|
||||
// Завершаем с соответствующим кодом
|
||||
if hasFailures(results) {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func parseFlags() *Config {
|
||||
config := &Config{}
|
||||
|
||||
flag.StringVar(&config.ServerURL, "server-url", "http://localhost:8088", "API server URL")
|
||||
flag.StringVar(&config.ServerPort, "server-port", "8088", "API server port")
|
||||
flag.DurationVar(&config.TestTimeout, "timeout", 30*time.Minute, "Test timeout")
|
||||
flag.BoolVar(&config.Verbose, "verbose", false, "Verbose output")
|
||||
flag.BoolVar(&config.Parallel, "parallel", false, "Run tests in parallel")
|
||||
flag.BoolVar(&config.Coverage, "coverage", false, "Generate coverage report")
|
||||
flag.StringVar(&config.TestSuite, "suite", "all", "Test suite to run (all, auth, account, object, feedback, comment, rating, appeal)")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func printBanner() {
|
||||
fmt.Printf("%s╔════════════════════════════════════════════════════════════════╗%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Printf("%s║ Go Test Runner for API ║%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Printf("%s║ Version 1.0.0 ║%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Printf("%s╚════════════════════════════════════════════════════════════════╝%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func checkServer(config *Config) bool {
|
||||
fmt.Printf("%s🔍 Checking server status...%s\n", colors.Yellow, colors.Reset)
|
||||
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
resp, err := client.Get(fmt.Sprintf("%s/health", config.ServerURL))
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("%s❌ Server is not running on %s%s\n", colors.Red, config.ServerURL, colors.Reset)
|
||||
fmt.Printf("%s💡 Please start the server first: go run cmd/main.go%s\n", colors.Yellow, colors.Reset)
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
fmt.Printf("%s✅ Server is running on %s%s\n\n", colors.Green, config.ServerURL, colors.Reset)
|
||||
return true
|
||||
}
|
||||
|
||||
fmt.Printf("%s❌ Server returned status: %d%s\n", colors.Red, resp.StatusCode, colors.Reset)
|
||||
return false
|
||||
}
|
||||
|
||||
func cleanTestCache() {
|
||||
fmt.Printf("%s🧹 Cleaning test cache...%s\n", colors.Yellow, colors.Reset)
|
||||
cmd := exec.Command("go", "clean", "-testcache")
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Printf("%s⚠️ Warning: failed to clean test cache: %v%s\n", colors.Yellow, err, colors.Reset)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func getTestSuites() []TestSuite {
|
||||
return []TestSuite{
|
||||
{Name: "All", Pattern: "", Timeout: 30 * time.Minute},
|
||||
{Name: "Auth", Pattern: "TestAuthFlow", Timeout: 5 * time.Minute},
|
||||
{Name: "Account", Pattern: "TestAccountEndpoints", Timeout: 5 * time.Minute},
|
||||
{Name: "Object", Pattern: "TestObjectEndpoints", Timeout: 5 * time.Minute},
|
||||
{Name: "Feedback", Pattern: "TestFeedbackEndpoints", Timeout: 5 * time.Minute},
|
||||
{Name: "Comment", Pattern: "TestCommentEndpoints", Timeout: 5 * time.Minute},
|
||||
{Name: "Rating", Pattern: "TestRatingEndpoints", Timeout: 5 * time.Minute},
|
||||
{Name: "Appeal", Pattern: "TestAppealEndpoints", Timeout: 5 * time.Minute},
|
||||
}
|
||||
}
|
||||
|
||||
func runTests(config *Config) []TestResult {
|
||||
suites := getTestSuites()
|
||||
var selectedSuites []TestSuite
|
||||
|
||||
if config.TestSuite == "all" {
|
||||
selectedSuites = suites
|
||||
} else {
|
||||
for _, suite := range suites {
|
||||
if strings.EqualFold(suite.Name, config.TestSuite) {
|
||||
selectedSuites = []TestSuite{suite}
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(selectedSuites) == 0 {
|
||||
fmt.Printf("%s❌ Unknown test suite: %s%s\n", colors.Red, config.TestSuite, colors.Reset)
|
||||
fmt.Printf("%s📚 Available suites: all, auth, account, object, feedback, comment, rating, appeal%s\n", colors.Yellow, colors.Reset)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
var results []TestResult
|
||||
|
||||
if config.Parallel && len(selectedSuites) > 1 {
|
||||
results = runTestsParallel(selectedSuites, config)
|
||||
} else {
|
||||
for _, suite := range selectedSuites {
|
||||
if suite.Name == "All" {
|
||||
result := runTestSuite(suite, config)
|
||||
results = append(results, result)
|
||||
} else {
|
||||
result := runTestSuite(suite, config)
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
func runTestsParallel(suites []TestSuite, config *Config) []TestResult {
|
||||
fmt.Printf("%s🚀 Running tests in parallel mode...%s\n\n", colors.Cyan, colors.Reset)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
results := make([]TestResult, len(suites))
|
||||
|
||||
// Пропускаем "All" suite в параллельном режиме
|
||||
var filteredSuites []TestSuite
|
||||
for _, suite := range suites {
|
||||
if suite.Name != "All" {
|
||||
filteredSuites = append(filteredSuites, suite)
|
||||
}
|
||||
}
|
||||
|
||||
for i, suite := range filteredSuites {
|
||||
wg.Add(1)
|
||||
go func(idx int, s TestSuite) {
|
||||
defer wg.Done()
|
||||
results[idx] = runTestSuite(s, config)
|
||||
}(i, suite)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
|
||||
func runTestSuite(suite TestSuite, config *Config) TestResult {
|
||||
startTime := time.Now()
|
||||
|
||||
fmt.Printf("%s📦 Running %s tests...%s\n", colors.Blue, suite.Name, colors.Reset)
|
||||
|
||||
cmd := exec.Command("go", "test")
|
||||
|
||||
if config.Verbose {
|
||||
cmd.Args = append(cmd.Args, "-v")
|
||||
}
|
||||
|
||||
cmd.Args = append(cmd.Args, "-timeout", suite.Timeout.String())
|
||||
|
||||
if suite.Pattern != "" {
|
||||
cmd.Args = append(cmd.Args, "-run", suite.Pattern)
|
||||
}
|
||||
|
||||
if config.Coverage && suite.Name == "All" {
|
||||
cmd.Args = append(cmd.Args, "-coverprofile=coverage.out")
|
||||
}
|
||||
|
||||
cmd.Args = append(cmd.Args, "./tests/integration/...")
|
||||
|
||||
// Захват вывода
|
||||
var stdout, stderr bytes.Buffer
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
// Запуск с таймаутом
|
||||
_, cancel := context.WithTimeout(context.Background(), suite.Timeout)
|
||||
defer cancel()
|
||||
|
||||
err := cmd.Run()
|
||||
duration := time.Since(startTime)
|
||||
|
||||
result := TestResult{
|
||||
Suite: suite.Name,
|
||||
Duration: duration,
|
||||
Output: stdout.String(),
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
result.Passed = false
|
||||
result.Error = err
|
||||
if stderr.Len() > 0 {
|
||||
result.Output += "\n" + stderr.String()
|
||||
}
|
||||
} else {
|
||||
result.Passed = true
|
||||
}
|
||||
|
||||
// Извлечение покрытия, если нужно
|
||||
if config.Coverage && suite.Name == "All" && result.Passed {
|
||||
result.Coverage = extractCoverage()
|
||||
}
|
||||
|
||||
// Вывод результата
|
||||
if result.Passed {
|
||||
fmt.Printf("%s✅ %s tests passed %s(%v)%s\n", colors.Green, suite.Name, colors.Green, duration, colors.Reset)
|
||||
} else {
|
||||
fmt.Printf("%s❌ %s tests failed %s(%v)%s\n", colors.Red, suite.Name, colors.Red, duration, colors.Reset)
|
||||
if config.Verbose {
|
||||
fmt.Println(result.Output)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func extractCoverage() float64 {
|
||||
// Проверяем наличие файла покрытия
|
||||
if _, err := os.Stat("coverage.out"); os.IsNotExist(err) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Запускаем go tool cover для получения процента покрытия
|
||||
cmd := exec.Command("go", "tool", "cover", "-func=coverage.out")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Парсим вывод для получения total
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "total:") {
|
||||
// Извлекаем процент
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 3 {
|
||||
percentage := strings.TrimSuffix(parts[2], "%")
|
||||
var coverage float64
|
||||
fmt.Sscanf(percentage, "%f", &coverage)
|
||||
return coverage
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func printResults(results []TestResult) {
|
||||
fmt.Println()
|
||||
fmt.Printf("%s╔════════════════════════════════════════════════════════════════╗%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Printf("%s║ Test Results ║%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Printf("%s╚════════════════════════════════════════════════════════════════╝%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Println()
|
||||
|
||||
// Таблица результатов
|
||||
fmt.Printf("%-15s %-10s %-15s %s\n", "Suite", "Status", "Duration", "Coverage")
|
||||
fmt.Println(strings.Repeat("-", 60))
|
||||
|
||||
var totalTests int
|
||||
var passedTests int
|
||||
var totalDuration time.Duration
|
||||
|
||||
for _, result := range results {
|
||||
status := fmt.Sprintf("%s✓ PASSED%s", colors.Green, colors.Reset)
|
||||
if !result.Passed {
|
||||
status = fmt.Sprintf("%s✗ FAILED%s", colors.Red, colors.Reset)
|
||||
}
|
||||
|
||||
coverage := "-"
|
||||
if result.Coverage > 0 {
|
||||
coverage = fmt.Sprintf("%.1f%%", result.Coverage)
|
||||
}
|
||||
|
||||
fmt.Printf("%-15s %-10s %-15v %s\n",
|
||||
result.Suite,
|
||||
status,
|
||||
result.Duration.Round(time.Millisecond),
|
||||
coverage)
|
||||
|
||||
totalTests++
|
||||
if result.Passed {
|
||||
passedTests++
|
||||
}
|
||||
totalDuration += result.Duration
|
||||
}
|
||||
|
||||
fmt.Println(strings.Repeat("-", 60))
|
||||
fmt.Printf("%-15s %d/%d passed\n", "TOTAL:", passedTests, totalTests)
|
||||
fmt.Printf("%-15s %v\n", "TIME:", totalDuration.Round(time.Millisecond))
|
||||
|
||||
// Генерация HTML отчета при покрытии
|
||||
if len(results) > 0 && results[0].Coverage > 0 {
|
||||
generateHTMLReport()
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func generateHTMLReport() {
|
||||
fmt.Printf("%s📊 Generating HTML coverage report...%s\n", colors.Yellow, colors.Reset)
|
||||
|
||||
cmd := exec.Command("go", "tool", "cover", "-html=coverage.out", "-o", "coverage.html")
|
||||
if err := cmd.Run(); err != nil {
|
||||
fmt.Printf("%s⚠️ Failed to generate HTML report: %v%s\n", colors.Yellow, err, colors.Reset)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s✅ Coverage report generated: coverage.html%s\n", colors.Green, colors.Reset)
|
||||
|
||||
// Открываем отчет в браузере
|
||||
var openCmd *exec.Cmd
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
openCmd = exec.Command("cmd", "/c", "start", "coverage.html")
|
||||
case "darwin":
|
||||
openCmd = exec.Command("open", "coverage.html")
|
||||
default:
|
||||
openCmd = exec.Command("xdg-open", "coverage.html")
|
||||
}
|
||||
|
||||
if err := openCmd.Run(); err != nil {
|
||||
fmt.Printf("%s⚠️ Could not open browser: %v%s\n", colors.Yellow, err, colors.Reset)
|
||||
}
|
||||
}
|
||||
|
||||
func hasFailures(results []TestResult) bool {
|
||||
for _, result := range results {
|
||||
if !result.Passed {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Интерактивный режим
|
||||
func interactiveMode() {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
for {
|
||||
printMenu()
|
||||
|
||||
fmt.Print("Enter your choice: ")
|
||||
input, _ := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
switch input {
|
||||
case "1":
|
||||
runWithConfig(&Config{TestSuite: "all", Verbose: true})
|
||||
case "2":
|
||||
runWithConfig(&Config{TestSuite: "auth", Verbose: true})
|
||||
case "3":
|
||||
runWithConfig(&Config{TestSuite: "account", Verbose: true})
|
||||
case "4":
|
||||
runWithConfig(&Config{TestSuite: "object", Verbose: true})
|
||||
case "5":
|
||||
runWithConfig(&Config{TestSuite: "feedback", Verbose: true})
|
||||
case "6":
|
||||
runWithConfig(&Config{TestSuite: "comment", Verbose: true})
|
||||
case "7":
|
||||
runWithConfig(&Config{TestSuite: "rating", Verbose: true})
|
||||
case "8":
|
||||
runWithConfig(&Config{TestSuite: "appeal", Verbose: true})
|
||||
case "9":
|
||||
runWithConfig(&Config{TestSuite: "all", Coverage: true, Verbose: false})
|
||||
case "10":
|
||||
runWithConfig(&Config{TestSuite: "all", Parallel: true, Verbose: false})
|
||||
case "0":
|
||||
fmt.Printf("%s👋 Goodbye!%s\n", colors.Yellow, colors.Reset)
|
||||
os.Exit(0)
|
||||
default:
|
||||
fmt.Printf("%s❌ Invalid choice! Please try again.%s\n", colors.Red, colors.Reset)
|
||||
}
|
||||
|
||||
fmt.Print("\nPress Enter to continue...")
|
||||
reader.ReadString('\n')
|
||||
}
|
||||
}
|
||||
|
||||
func printMenu() {
|
||||
fmt.Printf("\n%s╔════════════════════════════════════════════════════════════════╗%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Printf("%s║ Interactive Menu ║%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Printf("%s╚════════════════════════════════════════════════════════════════╝%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Println()
|
||||
fmt.Printf("%s1.%s Run all tests\n", colors.Green, colors.Reset)
|
||||
fmt.Printf("%s2.%s Run auth tests only\n", colors.Green, colors.Reset)
|
||||
fmt.Printf("%s3.%s Run account tests only\n", colors.Green, colors.Reset)
|
||||
fmt.Printf("%s4.%s Run object tests only\n", colors.Green, colors.Reset)
|
||||
fmt.Printf("%s5.%s Run feedback tests only\n", colors.Green, colors.Reset)
|
||||
fmt.Printf("%s6.%s Run comment tests only\n", colors.Green, colors.Reset)
|
||||
fmt.Printf("%s7.%s Run rating tests only\n", colors.Green, colors.Reset)
|
||||
fmt.Printf("%s8.%s Run appeal tests only\n", colors.Green, colors.Reset)
|
||||
fmt.Printf("%s9.%s Run all tests with coverage\n", colors.Green, colors.Reset)
|
||||
fmt.Printf("%s10.%s Run all tests in parallel\n", colors.Green, colors.Reset)
|
||||
fmt.Printf("%s0.%s Exit\n", colors.Red, colors.Reset)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func runWithConfig(config *Config) {
|
||||
config.ServerURL = "http://localhost:8088"
|
||||
config.TestTimeout = 30 * time.Minute
|
||||
|
||||
printBanner()
|
||||
|
||||
if !checkServer(config) {
|
||||
return
|
||||
}
|
||||
|
||||
cleanTestCache()
|
||||
results := runTests(config)
|
||||
printResults(results)
|
||||
}
|
||||
|
||||
// Функция для мониторинга сервера
|
||||
func watchServer(config *Config) {
|
||||
fmt.Printf("%s👁️ Starting server health monitor...%s\n", colors.Yellow, colors.Reset)
|
||||
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
if !checkServer(config) {
|
||||
fmt.Printf("%s⚠️ Server is down!%s\n", colors.Red, colors.Reset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Структура для бенчмарков
|
||||
type BenchmarkResult struct {
|
||||
Name string
|
||||
Ops int64
|
||||
NsPerOp time.Duration
|
||||
Allocs int64
|
||||
Bytes int64
|
||||
}
|
||||
|
||||
func runBenchmarks() {
|
||||
fmt.Printf("%s🏃 Running benchmarks...%s\n\n", colors.Yellow, colors.Reset)
|
||||
|
||||
cmd := exec.Command("go", "test", "-bench=.", "-benchmem", "./tests/integration/...")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Printf("%s❌ Benchmarks failed: %v%s\n", colors.Red, err, colors.Reset)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(string(output))
|
||||
}
|
||||
|
||||
// Точка входа с поддержкой аргументов командной строки
|
||||
func mainWithArgs() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "interactive" {
|
||||
interactiveMode()
|
||||
return
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 && os.Args[1] == "bench" {
|
||||
runBenchmarks()
|
||||
return
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 && os.Args[1] == "watch" {
|
||||
config := parseFlags()
|
||||
watchServer(config)
|
||||
return
|
||||
}
|
||||
|
||||
// Обычный режим с флагами
|
||||
config := parseFlags()
|
||||
printBanner()
|
||||
|
||||
if !checkServer(config) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cleanTestCache()
|
||||
results := runTests(config)
|
||||
printResults(results)
|
||||
|
||||
if hasFailures(results) {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// cmd/testrunner/runner.go - Дополнительные утилиты
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Сохранение результатов в JSON
|
||||
func saveResultsToJSON(results []TestResult, filename string) error {
|
||||
type JSONResult struct {
|
||||
Timestamp string `json:"timestamp"`
|
||||
Results []TestResult `json:"results"`
|
||||
Summary struct {
|
||||
Total int `json:"total"`
|
||||
Passed int `json:"passed"`
|
||||
Failed int `json:"failed"`
|
||||
PassRate float64 `json:"pass_rate"`
|
||||
} `json:"summary"`
|
||||
}
|
||||
|
||||
var jsonResult JSONResult
|
||||
jsonResult.Timestamp = time.Now().Format(time.RFC3339)
|
||||
jsonResult.Results = results
|
||||
|
||||
for _, r := range results {
|
||||
jsonResult.Summary.Total++
|
||||
if r.Passed {
|
||||
jsonResult.Summary.Passed++
|
||||
} else {
|
||||
jsonResult.Summary.Failed++
|
||||
}
|
||||
}
|
||||
|
||||
if jsonResult.Summary.Total > 0 {
|
||||
jsonResult.Summary.PassRate = float64(jsonResult.Summary.Passed) / float64(jsonResult.Summary.Total) * 100
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(jsonResult, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(filename, data, 0644)
|
||||
}
|
||||
|
||||
// Создание отчета в формате JUnit XML (для CI/CD)
|
||||
func generateJUnitReport(results []TestResult, filename string) error {
|
||||
type TestCase struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Classname string `xml:"classname,attr"`
|
||||
Time float64 `xml:"time,attr"`
|
||||
Failure *string `xml:"failure,omitempty"`
|
||||
}
|
||||
|
||||
type TestSuite struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Tests int `xml:"tests,attr"`
|
||||
Failures int `xml:"failures,attr"`
|
||||
Time float64 `xml:"time,attr"`
|
||||
TestCases []TestCase `xml:"testcase"`
|
||||
}
|
||||
|
||||
type TestSuites struct {
|
||||
XMLName string `xml:"testsuites"`
|
||||
TestSuites []TestSuite `xml:"testsuite"`
|
||||
}
|
||||
|
||||
var suites TestSuites
|
||||
|
||||
for _, result := range results {
|
||||
suite := TestSuite{
|
||||
Name: result.Suite,
|
||||
Tests: 1,
|
||||
Time: result.Duration.Seconds(),
|
||||
}
|
||||
|
||||
testCase := TestCase{
|
||||
Name: fmt.Sprintf("%s Suite", result.Suite),
|
||||
Classname: "api.tests",
|
||||
Time: result.Duration.Seconds(),
|
||||
}
|
||||
|
||||
if !result.Passed {
|
||||
suite.Failures = 1
|
||||
failureMsg := result.Error.Error()
|
||||
testCase.Failure = &failureMsg
|
||||
}
|
||||
|
||||
suite.TestCases = append(suite.TestCases, testCase)
|
||||
suites.TestSuites = append(suites.TestSuites, suite)
|
||||
}
|
||||
|
||||
xmlData, err := xml.MarshalIndent(suites, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(filename, append([]byte(xml.Header), xmlData...), 0644)
|
||||
}
|
||||
|
||||
// Функция для запуска определенных тестов по тегам
|
||||
func runTestsByTag(tag string, config *Config) []TestResult {
|
||||
fmt.Printf("%s🏷️ Running tests with tag: %s%s\n", colors.Yellow, tag, colors.Reset)
|
||||
|
||||
cmd := exec.Command("go", "test", "-v", "-tags", tag, "./tests/integration/...")
|
||||
output, err := cmd.CombinedOutput()
|
||||
|
||||
result := TestResult{
|
||||
Suite: fmt.Sprintf("Tag: %s", tag),
|
||||
Output: string(output),
|
||||
Passed: err == nil,
|
||||
Error: err,
|
||||
}
|
||||
|
||||
if result.Passed {
|
||||
fmt.Printf("%s✅ Tests with tag '%s' passed%s\n", colors.Green, tag, colors.Reset)
|
||||
} else {
|
||||
fmt.Printf("%s❌ Tests with tag '%s' failed%s\n", colors.Red, tag, colors.Reset)
|
||||
}
|
||||
|
||||
return []TestResult{result}
|
||||
}
|
||||
|
||||
// Создание скрипта для CI/CD
|
||||
func generateCIScript() {
|
||||
ciScript := `#!/bin/bash
|
||||
# CI/CD script for API tests
|
||||
|
||||
set -e
|
||||
|
||||
echo "Starting API Tests in CI environment"
|
||||
|
||||
# Запуск сервера в фоне
|
||||
go run cmd/main.go &
|
||||
SERVER_PID=$!
|
||||
|
||||
# Ждем запуска сервера
|
||||
sleep 5
|
||||
|
||||
# Запуск тестов
|
||||
go run cmd/testrunner/main.go -suite all -coverage
|
||||
|
||||
# Остановка сервера
|
||||
kill $SERVER_PID
|
||||
|
||||
echo "Tests completed"
|
||||
`
|
||||
|
||||
if err := os.WriteFile("scripts/ci_test.sh", []byte(ciScript), 0755); err != nil {
|
||||
fmt.Printf("Failed to generate CI script: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для сравнения результатов тестов
|
||||
func compareResults(current, previous []TestResult) {
|
||||
fmt.Printf("%s📊 Test Result Comparison%s\n", colors.Cyan, colors.Reset)
|
||||
fmt.Println(strings.Repeat("-", 50))
|
||||
|
||||
currentMap := make(map[string]TestResult)
|
||||
for _, r := range current {
|
||||
currentMap[r.Suite] = r
|
||||
}
|
||||
|
||||
prevMap := make(map[string]TestResult)
|
||||
for _, r := range previous {
|
||||
prevMap[r.Suite] = r
|
||||
}
|
||||
|
||||
for name, currentResult := range currentMap {
|
||||
prevResult, exists := prevMap[name]
|
||||
if !exists {
|
||||
fmt.Printf("%s➕ New test suite: %s%s\n", colors.Green, name, colors.Reset)
|
||||
continue
|
||||
}
|
||||
|
||||
if currentResult.Passed != prevResult.Passed {
|
||||
if currentResult.Passed {
|
||||
fmt.Printf("%s✅ Fixed: %s (was failing)%s\n", colors.Green, name, colors.Reset)
|
||||
} else {
|
||||
fmt.Printf("%s❌ Regression: %s (was passing)%s\n", colors.Red, name, colors.Reset)
|
||||
}
|
||||
} else if currentResult.Passed {
|
||||
// Сравнение времени выполнения
|
||||
diff := currentResult.Duration - prevResult.Duration
|
||||
if diff > 0 {
|
||||
fmt.Printf("%s⚠️ Slower: %s (+%v)%s\n", colors.Yellow, name, diff.Round(time.Millisecond), colors.Reset)
|
||||
} else if diff < 0 {
|
||||
fmt.Printf("%s⚡ Faster: %s (%v)%s\n", colors.Green, name, (-diff).Round(time.Millisecond), colors.Reset)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user