From 1dee4131b0211019168fe592e2aedebfc46275de Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 24 Nov 2022 23:10:19 +0800 Subject: [PATCH] docs: add examples for API --- examples/worldcup/cli.go | 71 +++++++++++ examples/worldcup/main.go | 208 +++++++++++++++++++++++++++++++++ examples/worldcup/main_test.go | 31 +++++ hrp/pkg/uixt/ext.go | 17 ++- hrp/pkg/uixt/ocr_vedem.go | 2 +- 5 files changed, 319 insertions(+), 10 deletions(-) create mode 100644 examples/worldcup/cli.go create mode 100644 examples/worldcup/main.go create mode 100644 examples/worldcup/main_test.go diff --git a/examples/worldcup/cli.go b/examples/worldcup/cli.go new file mode 100644 index 00000000..5ce17812 --- /dev/null +++ b/examples/worldcup/cli.go @@ -0,0 +1,71 @@ +package main + +import ( + "os" + "strings" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "wcl", + Short: "Monitor FIFA World Cup Live", + Version: "0.1", + PreRun: func(cmd *cobra.Command, args []string) { + log.Logger = zerolog.New( + zerolog.ConsoleWriter{NoColor: false, Out: os.Stderr}, + ).With().Timestamp().Logger() + zerolog.SetGlobalLevel(zerolog.InfoLevel) + setLogLevel(logLevel) + }, + RunE: func(cmd *cobra.Command, args []string) error { + wc := NewWorldCupLive(matchName, osType, duration, interval) + wc.Start() + wc.DumpResult() + return nil + }, +} + +var ( + uuid string + osType string + duration int + interval int + logLevel string + matchName string +) + +func main() { + rootCmd.PersistentFlags().StringVarP(&logLevel, "log-level", "l", "INFO", "set log level") + rootCmd.PersistentFlags().StringVarP(&uuid, "uuid", "u", "", "specify device serial or udid") + rootCmd.PersistentFlags().StringVarP(&osType, "os-type", "t", "ios", "specify mobile os type") + rootCmd.PersistentFlags().IntVarP(&duration, "duration", "d", 30, "set duration in seconds") + rootCmd.PersistentFlags().IntVarP(&interval, "interval", "i", 15, "set interval in seconds") + rootCmd.PersistentFlags().StringVarP(&matchName, "match-name", "n", "", "specify match name") + + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func setLogLevel(level string) { + level = strings.ToUpper(level) + log.Info().Str("level", level).Msg("Set log level") + switch level { + case "DEBUG": + zerolog.SetGlobalLevel(zerolog.DebugLevel) + case "INFO": + zerolog.SetGlobalLevel(zerolog.InfoLevel) + case "WARN": + zerolog.SetGlobalLevel(zerolog.WarnLevel) + case "ERROR": + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + case "FATAL": + zerolog.SetGlobalLevel(zerolog.FatalLevel) + case "PANIC": + zerolog.SetGlobalLevel(zerolog.PanicLevel) + } +} diff --git a/examples/worldcup/main.go b/examples/worldcup/main.go new file mode 100644 index 00000000..cde07c79 --- /dev/null +++ b/examples/worldcup/main.go @@ -0,0 +1,208 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "math" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/rs/zerolog/log" + + "github.com/httprunner/httprunner/v4/hrp/pkg/uixt" +) + +func convertTimeToSeconds(timeStr string) (int, error) { + if !strings.Contains(timeStr, ":") { + return 0, fmt.Errorf("invalid time string: %s", timeStr) + } + + ss := strings.Split(timeStr, ":") + var seconds int + for idx, s := range ss { + i, err := strconv.Atoi(s) + if err != nil { + return 0, err + } + seconds += i * int(math.Pow(60, float64(len(ss)-idx-1))) + } + + return seconds, nil +} + +func initIOSDevice() uixt.Device { + device, err := uixt.NewIOSDevice( + uixt.WithUDID(uuid), + uixt.WithWDAPort(8700), uixt.WithWDAMjpegPort(8800), + uixt.WithResetHomeOnStartup(false), // not reset home on startup + // uixt.WithPerfOptions(), + ) + if err != nil { + log.Fatal().Err(err).Msg("failed to init ios device") + } + return device +} + +func initAndroidDevice() uixt.Device { + device, err := uixt.NewAndroidDevice(uixt.WithSerialNumber(uuid)) + if err != nil { + log.Fatal().Err(err).Msg("failed to init android device") + } + return device +} + +type timeLog struct { + UTCTime int64 `json:"utc_time"` + LiveTime string `json:"live_time"` + LiveTimeSeconds int `json:"live_time_seconds"` +} + +type WorldCupLive struct { + driver *uixt.DriverExt + done chan bool + file *os.File + resultDir string + UUID string `json:"uuid"` + MatchName string `json:"matchName"` + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + Interval int `json:"interval"` // seconds + Duration int `json:"duration"` // seconds + Summary []timeLog `json:"summary"` +} + +func NewWorldCupLive(matchName, osType string, duration, interval int) *WorldCupLive { + var device uixt.Device + log.Info().Str("osType", osType).Msg("init device") + if osType == "ios" { + device = initIOSDevice() + } else { + device = initAndroidDevice() + } + + driverExt, err := device.NewDriver(nil) + if err != nil { + log.Fatal().Err(err).Msg("failed to init driver") + } + + if matchName == "" { + matchName = "unknown-match" + } + + startTime := time.Now() + matchName = fmt.Sprintf("%s-%s", startTime.Format("2006-01-02"), matchName) + resultDir := filepath.Join("worldcup-archives", matchName, startTime.Format("15:04:05")) + + if err = os.MkdirAll(filepath.Join(resultDir, "screenshot"), 0o755); err != nil { + log.Fatal().Err(err).Msg("failed to create result dir") + } + + filename := filepath.Join(resultDir, "log.txt") + f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o755) + if err != nil { + log.Fatal().Err(err).Msg("failed to open file") + } + // write title + f.WriteString("utc_time\tutc_timestamp\tlive_time\tlive_seconds\n") + + if interval == 0 { + interval = 15 + } + if duration == 0 { + duration = 30 + } + + return &WorldCupLive{ + driver: driverExt, + file: f, + resultDir: resultDir, + UUID: device.UUID(), + Duration: duration, + Interval: interval, + StartTime: startTime.Format("2006-01-02 15:04:05"), + MatchName: matchName, + } +} + +func (wc *WorldCupLive) getCurrentLiveTime(utcTime time.Time) error { + utcTimeStr := utcTime.Format("15:04:05") + fileName := filepath.Join( + wc.resultDir, "screenshot", utcTimeStr) + ocrTexts, err := wc.driver.GetTextsByOCR(uixt.WithScreenShot(fileName)) + if err != nil { + log.Error().Err(err).Msg("get ocr texts failed") + return err + } + + var liveTimeSeconds int + for _, ocrText := range ocrTexts { + seconds, err := convertTimeToSeconds(ocrText.Text) + if err == nil { + liveTimeSeconds = seconds + line := fmt.Sprintf("%s\t%d\t%s\t%d\n", + utcTimeStr, utcTime.Unix(), ocrText.Text, liveTimeSeconds) + fmt.Print(line) + if _, err := wc.file.WriteString(line); err != nil { + log.Error().Err(err).Str("line", line).Msg("write timeseries failed") + } + wc.Summary = append(wc.Summary, timeLog{ + UTCTime: utcTime.Unix(), + LiveTime: ocrText.Text, + LiveTimeSeconds: liveTimeSeconds, + }) + break + } + } + return nil +} + +func (wc *WorldCupLive) Start() { + wc.done = make(chan bool) + go func() { + for { + select { + case <-wc.done: + return + default: + utcTime := time.Now() + if utcTime.Unix()%int64(wc.Interval) == 0 { + wc.getCurrentLiveTime(utcTime) + } else { + time.Sleep(500 * time.Millisecond) + } + } + } + }() + time.Sleep(time.Duration(wc.Duration) * time.Second) + wc.Stop() +} + +func (wc *WorldCupLive) Stop() { + wc.EndTime = time.Now().Format("2006-01-02 15:04:05") + wc.done <- true +} + +func (wc *WorldCupLive) DumpResult() error { + // init json encoder + buffer := new(bytes.Buffer) + encoder := json.NewEncoder(buffer) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", " ") + + err := encoder.Encode(wc) + if err != nil { + return err + } + + filename := filepath.Join(wc.resultDir, "summary.json") + err = os.WriteFile(filename, buffer.Bytes(), 0o755) + if err != nil { + log.Error().Err(err).Msg("dump json path failed") + return err + } + return nil +} diff --git a/examples/worldcup/main_test.go b/examples/worldcup/main_test.go new file mode 100644 index 00000000..cbc75f4e --- /dev/null +++ b/examples/worldcup/main_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConvertTimeToSeconds(t *testing.T) { + testData := []struct { + timeStr string + seconds int + }{ + {"00:00", 0}, + {"00:01", 1}, + {"01:00", 60}, + {"01:01", 61}, + {"00:01:02", 62}, + {"01:02:03", 3723}, + } + + for _, td := range testData { + seconds, err := convertTimeToSeconds(td.timeStr) + assert.Nil(t, err) + assert.Equal(t, td.seconds, seconds) + } +} + +func TestMain(t *testing.T) { + main() +} diff --git a/hrp/pkg/uixt/ext.go b/hrp/pkg/uixt/ext.go index b544cd19..903c3558 100644 --- a/hrp/pkg/uixt/ext.go +++ b/hrp/pkg/uixt/ext.go @@ -259,21 +259,14 @@ func (dExt *DriverExt) takeScreenShot() (raw *bytes.Buffer, err error) { return raw, nil } -// saveScreenShot saves image file to $CWD/screenshots/ folder +// saveScreenShot saves image file with file name func saveScreenShot(raw *bytes.Buffer, fileName string) (string, error) { img, format, err := image.Decode(raw) if err != nil { return "", errors.Wrap(err, "decode screenshot image failed") } - dir, _ := os.Getwd() - screenshotsDir := filepath.Join(dir, "screenshots") - if err = os.MkdirAll(screenshotsDir, os.ModePerm); err != nil { - return "", errors.Wrap(err, "create screenshots directory failed") - } - screenshotPath := filepath.Join(screenshotsDir, - fmt.Sprintf("%s.%s", fileName, format)) - + screenshotPath := filepath.Join(fmt.Sprintf("%s.%s", fileName, format)) file, err := os.Create(screenshotPath) if err != nil { return "", errors.Wrap(err, "create screenshot image file failed") @@ -304,6 +297,12 @@ func (dExt *DriverExt) ScreenShot(fileName string) (string, error) { return "", errors.Wrap(err, "screenshot failed") } + dir, _ := os.Getwd() + screenshotsDir := filepath.Join(dir, "screenshots") + if err = os.MkdirAll(screenshotsDir, os.ModePerm); err != nil { + return "", errors.Wrap(err, "create screenshots directory failed") + } + fileName = filepath.Join(screenshotsDir, fileName) path, err := saveScreenShot(raw, fileName) if err != nil { return "", errors.Wrap(err, "save screenshot failed") diff --git a/hrp/pkg/uixt/ocr_vedem.go b/hrp/pkg/uixt/ocr_vedem.go index 9d1bd229..03d8a271 100644 --- a/hrp/pkg/uixt/ocr_vedem.go +++ b/hrp/pkg/uixt/ocr_vedem.go @@ -177,7 +177,7 @@ func (s *veDEMOCRService) GetTexts(imageBuf *bytes.Buffer, options ...DataOption if err != nil { return nil, errors.Wrap(err, "save screenshot failed") } - log.Info().Str("path", path).Msg("save screenshot") + log.Debug().Str("path", path).Msg("save screenshot") } for _, ocrResult := range ocrResults {