package smtp import ( "bufio" "context" "fmt" "net" "strings" "testing" "time" "gitea.perlbach24.de/scripte/nexarch/mail/internal/loadtest" ) // smtpLoadtestConcurrency/smtpLoadtestSessions — siehe pop3-Pendant für // die Begründung der Größenordnung (Akzeptanzkriterium 1, QA-07). const ( smtpLoadtestConcurrency = 200 smtpLoadtestSessions = 2000 ) // Zielwerte (Akzeptanzkriterium 3, QA-07) — festgelegt VOR dem // entscheidenden Testlauf, siehe Prüfprotokoll QA-07 für die // Vorab-Messwerte auf 192.168.1.131. const ( smtpTargetP95Latency = 100 * time.Millisecond smtpTargetThroughputMin = 500.0 // Sessions/Sekunde ) // TestLoadtest_SMTPParallelSessionsMeetTargets ist die geforderte // Pflichtprüfung 1+2 (QA-07). func TestLoadtest_SMTPParallelSessionsMeetTargets(t *testing.T) { if testing.Short() { t.Skip("lasttest übersprungen (-short)") } sink := &fakeSink{} addr, stop := startTestServer(t, sink, defaultMaxMessageBytes) defer stop() resourcesBefore := loadtest.SampleResources() result := loadtest.Run(context.Background(), "smtp", smtpLoadtestConcurrency, smtpLoadtestSessions, func(ctx context.Context) error { return runSMTPLoadSession(addr) }) resourcesAfter := loadtest.SampleResources() p50 := result.Percentile(0.50) p95 := result.Percentile(0.95) maxLatency := result.Max() throughput := result.Throughput() heapDeltaMB := float64(resourcesAfter.HeapAllocBytes-resourcesBefore.HeapAllocBytes) / (1024 * 1024) cpuUsed := resourcesAfter.CPUTimeSeconds - resourcesBefore.CPUTimeSeconds fmt.Printf("QA-07 SMTP-Lasttest: %d Sessions, %d parallel, Dauer %v\n", result.Sessions, result.Concurrency, result.Duration) fmt.Printf(" Fehler: %d\n", result.Errors) fmt.Printf(" Durchsatz: %.1f Sessions/s (Ziel: >= %.1f)\n", throughput, smtpTargetThroughputMin) fmt.Printf(" Latenz p50=%v p95=%v (Ziel: <= %v) max=%v\n", p50, p95, smtpTargetP95Latency, maxLatency) fmt.Printf(" Ressourcen: Heap-Delta=%.1f MiB, CPU-Zeit=%.2fs\n", heapDeltaMB, cpuUsed) fmt.Printf(" Angenommene Nachrichten (Sink): %d\n", sink.count()) if result.Errors != 0 { t.Fatalf("QA-07 SMTP: %d von %d sessions fehlgeschlagen", result.Errors, result.Sessions) } if sink.count() != smtpLoadtestSessions { t.Fatalf("QA-07 SMTP: erwartete %d angenommene nachrichten, habe %d", smtpLoadtestSessions, sink.count()) } if p95 > smtpTargetP95Latency { t.Fatalf("QA-07 SMTP: p95-latenz %v überschreitet zielwert %v", p95, smtpTargetP95Latency) } if throughput < smtpTargetThroughputMin { t.Fatalf("QA-07 SMTP: durchsatz %.1f sessions/s unter zielwert %.1f", throughput, smtpTargetThroughputMin) } if heapDeltaMB > 100 { t.Fatalf("QA-07 SMTP: heap-wachstum %.1f MiB deutet auf ressourcenleck hin", heapDeltaMB) } } func runSMTPLoadSession(addr string) error { conn, err := net.DialTimeout("tcp", addr, 3*time.Second) if err != nil { return err } defer func() { _ = conn.Close() }() _ = conn.SetDeadline(time.Now().Add(5 * time.Second)) reader := bufio.NewReader(conn) if _, err := reader.ReadString('\n'); err != nil { // 220-Begrüßung return err } if _, err := conn.Write([]byte("EHLO client.example.com\r\n")); err != nil { return err } for { line, err := reader.ReadString('\n') if err != nil { return err } if strings.HasPrefix(line, "250 ") { break } } if err := sendAndExpectCode(conn, reader, "MAIL FROM:", "250"); err != nil { return err } if err := sendAndExpectCode(conn, reader, "RCPT TO:", "250"); err != nil { return err } if err := sendAndExpectCode(conn, reader, "DATA", "354"); err != nil { return err } if err := sendAndExpectCode(conn, reader, "Subject: last\r\n\r\nHallo\r\n.", "250"); err != nil { return err } return sendAndExpectCode(conn, reader, "QUIT", "221") } func sendAndExpectCode(conn net.Conn, reader *bufio.Reader, cmd, wantCode string) error { if _, err := conn.Write([]byte(cmd + "\r\n")); err != nil { return err } resp, err := reader.ReadString('\n') if err != nil { return err } if code(resp) != wantCode { return fmt.Errorf("%q: erwartete %s, habe %q", cmd, wantCode, resp) } return nil }