diff --git a/cli/cmd/geniex/BUILD.bazel b/cli/cmd/geniex/BUILD.bazel index ea4c8e39c..222335d24 100644 --- a/cli/cmd/geniex/BUILD.bazel +++ b/cli/cmd/geniex/BUILD.bazel @@ -45,7 +45,6 @@ go_cgo_test( srcs = [ "infer_test.go", "model_test.go", - "update_test.go", ], embed = [":geniex_lib"], gotags = [ diff --git a/cli/cmd/geniex/update.go b/cli/cmd/geniex/update.go index 8bf177dae..fae41790d 100644 --- a/cli/cmd/geniex/update.go +++ b/cli/cmd/geniex/update.go @@ -48,18 +48,21 @@ const ( ) func update() *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ GroupID: "management", Use: "update", Short: "update geniex", Long: "Update geniex to the latest version", RunE: func(cmd *cobra.Command, args []string) error { - return runUpdate(cmd, args) + silent, _ := cmd.Flags().GetBool("silent") + return runUpdate(silent) }, } + cmd.Flags().Bool("silent", false, "run the installer silently (no prompts or progress UI)") + return cmd } -func runUpdate(_ *cobra.Command, _ []string) error { +func runUpdate(silent bool) error { latest, err := getLatestVersion() if err != nil { return err @@ -138,7 +141,14 @@ func runUpdate(_ *cobra.Command, _ []string) error { return err } - if err := exec.Command(dst).Start(); err != nil { + var installArgs []string + if silent { + installArgs = []string{"/VERYSILENT", "/SUPPRESSMSGBOXES", "/NORESTART"} + } + // dst is a local file downloaded from the official release S3 bucket and + // verified against its published SHA256 above, so executing it is safe. + // nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command + if err := exec.Command(dst, installArgs...).Start(); err != nil { return err } fmt.Println("update package is ready to install") diff --git a/cli/cmd/geniex/update_test.go b/cli/cmd/geniex/update_test.go deleted file mode 100644 index 19d2451b3..000000000 --- a/cli/cmd/geniex/update_test.go +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2024-2026 Qualcomm Technologies, Inc. and/or its subsidiaries. -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "testing" -) - -func TestCompareVersion(t *testing.T) { - tests := []struct { - name string - v1 string - v2 string - expected int - wantErr bool - }{ - // Equal versions - { - name: "equal versions with v prefix", - v1: "v1.0.0", - v2: "v1.0.0", - expected: 0, - wantErr: false, - }, - { - name: "equal versions without v prefix", - v1: "1.0.0", - v2: "1.0.0", - expected: 0, - wantErr: false, - }, - { - name: "equal versions mixed prefix", - v1: "v2.5.10", - v2: "2.5.10", - expected: 0, - wantErr: false, - }, - { - name: "equal versions with large numbers", - v1: "v10.20.30", - v2: "v10.20.30", - expected: 0, - wantErr: false, - }, - - // v1 < v2 cases - { - name: "major version less", - v1: "v1.0.0", - v2: "v2.0.0", - expected: -1, - wantErr: false, - }, - { - name: "minor version less", - v1: "v1.0.0", - v2: "v1.1.0", - expected: -1, - wantErr: false, - }, - { - name: "patch version less", - v1: "v1.0.0", - v2: "v1.0.1", - expected: -1, - wantErr: false, - }, - { - name: "large numbers less", - v1: "v10.20.30", - v2: "v10.20.31", - expected: -1, - wantErr: false, - }, - { - name: "major version large numbers less", - v1: "v99.99.99", - v2: "v100.0.0", - expected: -1, - wantErr: false, - }, - - // v1 > v2 cases - { - name: "major version greater", - v1: "v2.0.0", - v2: "v1.0.0", - expected: 1, - wantErr: false, - }, - { - name: "minor version greater", - v1: "v1.1.0", - v2: "v1.0.0", - expected: 1, - wantErr: false, - }, - { - name: "patch version greater", - v1: "v1.0.1", - v2: "v1.0.0", - expected: 1, - wantErr: false, - }, - { - name: "large numbers greater", - v1: "v10.20.31", - v2: "v10.20.30", - expected: 1, - wantErr: false, - }, - { - name: "complex comparison", - v1: "v1.2.3", - v2: "v1.2.2", - expected: 1, - wantErr: false, - }, - { - name: "complex comparison reverse", - v1: "v1.2.2", - v2: "v1.2.3", - expected: -1, - wantErr: false, - }, - - // Edge cases - { - name: "zero versions", - v1: "v0.0.0", - v2: "v0.0.0", - expected: 0, - wantErr: false, - }, - { - name: "zero to one", - v1: "v0.0.0", - v2: "v0.0.1", - expected: -1, - wantErr: false, - }, - { - name: "very large numbers", - v1: "v999.999.999", - v2: "v999.999.998", - expected: 1, - wantErr: false, - }, - - // Pre-release versions (SemVer: pre-release < stable of same base) - { - name: "rc version less than stable", - v1: "v0.2.68-rc2", - v2: "v0.2.68", - expected: -1, - wantErr: false, - }, - { - name: "rc version greater than older stable", - v1: "v0.2.68-rc2", - v2: "v0.2.67", - expected: 1, - wantErr: false, - }, - { - name: "rc version less than newer stable", - v1: "v0.2.68-rc2", - v2: "v0.2.69", - expected: -1, - wantErr: false, - }, - { - name: "beta version less than stable", - v1: "v1.0.0-beta", - v2: "v1.0.0", - expected: -1, - wantErr: false, - }, - { - name: "alpha version greater than older stable", - v1: "v2.5.0-alpha.1", - v2: "v2.4.9", - expected: 1, - wantErr: false, - }, - { - name: "build metadata ignored", - v1: "v1.0.0+build123", - v2: "v1.0.0", - expected: 0, - wantErr: false, - }, - { - name: "prerelease with build metadata less than stable", - v1: "v3.2.1-rc1+build456", - v2: "v3.2.1", - expected: -1, - wantErr: false, - }, - { - name: "rc1 less than rc2", - v1: "v0.2.68-rc1", - v2: "v0.2.68-rc2", - expected: -1, - wantErr: false, - }, - - // Invalid format cases - { - name: "short form canonicalizes to zero patch", - v1: "v1.0", - v2: "v1.0.0", - expected: 0, - wantErr: false, - }, - { - name: "invalid format - too many parts", - v1: "v1.0.0.0", - v2: "v1.0.0", - expected: 0, - wantErr: true, - }, - { - name: "invalid format - non-numeric", - v1: "v1.0.a", - v2: "v1.0.0", - expected: 0, - wantErr: true, - }, - { - name: "invalid format - empty string", - v1: "", - v2: "v1.0.0", - expected: 0, - wantErr: true, - }, - { - name: "invalid format - both invalid", - v1: "invalid", - v2: "also-invalid", - expected: 0, - wantErr: true, - }, - { - name: "invalid format - negative numbers", - v1: "v-1.0.0", - v2: "v1.0.0", - expected: 0, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := compareVersion(tt.v1, tt.v2) - if (err != nil) != tt.wantErr { - t.Errorf("compareVersion() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !tt.wantErr && result != tt.expected { - t.Errorf("compareVersion() = %v, want %v", result, tt.expected) - } - }) - } -} diff --git a/cli/release/windows/geniex-cli-setup.iss b/cli/release/windows/geniex-cli-setup.iss index c22858d14..41b1143f2 100644 --- a/cli/release/windows/geniex-cli-setup.iss +++ b/cli/release/windows/geniex-cli-setup.iss @@ -68,19 +68,23 @@ begin if not RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', UninstallString) then Exit; - if MsgBox('Existing version detected.'#13#10 + + { Bare MsgBox still renders (hidden) under /VERYSILENT and deadlocks setup; + SuppressibleMsgBox returns Default in silent mode. } + if SuppressibleMsgBox('Existing version detected.'#13#10 + 'Please uninstall the existing version first.'#13#10#13#10 + - 'Uninstall now?', mbConfirmation, MB_YESNO) <> IDYES then + 'Uninstall now?', mbConfirmation, MB_YESNO, IDYES) <> IDYES then begin - MsgBox('Installation aborted.', mbInformation, MB_OK); + SuppressibleMsgBox('Installation aborted.', mbInformation, MB_OK, IDOK); Result := False; Exit; end; - if (not Exec(RemoveQuotes(UninstallString), '/SILENT', '', SW_SHOW, ewWaitUntilTerminated, ResultCode)) + { /SUPPRESSMSGBOXES is required — /VERYSILENT alone does not silence the + old uninstaller's own MsgBox calls. } + if (not Exec(RemoveQuotes(UninstallString), '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES', '', SW_HIDE, ewWaitUntilTerminated, ResultCode)) or (ResultCode <> 0) then begin - MsgBox(Format('Uninstall failed (ErrCode: %d).', [ResultCode]), mbError, MB_OK); + SuppressibleMsgBox(Format('Uninstall failed (ErrCode: %d).', [ResultCode]), mbError, MB_OK, IDOK); Result := False; Exit; end; @@ -90,7 +94,7 @@ begin begin if Waited >= 30000 then begin - MsgBox('Timed out waiting for the previous version to finish uninstalling.', mbError, MB_OK); + SuppressibleMsgBox('Timed out waiting for the previous version to finish uninstalling.', mbError, MB_OK, IDOK); Result := False; Exit; end;