Merge branch 'fix-sleep' into 'master'

refactor: unify float64 conversion logic in ToolSleep and ToolSleepMS, enhance error logging

See merge request iesqa/httprunner!152
This commit is contained in:
李隆
2025-08-12 06:53:15 +00:00
4 changed files with 80 additions and 85 deletions

View File

@@ -217,6 +217,8 @@ func Interface2Float64(i interface{}) (float64, error) {
case string: // e.g. "1", "0.5" case string: // e.g. "1", "0.5"
floatVar, err := strconv.ParseFloat(v, 64) floatVar, err := strconv.ParseFloat(v, 64)
if err != nil { if err != nil {
log.Error().Err(err).Str("value", v).
Msg("convert string to float64 failed")
return 0, err return 0, err
} }
return floatVar, nil return floatVar, nil
@@ -226,6 +228,10 @@ func Interface2Float64(i interface{}) (float64, error) {
if ok { if ok {
return value.Float64() return value.Float64()
} }
// Log error for unsupported types
log.Error().Interface("value", i).Type("type", i).
Msg("convert float64 failed")
return 0, errors.New("failed to convert interface to float64") return 0, errors.New("failed to convert interface to float64")
} }
@@ -334,29 +340,6 @@ func IsZeroFloat64(f float64) bool {
return math.Abs(f) < threshold return math.Abs(f) < threshold
} }
func ConvertToFloat64(val interface{}) (float64, error) {
switch v := val.(type) {
case float64:
return v, nil
case int:
return float64(v), nil
case int64:
return float64(v), nil
case string:
f, err := strconv.ParseFloat(v, 64)
if err != nil {
log.Error().Err(err).Str("value", v).
Msg("convert string to float64 failed")
return 0, err
}
return f, nil
default:
log.Error().Interface("value", val).Type("type", val).
Msg("convert float64 failed")
return 0, errors.New("convert float64 error")
}
}
func ConvertToFloat64Slice(val interface{}) ([]float64, error) { func ConvertToFloat64Slice(val interface{}) ([]float64, error) {
if paramsSlice, ok := val.([]float64); ok { if paramsSlice, ok := val.([]float64); ok {
return paramsSlice, nil return paramsSlice, nil
@@ -369,7 +352,7 @@ func ConvertToFloat64Slice(val interface{}) ([]float64, error) {
var err error var err error
float64Slice := make([]float64, len(paramsSlice)) float64Slice := make([]float64, len(paramsSlice))
for i, v := range paramsSlice { for i, v := range paramsSlice {
float64Slice[i], err = ConvertToFloat64(v) float64Slice[i], err = Interface2Float64(v)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -1 +1 @@
v5.0.0-250811 v5.0.0-250812

View File

@@ -2,9 +2,7 @@ package uixt
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"strconv"
"time" "time"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
@@ -70,28 +68,12 @@ func (t *ToolSleep) Implement() server.ToolHandlerFunc {
// Sleep action logic // Sleep action logic
log.Info().Interface("seconds", seconds).Msg("sleeping") log.Info().Interface("seconds", seconds).Msg("sleeping")
var duration time.Duration // Use Interface2Float64 for unified type conversion
var actualSeconds float64 actualSeconds, err := builtin.Interface2Float64(seconds)
switch v := seconds.(type) { if err != nil {
case float64: return nil, fmt.Errorf("invalid sleep duration: %v", seconds)
actualSeconds = v
duration = time.Duration(v*1000) * time.Millisecond
case int:
actualSeconds = float64(v)
duration = time.Duration(v) * time.Second
case int64:
actualSeconds = float64(v)
duration = time.Duration(v) * time.Second
case string:
s, err := builtin.ConvertToFloat64(v)
if err != nil {
return nil, fmt.Errorf("invalid sleep duration: %v", v)
}
actualSeconds = s
duration = time.Duration(s*1000) * time.Millisecond
default:
return nil, fmt.Errorf("unsupported sleep duration type: %T", v)
} }
duration := time.Duration(actualSeconds) * time.Second
// Extract start_time_ms and use sleepStrict for unified sleep logic // Extract start_time_ms and use sleepStrict for unified sleep logic
startTime, err := extractStartTimeMs(request) startTime, err := extractStartTimeMs(request)
@@ -116,19 +98,19 @@ func (t *ToolSleep) ConvertActionToCallToolRequest(action option.MobileAction) (
arguments := map[string]any{} arguments := map[string]any{}
var seconds float64 var seconds float64
if param, ok := action.Params.(json.Number); ok { if sleepConfig, ok := action.Params.(SleepConfig); ok {
seconds, _ = param.Float64()
arguments["seconds"] = seconds
} else if param, ok := action.Params.(int64); ok {
seconds = float64(param)
arguments["seconds"] = seconds
} else if sleepConfig, ok := action.Params.(SleepConfig); ok {
// When startTime is provided, pass both seconds and startTime // When startTime is provided, pass both seconds and startTime
seconds = sleepConfig.Seconds seconds = sleepConfig.Seconds
arguments["seconds"] = seconds arguments["seconds"] = seconds
arguments["start_time_ms"] = sleepConfig.StartTime.UnixMilli() arguments["start_time_ms"] = sleepConfig.StartTime.UnixMilli()
} else { } else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep params: %v", action.Params) // Use builtin.Interface2Float64 for unified parameter handling
var err error
seconds, err = builtin.Interface2Float64(action.Params)
if err != nil {
return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep params: %v", action.Params)
}
arguments["seconds"] = seconds
} }
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil return BuildMCPCallToolRequest(t.Name(), arguments, action), nil
@@ -167,28 +149,13 @@ func (t *ToolSleepMS) Implement() server.ToolHandlerFunc {
// Sleep MS action logic // Sleep MS action logic
log.Info().Interface("milliseconds", milliseconds).Msg("sleeping in milliseconds") log.Info().Interface("milliseconds", milliseconds).Msg("sleeping in milliseconds")
var duration time.Duration // Use Interface2Float64 for unified type conversion, then convert to int64
var actualMilliseconds int64 floatVal, err := builtin.Interface2Float64(milliseconds)
switch v := milliseconds.(type) { if err != nil {
case float64: return nil, fmt.Errorf("invalid sleep duration: %v", milliseconds)
actualMilliseconds = int64(v)
duration = time.Duration(v) * time.Millisecond
case int:
actualMilliseconds = int64(v)
duration = time.Duration(v) * time.Millisecond
case int64:
actualMilliseconds = v
duration = time.Duration(v) * time.Millisecond
case string:
ms, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid sleep duration: %v", v)
}
actualMilliseconds = ms
duration = time.Duration(ms) * time.Millisecond
default:
return nil, fmt.Errorf("unsupported sleep duration type: %T", v)
} }
actualMilliseconds := int64(floatVal)
duration := time.Duration(actualMilliseconds) * time.Millisecond
// Extract start_time_ms and use sleepStrict for unified sleep logic // Extract start_time_ms and use sleepStrict for unified sleep logic
startTime, err := extractStartTimeMs(request) startTime, err := extractStartTimeMs(request)
@@ -212,19 +179,19 @@ func (t *ToolSleepMS) ConvertActionToCallToolRequest(action option.MobileAction)
arguments := map[string]any{} arguments := map[string]any{}
var milliseconds int64 var milliseconds int64
if param, ok := action.Params.(json.Number); ok { if sleepConfig, ok := action.Params.(SleepConfig); ok {
milliseconds, _ = param.Int64()
arguments["milliseconds"] = milliseconds
} else if param, ok := action.Params.(int64); ok {
milliseconds = param
arguments["milliseconds"] = milliseconds
} else if sleepConfig, ok := action.Params.(SleepConfig); ok {
// When startTime is provided, pass both milliseconds and startTime // When startTime is provided, pass both milliseconds and startTime
milliseconds = sleepConfig.Milliseconds milliseconds = sleepConfig.Milliseconds
arguments["milliseconds"] = milliseconds arguments["milliseconds"] = milliseconds
arguments["start_time_ms"] = sleepConfig.StartTime.UnixMilli() arguments["start_time_ms"] = sleepConfig.StartTime.UnixMilli()
} else { } else {
return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep ms params: %v", action.Params) // Use builtin.Interface2Float64 for unified parameter handling, then convert to int64
floatVal, err := builtin.Interface2Float64(action.Params)
if err != nil {
return mcp.CallToolRequest{}, fmt.Errorf("invalid sleep ms params: %v", action.Params)
}
milliseconds = int64(floatVal)
arguments["milliseconds"] = milliseconds
} }
return BuildMCPCallToolRequest(t.Name(), arguments, action), nil return BuildMCPCallToolRequest(t.Name(), arguments, action), nil

View File

@@ -30,6 +30,15 @@ func TestToolSleep_ConvertActionToCallToolRequest(t *testing.T) {
expectedArgs: map[string]any{"seconds": float64(3.5)}, expectedArgs: map[string]any{"seconds": float64(3.5)},
shouldError: false, shouldError: false,
}, },
{
name: "float64 parameter",
action: option.MobileAction{
Method: option.ACTION_Sleep,
Params: float64(5.2),
},
expectedArgs: map[string]any{"seconds": float64(5.2)},
shouldError: false,
},
{ {
name: "int64 parameter", name: "int64 parameter",
action: option.MobileAction{ action: option.MobileAction{
@@ -63,6 +72,24 @@ func TestToolSleep_ConvertActionToCallToolRequest(t *testing.T) {
expectedArgs: nil, expectedArgs: nil,
shouldError: true, shouldError: true,
}, },
{
name: "json.Number with integer value",
action: option.MobileAction{
Method: option.ACTION_Sleep,
Params: json.Number("10"),
},
expectedArgs: map[string]any{"seconds": float64(10)},
shouldError: false,
},
{
name: "json.Number with decimal value",
action: option.MobileAction{
Method: option.ACTION_Sleep,
Params: json.Number("1.25"),
},
expectedArgs: map[string]any{"seconds": float64(1.25)},
shouldError: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
@@ -109,6 +136,15 @@ func TestToolSleepMS_ConvertActionToCallToolRequest(t *testing.T) {
expectedArgs: map[string]any{"milliseconds": int64(2000)}, expectedArgs: map[string]any{"milliseconds": int64(2000)},
shouldError: false, shouldError: false,
}, },
{
name: "float64 parameter",
action: option.MobileAction{
Method: option.ACTION_SleepMS,
Params: float64(2500.7),
},
expectedArgs: map[string]any{"milliseconds": int64(2500)},
shouldError: false,
},
{ {
name: "SleepConfig with startTime", name: "SleepConfig with startTime",
action: option.MobileAction{ action: option.MobileAction{
@@ -124,6 +160,15 @@ func TestToolSleepMS_ConvertActionToCallToolRequest(t *testing.T) {
}, },
shouldError: false, shouldError: false,
}, },
{
name: "json.Number with decimal value",
action: option.MobileAction{
Method: option.ACTION_SleepMS,
Params: json.Number("1234.56"),
},
expectedArgs: map[string]any{"milliseconds": int64(1234)},
shouldError: false,
},
{ {
name: "invalid parameter type", name: "invalid parameter type",
action: option.MobileAction{ action: option.MobileAction{