From 2285bc86b9285d6accb54e7c1875a8ec7e79e2d4 Mon Sep 17 00:00:00 2001 From: zijiren233 Date: Fri, 13 Oct 2023 16:27:49 +0800 Subject: [PATCH] Feat: self-update command --- build.sh | 6 +- cmd/root.go | 4 +- cmd/self-update.go | 34 ++++++ cmd/version.go | 8 +- go.mod | 3 + go.sum | 9 +- internal/bootstrap/log.go | 16 +-- internal/conf/global.go | 5 +- internal/conf/var.go | 6 - internal/version/update.go | 110 ++++++++++++++++++ internal/version/version.go | 188 +++++++++++++++++++++++++++++++ internal/version/version_test.go | 32 ++++++ utils/utils.go | 47 ++++++++ utils/utils_test.go | 12 ++ 14 files changed, 454 insertions(+), 26 deletions(-) create mode 100644 cmd/self-update.go create mode 100644 internal/version/update.go create mode 100644 internal/version/version.go create mode 100644 internal/version/version_test.go diff --git a/build.sh b/build.sh index e220994..46316f8 100755 --- a/build.sh +++ b/build.sh @@ -118,9 +118,9 @@ function FixArgs() { fi fi LDFLAGS="$LDFLAGS \ - -X 'github.com/synctv-org/synctv/internal/conf.Version=$VERSION' \ - -X 'github.com/synctv-org/synctv/internal/conf.WebVersion=$WEB_VERSION' \ - -X 'github.com/synctv-org/synctv/internal/conf.GitCommit=$GIT_COMMIT'" + -X 'github.com/synctv-org/synctv/internal/version.Version=$VERSION' \ + -X 'github.com/synctv-org/synctv/internal/version.WebVersion=$WEB_VERSION' \ + -X 'github.com/synctv-org/synctv/internal/version.GitCommit=$GIT_COMMIT'" BUILD_DIR="$(echo "$BUILD_DIR" | sed 's#/$##')" } diff --git a/cmd/root.go b/cmd/root.go index 0d4aa6d..7fe77c7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,7 +6,7 @@ import ( "github.com/spf13/cobra" "github.com/synctv-org/synctv/cmd/flags" - "github.com/synctv-org/synctv/internal/conf" + "github.com/synctv-org/synctv/internal/version" ) var RootCmd = &cobra.Command{ @@ -23,7 +23,7 @@ func Execute() { } func init() { - RootCmd.PersistentFlags().BoolVar(&flags.Dev, "dev", conf.Version == "dev", "start with dev mode") + RootCmd.PersistentFlags().BoolVar(&flags.Dev, "dev", version.Version == "dev", "start with dev mode") RootCmd.PersistentFlags().BoolVar(&flags.LogStd, "log-std", true, "log to std") RootCmd.PersistentFlags().BoolVar(&flags.EnvNoPrefix, "env-no-prefix", false, "env no SYNCTV_ prefix") RootCmd.PersistentFlags().BoolVar(&flags.SkipConfig, "skip-config", false, "skip config") diff --git a/cmd/self-update.go b/cmd/self-update.go new file mode 100644 index 0000000..732ab63 --- /dev/null +++ b/cmd/self-update.go @@ -0,0 +1,34 @@ +package cmd + +import ( + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/synctv-org/synctv/internal/conf" + "github.com/synctv-org/synctv/internal/version" +) + +const SelfUpdateLong = `self-update command will update synctv-server binary to latest version. +Version check url: https://github.com/synctv-org/synctv/releases/latest + +If use '--dev' flag, will update to latest dev version always.` + +var SelfUpdateCmd = &cobra.Command{ + Use: "self-update", + Short: "self-update", + Long: SelfUpdateLong, + PersistentPreRunE: Init, + RunE: SelfUpdate, +} + +func SelfUpdate(cmd *cobra.Command, args []string) error { + v, err := version.NewVersionInfo(version.WithBaseURL(conf.Conf.Global.GitHubBaseURL)) + if err != nil { + log.Errorf("get version info error: %v", err) + return err + } + return v.SelfUpdate(cmd.Context()) +} + +func init() { + RootCmd.AddCommand(SelfUpdateCmd) +} diff --git a/cmd/version.go b/cmd/version.go index 1ef1d77..52a4fa4 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -5,7 +5,7 @@ import ( "runtime" "github.com/spf13/cobra" - "github.com/synctv-org/synctv/internal/conf" + "github.com/synctv-org/synctv/internal/version" ) var VersionCmd = &cobra.Command{ @@ -13,9 +13,9 @@ var VersionCmd = &cobra.Command{ Short: "Print the version number of Sync TV Server", Long: `All software has versions. This is Sync TV Server's`, Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("synctv %s\n", conf.Version) - fmt.Printf("- web/version: %s\n", conf.WebVersion) - fmt.Printf("- git/commit: %s\n", conf.GitCommit) + fmt.Printf("synctv %s\n", version.Version) + fmt.Printf("- web/version: %s\n", version.WebVersion) + fmt.Printf("- git/commit: %s\n", version.GitCommit) fmt.Printf("- os/platform: %s\n", runtime.GOOS) fmt.Printf("- os/arch: %s\n", runtime.GOARCH) fmt.Printf("- go/version: %s\n", runtime.Version()) diff --git a/go.mod b/go.mod index 8478665..04b9e87 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,12 @@ go 1.20 require ( github.com/caarlos0/env/v9 v9.0.0 + github.com/cavaliergopher/grab/v3 v3.0.1 github.com/gin-contrib/cors v1.4.0 github.com/gin-gonic/gin v1.9.1 github.com/go-resty/resty/v2 v2.9.1 github.com/golang-jwt/jwt/v5 v5.0.0 + github.com/google/go-github/v56 v56.0.0 github.com/google/uuid v1.3.1 github.com/gorilla/websocket v1.5.0 github.com/json-iterator/go v1.1.12 @@ -40,6 +42,7 @@ require ( github.com/go-playground/validator/v10 v10.15.5 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect diff --git a/go.sum b/go.sum index 34a0f17..195087c 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZF github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/caarlos0/env/v9 v9.0.0 h1:SI6JNsOA+y5gj9njpgybykATIylrRMklbs5ch6wO6pc= github.com/caarlos0/env/v9 v9.0.0/go.mod h1:ye5mlCVMYh6tZ+vCgrs/B95sj88cg5Tlnc0XIzgZ020= +github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= +github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -17,8 +19,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= @@ -51,8 +51,13 @@ github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJ github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-github/v56 v56.0.0 h1:TysL7dMa/r7wsQi44BjqlwaHvwlFlqkK8CtBWCX3gb4= +github.com/google/go-github/v56 v56.0.0/go.mod h1:D8cdcX98YWJvi7TLo7zM4/h8ZTx6u6fwGEkCdisopo0= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98 h1:pUa4ghanp6q4IJHwE9RwLgmVFfReJN+KbQ8ExNEUUoQ= github.com/google/pprof v0.0.0-20230926050212-f7f687d19a98/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= diff --git a/internal/bootstrap/log.go b/internal/bootstrap/log.go index b8cc89a..1b1d1f1 100644 --- a/internal/bootstrap/log.go +++ b/internal/bootstrap/log.go @@ -24,14 +24,13 @@ func setLog(l *logrus.Logger) { func InitLog() { setLog(logrus.StandardLogger()) - logConfig := conf.Conf.Log - if logConfig.Enable { + if conf.Conf.Log.Enable { var l = &lumberjack.Logger{ - Filename: logConfig.FilePath, - MaxSize: logConfig.MaxSize, - MaxBackups: logConfig.MaxBackups, - MaxAge: logConfig.MaxAge, - Compress: logConfig.Compress, + Filename: conf.Conf.Log.FilePath, + MaxSize: conf.Conf.Log.MaxSize, + MaxBackups: conf.Conf.Log.MaxBackups, + MaxAge: conf.Conf.Log.MaxAge, + Compress: conf.Conf.Log.Compress, } if err := l.Rotate(); err != nil { logrus.Fatalf("log: rotate log file error: %v", err) @@ -39,8 +38,9 @@ func InitLog() { var w io.Writer = colorable.NewNonColorableWriter(l) if flags.Dev || flags.LogStd { w = io.MultiWriter(os.Stdout, w) + logrus.Infof("log: enable log to stdout and file: %s", conf.Conf.Log.FilePath) } else { - logrus.Infof("log: disable log to stdout, only log to file: %s", logConfig.FilePath) + logrus.Infof("log: disable log to stdout, only log to file: %s", conf.Conf.Log.FilePath) } logrus.SetOutput(w) } diff --git a/internal/conf/global.go b/internal/conf/global.go index 0fe5949..496ccb7 100644 --- a/internal/conf/global.go +++ b/internal/conf/global.go @@ -1,8 +1,11 @@ package conf type GlobalConfig struct { + GitHubBaseURL string `yaml:"github_base_url" lc:"default: https://api.github.com/" env:"GITHUB_BASE_URL"` } func DefaultGlobalConfig() GlobalConfig { - return GlobalConfig{} + return GlobalConfig{ + GitHubBaseURL: "https://api.github.com/", + } } diff --git a/internal/conf/var.go b/internal/conf/var.go index 2f43ca4..ec6d985 100644 --- a/internal/conf/var.go +++ b/internal/conf/var.go @@ -3,9 +3,3 @@ package conf var ( Conf *Config ) - -var ( - Version string = "dev" - WebVersion string = "dev" - GitCommit string -) diff --git a/internal/version/update.go b/internal/version/update.go new file mode 100644 index 0000000..6880517 --- /dev/null +++ b/internal/version/update.go @@ -0,0 +1,110 @@ +package version + +import ( + "context" + "fmt" + "os" + "path/filepath" + "time" + + "github.com/cavaliergopher/grab/v3" + log "github.com/sirupsen/logrus" +) + +func SelfUpdate(ctx context.Context, url string) error { + now := time.Now().UnixNano() + currentExecFile, err := ExecutableFile() + if err != nil { + log.Errorf("self update: get current executable file error: %v", err) + return err + } + log.Infof("self update: current executable file: %s", currentExecFile) + + tmp := filepath.Join(os.TempDir(), "synctv-server", fmt.Sprintf("self-update-%d", now)) + if err := os.MkdirAll(tmp, 0755); err != nil { + log.Errorf("self update: mkdir %s error: %v", tmp, err) + return err + } + log.Infof("self update: temp path: %s", tmp) + defer func() { + log.Infof("self update: remove temp path: %s", tmp) + if err := os.RemoveAll(tmp); err != nil { + log.Warnf("self update: remove temp path error: %v", err) + } + }() + file, err := DownloadWithProgress(ctx, url, tmp) + if err != nil { + log.Errorf("self update: download %s error: %v", url, err) + return err + } + log.Infof("self update: download success: %s", file) + + if err := os.Chmod(file, 0755); err != nil { + log.Errorf("self update: chmod %s error: %v", file, err) + return err + } + log.Infof("self update: chmod success: %s", file) + + oldName := fmt.Sprintf("%s-%d.old", currentExecFile, now) + if err := os.Rename(currentExecFile, oldName); err != nil { + log.Errorf("self update: rename %s -> %s error: %v", currentExecFile, oldName, err) + return err + } + log.Infof("self update: rename success: %s -> %s", currentExecFile, oldName) + + defer func() { + if err != nil { + log.Infof("self update: rollback: %s -> %s", oldName, currentExecFile) + if err := os.Rename(oldName, currentExecFile); err != nil { + log.Errorf("self update: rollback: rename %s -> %s error: %v", oldName, currentExecFile, err) + } + } else { + log.Infof("self update: remove old executable file: %s", oldName) + if err := os.Remove(oldName); err != nil { + log.Warnf("self update: remove old executable file error: %v", err) + } + } + }() + + if err := os.Rename(file, currentExecFile); err != nil { + log.Errorf("self update: rename %s -> %s error: %v", file, currentExecFile, err) + return err + } + + log.Infof("self update: update success: %s", currentExecFile) + + return nil +} + +func DownloadWithProgress(ctx context.Context, url, path string) (string, error) { + req, err := grab.NewRequest(path, url) + if err != nil { + return "", err + } + req = req.WithContext(ctx) + resp := grab.NewClient().Do(req) + t := time.NewTicker(250 * time.Millisecond) + defer t.Stop() + + for { + select { + case <-t.C: + log.Infof("self update: transferred %d / %d bytes (%.2f%%)", + resp.BytesComplete(), + resp.Size(), + 100*resp.Progress()) + + case <-resp.Done: + return resp.Filename, resp.Err() + } + } +} + +// get current executable file +func ExecutableFile() (string, error) { + p, err := os.Executable() + if err != nil { + return "", err + } + return filepath.EvalSymlinks(p) +} diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..0b7a187 --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,188 @@ +package version + +import ( + "context" + "errors" + "fmt" + "runtime" + "strings" + + "github.com/google/go-github/v56/github" + log "github.com/sirupsen/logrus" + "github.com/synctv-org/synctv/cmd/flags" + "github.com/synctv-org/synctv/utils" +) + +const ( + owner = "synctv-org" + repo = "synctv" +) + +var ( + Version string = "dev" + WebVersion string = "dev" + GitCommit string +) + +type VersionInfo struct { + current string + latest *github.RepositoryRelease + dev *github.RepositoryRelease + c *github.Client + + baseURL string +} + +func WithBaseURL(baseURL string) VersionInfoConf { + return func(v *VersionInfo) { + v.baseURL = baseURL + } +} + +type VersionInfoConf func(*VersionInfo) + +func NewVersionInfo(conf ...VersionInfoConf) (*VersionInfo, error) { + v := &VersionInfo{ + current: Version, + } + for _, c := range conf { + c(v) + } + return v, v.fix() +} + +func (v *VersionInfo) fix() (err error) { + if v.baseURL == "" { + v.baseURL = "https://api.github.com/" + } + v.c, err = github.NewClient(nil).WithEnterpriseURLs(v.baseURL, "") + return err +} + +func (v *VersionInfo) initLatest(ctx context.Context) (err error) { + if v.latest != nil { + return nil + } + v.latest, _, err = v.c.Repositories.GetLatestRelease(ctx, owner, repo) + return +} + +func (v *VersionInfo) initDev(ctx context.Context) (err error) { + if v.dev != nil { + return nil + } + v.dev, _, err = v.c.Repositories.GetReleaseByTag(ctx, owner, repo, "dev") + return +} + +func (v *VersionInfo) Current() string { + return v.current +} + +func (v *VersionInfo) Latest(ctx context.Context) (string, error) { + if err := v.initLatest(ctx); err != nil { + return "", err + } + return v.latest.GetTagName(), nil +} + +func (v *VersionInfo) CheckLatest(ctx context.Context) (string, error) { + release, _, err := v.c.Repositories.GetLatestRelease(ctx, owner, repo) + if err != nil { + return "", err + } + v.latest = release + return release.GetTagName(), nil +} + +func (v *VersionInfo) LatestBinaryURL(ctx context.Context) (string, error) { + if err := v.initLatest(ctx); err != nil { + return "", err + } + return getBinaryURL(v.latest) +} + +func (v *VersionInfo) DevBinaryURL(ctx context.Context) (string, error) { + if err := v.initDev(ctx); err != nil { + return "", err + } + return getBinaryURL(v.dev) +} + +func getBinaryURL(repo *github.RepositoryRelease) (string, error) { + prefix := fmt.Sprintf("synctv-%s-%s", runtime.GOOS, runtime.GOARCH) + for _, a := range repo.Assets { + if strings.HasPrefix(a.GetName(), prefix) { + return a.GetBrowserDownloadURL(), nil + } + } + return "", errors.New("no binary found") +} + +// NeedUpdate return true if current version is less than latest version +// if current version is dev, always return false +func (v *VersionInfo) NeedUpdate(ctx context.Context) (bool, error) { + if v.Current() == "dev" { + return false, nil + } + + latest, err := v.Latest(ctx) + if err != nil { + return false, err + } + + comp, err := utils.CompVersion(v.Current(), latest) + if err != nil { + return false, err + } + + switch comp { + case utils.VersionEqual: + return false, nil + case utils.VersionLess: + return true, nil + case utils.VersionGreater: + return false, nil + } + + return false, nil +} + +func (v *VersionInfo) SelfUpdate(ctx context.Context) (err error) { + if flags.Dev { + log.Info("self update: dev mode, update to latest dev version") + } else if v.Current() != "dev" { + latest, err := v.Latest(ctx) + if err != nil { + return err + } + comp, err := utils.CompVersion(v.Current(), latest) + if err != nil { + return err + } + switch comp { + case utils.VersionEqual: + log.Infof("self update: current version is latest: %s", v.Current()) + return nil + case utils.VersionLess: + log.Infof("self update: current version is less than latest: %s -> %s", v.Current(), latest) + case utils.VersionGreater: + log.Infof("self update: current version is greater than latest: %s ? %s", v.Current(), latest) + return nil + } + } else { + log.Info("self update: current version is dev, force update") + } + + var url string + if flags.Dev { + url, err = v.DevBinaryURL(ctx) + } else { + url, err = v.LatestBinaryURL(ctx) + } + if err != nil { + return err + } + + return SelfUpdate(ctx, url) +} diff --git a/internal/version/version_test.go b/internal/version/version_test.go new file mode 100644 index 0000000..64d02f2 --- /dev/null +++ b/internal/version/version_test.go @@ -0,0 +1,32 @@ +package version_test + +import ( + "context" + "testing" + + "github.com/synctv-org/synctv/internal/version" +) + +func TestCheckLatest(t *testing.T) { + v, err := version.NewVersionInfo() + if err != nil { + t.Fatal(err) + } + s, err := v.CheckLatest(context.Background()) + if err != nil { + t.Fatal(err) + } + t.Log(s) +} + +func TestLatestBinaryURL(t *testing.T) { + v, err := version.NewVersionInfo() + if err != nil { + t.Fatal(err) + } + s, err := v.LatestBinaryURL(context.Background()) + if err != nil { + t.Fatal(err) + } + t.Log(s) +} diff --git a/utils/utils.go b/utils/utils.go index e43c84d..c69f23b 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -4,6 +4,8 @@ import ( "math/rand" "os" "path/filepath" + "strconv" + "strings" yamlcomment "github.com/zijiren233/yaml-comment" "gopkg.in/yaml.v3" @@ -86,3 +88,48 @@ func ReadYaml(file string, module any) error { defer f.Close() return yaml.NewDecoder(f).Decode(module) } + +const ( + VersionEqual = iota + VersionGreater + VersionLess +) + +func CompVersion(v1, v2 string) (int, error) { + if v1 == v2 { + return VersionEqual, nil + } + v1s, err := SplitVersion(strings.TrimLeft(v1, "v")) + if err != nil { + return VersionEqual, err + } + v2s, err := SplitVersion(strings.TrimLeft(v2, "v")) + if err != nil { + return VersionEqual, err + } + for i := 0; i < len(v1s) && i < len(v2s); i++ { + if v1s[i] > v2s[i] { + return VersionGreater, nil + } else if v1s[i] < v2s[i] { + return VersionLess, nil + } + } + if len(v1s) > len(v2s) { + return VersionGreater, nil + } else if len(v1s) < len(v2s) { + return VersionLess, nil + } + return VersionGreater, nil +} + +func SplitVersion(v string) ([]int, error) { + var vs []int + for _, s := range strings.Split(v, ".") { + i, err := strconv.Atoi(s) + if err != nil { + return nil, err + } + vs = append(vs, i) + } + return vs, nil +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 1826386..e7b9d6b 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -54,3 +54,15 @@ func TestGetPageItems(t *testing.T) { }) } } + +func FuzzCompVersion(f *testing.F) { + f.Add("v1.0.0", "v1.0.1") + f.Add("v0.2.9", "v1.5.2") + f.Fuzz(func(t *testing.T, a, b string) { + t.Logf("a: %s, b: %s", a, b) + _, err := utils.CompVersion(a, b) + if err != nil { + t.Errorf("CompVersion error = %v", err) + } + }) +}