mirror of
https://github.com/httprunner/httprunner.git
synced 2026-09-06 16:07:45 +08:00
refactor: simplify log entry filtering in report generation by removing redundant time handling and enhancing step boundary checks
This commit is contained in:
@@ -1 +1 @@
|
|||||||
v5.0.0-beta-2506182314
|
v5.0.0-beta-2506191040
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
|
|
||||||
"github.com/httprunner/httprunner/v5/internal/builtin"
|
"github.com/httprunner/httprunner/v5/internal/builtin"
|
||||||
"github.com/httprunner/httprunner/v5/uixt"
|
"github.com/httprunner/httprunner/v5/uixt"
|
||||||
"github.com/httprunner/httprunner/v5/uixt/ai"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@@ -174,127 +173,17 @@ func (g *HTMLReportGenerator) loadLogData() error {
|
|||||||
return scanner.Err()
|
return scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// getStepLogs filters log entries for a specific test step based on time range
|
// getStepLogs filters log entries for a specific test step based on step boundaries
|
||||||
func (g *HTMLReportGenerator) getStepLogs(stepName string, startTime int64, elapsed int64) []LogEntry {
|
func (g *HTMLReportGenerator) getStepLogs(stepName string, startTime int64, elapsed int64) []LogEntry {
|
||||||
if len(g.LogData) == 0 {
|
if len(g.LogData) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var stepLogs []LogEntry
|
var stepLogs []LogEntry
|
||||||
|
|
||||||
// Handle different time units properly
|
|
||||||
// Check if startTime is already in milliseconds (> year 2100 in seconds)
|
|
||||||
var startTimeMs, endTimeMs int64
|
|
||||||
if startTime > 4000000000 { // If > year 2100, assume it's already in milliseconds
|
|
||||||
startTimeMs = startTime
|
|
||||||
} else {
|
|
||||||
// startTime is in seconds (backward compatibility), elapsed is in milliseconds
|
|
||||||
startTimeMs = startTime * 1000
|
|
||||||
}
|
|
||||||
endTimeMs = startTimeMs + elapsed
|
|
||||||
|
|
||||||
// Find the actual time range of this step's logs by scanning for step boundaries
|
|
||||||
var actualStartTime, actualEndTime int64
|
|
||||||
actualStartTime = startTimeMs
|
|
||||||
actualEndTime = endTimeMs
|
|
||||||
|
|
||||||
// Look for step start/end markers to determine precise boundaries
|
|
||||||
for _, logEntry := range g.LogData {
|
|
||||||
logTime, err := g.parseLogTimeMs(logEntry.Time)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for step start marker - more precise matching
|
|
||||||
if logEntry.Message == "run step start" {
|
|
||||||
if stepFieldValue, exists := logEntry.Fields["step"]; exists {
|
|
||||||
if stepFieldValue == stepName {
|
|
||||||
actualStartTime = logTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check for step end marker - more precise matching
|
|
||||||
if logEntry.Message == "run step end" {
|
|
||||||
if stepFieldValue, exists := logEntry.Fields["step"]; exists {
|
|
||||||
if stepFieldValue == stepName {
|
|
||||||
actualEndTime = logTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the next step's start time to avoid overlapping logs
|
|
||||||
var nextStepStartTime int64 = 0
|
|
||||||
for _, logEntry := range g.LogData {
|
|
||||||
logTime, err := g.parseLogTimeMs(logEntry.Time)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the next step start that occurs after our step's actual end time
|
|
||||||
if logEntry.Message == "run step start" && logTime > actualEndTime {
|
|
||||||
if stepFieldValue, exists := logEntry.Fields["step"]; exists {
|
|
||||||
if stepFieldValue != stepName { // Make sure it's a different step
|
|
||||||
nextStepStartTime = logTime
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add intelligent buffer for AI conversations, but respect step boundaries
|
|
||||||
var maxAIEndTime int64 = actualEndTime
|
|
||||||
var inAIConversation bool
|
|
||||||
|
|
||||||
for _, logEntry := range g.LogData {
|
|
||||||
logTime, err := g.parseLogTimeMs(logEntry.Time)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip logs that are clearly before our step
|
|
||||||
if logTime < actualStartTime {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip logs that are after the next step starts
|
|
||||||
if nextStepStartTime > 0 && logTime >= nextStepStartTime {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track AI conversation patterns
|
|
||||||
if strings.Contains(logEntry.Message, ai.LOG_REQUEST_MESSAGES) {
|
|
||||||
if logTime >= actualStartTime && logTime <= actualEndTime+30000 { // 30s buffer for AI requests
|
|
||||||
inAIConversation = true
|
|
||||||
}
|
|
||||||
} else if strings.Contains(logEntry.Message, ai.LOG_RESPONSE_MESSAGE) && inAIConversation {
|
|
||||||
// Extend end time to include complete AI conversation, but respect next step boundary
|
|
||||||
extendedEndTime := logTime
|
|
||||||
if nextStepStartTime > 0 && extendedEndTime >= nextStepStartTime {
|
|
||||||
extendedEndTime = nextStepStartTime - 1 // Stop just before next step
|
|
||||||
}
|
|
||||||
if extendedEndTime > maxAIEndTime {
|
|
||||||
maxAIEndTime = extendedEndTime
|
|
||||||
}
|
|
||||||
inAIConversation = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the extended end time if AI conversation was detected
|
|
||||||
if maxAIEndTime > actualEndTime {
|
|
||||||
actualEndTime = maxAIEndTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final boundary check: ensure we don't go beyond the next step
|
|
||||||
if nextStepStartTime > 0 && actualEndTime >= nextStepStartTime {
|
|
||||||
actualEndTime = nextStepStartTime - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final filtering: collect logs strictly within step boundaries
|
|
||||||
var inCurrentStep bool = false
|
var inCurrentStep bool = false
|
||||||
|
|
||||||
|
// Simple approach: use step start/end markers for precise boundaries
|
||||||
for _, logEntry := range g.LogData {
|
for _, logEntry := range g.LogData {
|
||||||
|
|
||||||
// Check for step boundaries to control inclusion
|
// Check for step boundaries to control inclusion
|
||||||
if logEntry.Message == "run step start" {
|
if logEntry.Message == "run step start" {
|
||||||
if stepFieldValue, exists := logEntry.Fields["step"]; exists {
|
if stepFieldValue, exists := logEntry.Fields["step"]; exists {
|
||||||
@@ -302,14 +191,10 @@ func (g *HTMLReportGenerator) getStepLogs(stepName string, startTime int64, elap
|
|||||||
inCurrentStep = true
|
inCurrentStep = true
|
||||||
stepLogs = append(stepLogs, logEntry)
|
stepLogs = append(stepLogs, logEntry)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else if inCurrentStep {
|
||||||
// This is a different step starting
|
// This is a different step starting, we're done
|
||||||
if inCurrentStep {
|
|
||||||
// We were in our step, now we're not
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +203,6 @@ func (g *HTMLReportGenerator) getStepLogs(stepName string, startTime int64, elap
|
|||||||
if stepFieldValue == stepName {
|
if stepFieldValue == stepName {
|
||||||
stepLogs = append(stepLogs, logEntry)
|
stepLogs = append(stepLogs, logEntry)
|
||||||
inCurrentStep = false
|
inCurrentStep = false
|
||||||
// Continue to check for AI conversations that might extend beyond
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,16 +233,6 @@ func (g *HTMLReportGenerator) getStepLogs(stepName string, startTime int64, elap
|
|||||||
return stepLogs
|
return stepLogs
|
||||||
}
|
}
|
||||||
|
|
||||||
// containsLogEntry checks if a log entry already exists in the slice
|
|
||||||
func containsLogEntry(logs []LogEntry, entry LogEntry) bool {
|
|
||||||
for _, log := range logs {
|
|
||||||
if log.LogIndex == entry.LogIndex {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseLogTime parses various time formats from log entries
|
// parseLogTime parses various time formats from log entries
|
||||||
func (g *HTMLReportGenerator) parseLogTime(timeStr string) (time.Time, error) {
|
func (g *HTMLReportGenerator) parseLogTime(timeStr string) (time.Time, error) {
|
||||||
// Handle different time formats that might appear in logs
|
// Handle different time formats that might appear in logs
|
||||||
@@ -386,15 +260,6 @@ func (g *HTMLReportGenerator) parseLogTime(timeStr string) (time.Time, error) {
|
|||||||
return time.Time{}, fmt.Errorf("unable to parse time: %s", timeStr)
|
return time.Time{}, fmt.Errorf("unable to parse time: %s", timeStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseLogTimeMs parses time string to milliseconds since Unix epoch
|
|
||||||
func (g *HTMLReportGenerator) parseLogTimeMs(timeStr string) (int64, error) {
|
|
||||||
t, err := g.parseLogTime(timeStr)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return t.UnixNano() / 1000000, nil // Convert nanoseconds to milliseconds
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeImageToBase64 encodes an image file to base64 string with compression
|
// encodeImageToBase64 encodes an image file to base64 string with compression
|
||||||
func (g *HTMLReportGenerator) encodeImageToBase64(imagePath string) string {
|
func (g *HTMLReportGenerator) encodeImageToBase64(imagePath string) string {
|
||||||
// Convert relative path to absolute path
|
// Convert relative path to absolute path
|
||||||
@@ -460,38 +325,15 @@ func (g *HTMLReportGenerator) getStepLogsForTemplate(step *StepResult) []LogEntr
|
|||||||
|
|
||||||
// calculateTotalActions calculates the total number of actions across all test cases
|
// calculateTotalActions calculates the total number of actions across all test cases
|
||||||
func (g *HTMLReportGenerator) calculateTotalActions() int {
|
func (g *HTMLReportGenerator) calculateTotalActions() int {
|
||||||
total := 0
|
return g.iterateTestData(func(action *ActionResult) int {
|
||||||
if g.SummaryData == nil || g.SummaryData.Details == nil {
|
return 1 // Count each action
|
||||||
return total
|
})
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range g.SummaryData.Details {
|
|
||||||
if testCase.Records == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, step := range testCase.Records {
|
|
||||||
if step.Actions != nil {
|
|
||||||
total += len(step.Actions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculateTotalSubActions calculates the total number of sub-actions across all test cases
|
// calculateTotalSubActions calculates the total number of sub-actions across all test cases
|
||||||
func (g *HTMLReportGenerator) calculateTotalSubActions() int {
|
func (g *HTMLReportGenerator) calculateTotalSubActions() int {
|
||||||
|
return g.iterateTestData(func(action *ActionResult) int {
|
||||||
total := 0
|
total := 0
|
||||||
if g.SummaryData == nil || g.SummaryData.Details == nil {
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range g.SummaryData.Details {
|
|
||||||
if testCase.Records == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, step := range testCase.Records {
|
|
||||||
if step.Actions != nil {
|
|
||||||
for _, action := range step.Actions {
|
|
||||||
// Count sub-actions from regular actions
|
// Count sub-actions from regular actions
|
||||||
if action.SubActions != nil {
|
if action.SubActions != nil {
|
||||||
total += len(action.SubActions)
|
total += len(action.SubActions)
|
||||||
@@ -504,35 +346,18 @@ func (g *HTMLReportGenerator) calculateTotalSubActions() int {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total
|
return total
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculateTotalPlannings calculates the total number of planning results across all test cases
|
// calculateTotalPlannings calculates the total number of planning results across all test cases
|
||||||
func (g *HTMLReportGenerator) calculateTotalPlannings() int {
|
func (g *HTMLReportGenerator) calculateTotalPlannings() int {
|
||||||
total := 0
|
return g.iterateTestData(func(action *ActionResult) int {
|
||||||
if g.SummaryData == nil || g.SummaryData.Details == nil {
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range g.SummaryData.Details {
|
|
||||||
if testCase.Records == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, step := range testCase.Records {
|
|
||||||
if step.Actions != nil {
|
|
||||||
for _, action := range step.Actions {
|
|
||||||
if action.Plannings != nil {
|
if action.Plannings != nil {
|
||||||
total += len(action.Plannings)
|
return len(action.Plannings)
|
||||||
}
|
}
|
||||||
}
|
return 0
|
||||||
}
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
return total
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculateTotalUsage calculates the total token usage across all test cases
|
// calculateTotalUsage calculates the total token usage across all test cases
|
||||||
@@ -573,6 +398,28 @@ func (g *HTMLReportGenerator) calculateTotalUsage() map[string]interface{} {
|
|||||||
return totalUsage
|
return totalUsage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// iterateTestData is a helper function that iterates through all actions and applies a counting function
|
||||||
|
func (g *HTMLReportGenerator) iterateTestData(countFunc func(*ActionResult) int) int {
|
||||||
|
total := 0
|
||||||
|
if g.SummaryData == nil || g.SummaryData.Details == nil {
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range g.SummaryData.Details {
|
||||||
|
if testCase.Records == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, step := range testCase.Records {
|
||||||
|
if step.Actions != nil {
|
||||||
|
for _, action := range step.Actions {
|
||||||
|
total += countFunc(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
// GenerateReport generates the complete HTML test report
|
// GenerateReport generates the complete HTML test report
|
||||||
func (g *HTMLReportGenerator) GenerateReport(outputFile string) error {
|
func (g *HTMLReportGenerator) GenerateReport(outputFile string) error {
|
||||||
if outputFile == "" {
|
if outputFile == "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user