diff --git a/hrp/internal/builtin/utils.go b/hrp/internal/builtin/utils.go index 48ee8e38..2ef2f726 100644 --- a/hrp/internal/builtin/utils.go +++ b/hrp/internal/builtin/utils.go @@ -1,7 +1,9 @@ package builtin import ( + "bufio" "bytes" + "context" "crypto/hmac" "crypto/sha256" "encoding/binary" @@ -583,7 +585,69 @@ func RunCommand(cmdName string, args ...string) error { } return err } - + stderrStr := stderr.String() + log.Error().Msg("failed to exec command. msg: " + stderrStr) log.Info().Msg("exec command output: " + stdout.String()) return nil } + +type LineCallback func(line string) bool + +// RunCommandWithCallback 运行命令并根据回调判断是否成功 +func RunCommandWithCallback(cmdName string, args []string, callback LineCallback) error { + cmd := exec.Command(cmdName, args...) + log.Info().Str("command", cmd.String()).Msg("exec command") + + // 使用管道获取标准输出 + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + log.Error().Err(err).Msg("failed to get stdout pipe") + return err + } + var stderr bytes.Buffer + cmd.Stderr = &stderr + + if err := cmd.Start(); err != nil { + log.Error().Err(err).Msg("failed to start command") + return err + } + + // 创建一个用于标识成功的通道 + done := make(chan struct{}) + defer close(done) + + // 逐行读取 stdout + go func() { + stdoutScanner := bufio.NewScanner(stdoutPipe) + for stdoutScanner.Scan() { + line := stdoutScanner.Text() + log.Info().Msg("stdout: " + line) + if callback(line) { + done <- struct{}{} + return + } + } + }() + + // 等待命令执行完成 + err = cmd.Wait() + if err != nil { + log.Error().Msg("failed to exec command. msg: " + stderr.String()) + return err + } + + // 设置一个1秒的超时上下文 + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + select { + case <-done: + return nil + case <-ctx.Done(): + // 超时,判断失败 + log.Error().Msg("failed to exec command. msg: " + stderr.String()) + err = errors.New("command execution failed: callback failed while exec command") + log.Error().Err(err).Msg("failed to find keyword in time") + return err + } +} diff --git a/hrp/pkg/uixt/ios_device.go b/hrp/pkg/uixt/ios_device.go index 8acd8cc2..d7af0615 100644 --- a/hrp/pkg/uixt/ios_device.go +++ b/hrp/pkg/uixt/ios_device.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "time" "github.com/pkg/errors" @@ -474,7 +475,15 @@ func (dev *IOSDevice) StopPcap() string { func (dev *IOSDevice) Install(appPath string, opts *InstallOptions) (err error) { for i := 0; i <= opts.RetryTime; i++ { - err = builtin.RunCommand("ideviceinstaller", "-u", dev.UDID, "-i", appPath) + err = builtin.RunCommandWithCallback("ideviceinstaller", []string{"-u", dev.UDID, "-i", appPath}, func(line string) bool { + if strings.Contains(line, "Complete") { + return true + } + if strings.Contains(line, "90%") { + return true + } + return false + }) if err == nil { return nil } @@ -483,7 +492,15 @@ func (dev *IOSDevice) Install(appPath string, opts *InstallOptions) (err error) } func (dev *IOSDevice) Uninstall(bundleId string) error { - return builtin.RunCommand("ideviceinstaller", "-u", dev.UDID, "-U", bundleId) + return builtin.RunCommandWithCallback("ideviceinstaller", []string{"-u", dev.UDID, "-U", bundleId}, func(line string) bool { + if strings.Contains(line, "Complete") { + return true + } + if strings.Contains(line, "90%") { + return true + } + return false + }) } func (dev *IOSDevice) forward(localPort, remotePort int) error { diff --git a/hrp/pkg/uixt/ios_test.go b/hrp/pkg/uixt/ios_test.go index 18029ca9..6c6565a8 100644 --- a/hrp/pkg/uixt/ios_test.go +++ b/hrp/pkg/uixt/ios_test.go @@ -7,6 +7,8 @@ import ( "fmt" "testing" "time" + + "github.com/rs/zerolog/log" ) var ( @@ -39,7 +41,8 @@ func TestViaUSB(t *testing.T) { func TestInstall(t *testing.T) { setup(t) - err := iOSDriverExt.Install("/Users/bytedance/workcode/httprunner/hrp/pkg/uixt/1722942152176906000", NewInstallOptions(WithRetryTime(5))) + err := iOSDriverExt.Install("/Users/bytedance/Downloads/com.yueyou.cyreader_1387717110_7.54.20.ipa", NewInstallOptions(WithRetryTime(5))) + log.Error().Err(err) if err != nil { t.Fatal(err) }