// +build !noselfupdate package selfupdate import ( "context" "io/ioutil" "os" "os/exec" "path/filepath" "regexp" "runtime" "testing" "time" "github.com/rclone/rclone/fs" "github.com/rclone/rclone/fstest/testy" "github.com/rclone/rclone/lib/random" "github.com/stretchr/testify/assert") func TestGetVersion(t *testing.T) { testy.SkipUnreliable(t) ctx := context.Background() // a beta version can only have "v" prepended resultVer, _, err := GetVersion(ctx, true, "1.2.3.4") assert.NoError(t, err) assert.Equal(t, "v1.2.3.4", resultVer) // but a stable version syntax should be checked _, _, err = GetVersion(ctx, false, "1") assert.Error(t, err) _, _, err = GetVersion(ctx, false, "1.") assert.Error(t, err) _, _, err = GetVersion(ctx, false, "1.2.") assert.Error(t, err) _, _, err = GetVersion(ctx, false, "1.2.3.4") assert.Error(t, err) // incomplete stable version should have micro release added resultVer, _, err = GetVersion(ctx, false, "1.52") assert.NoError(t, err) assert.Equal(t, "v1.52.3", resultVer)} func makeTestDir() (testDir string, err error) { const maxAttempts = 5 testDirBase := filepath.Join(os.TempDir(), "rclone-test-selfupdate.") for attempt := 0; attempt < maxAttempts; attempt++ { testDir = testDirBase + random.String(4) err = os.MkdirAll(testDir, os.ModePerm) if err == nil { break } } return} func TestInstallOnLinux(t *testing.T) { testy.SkipUnreliable(t) if runtime.GOOS != "linux" { t.Skip("this is a Linux only test") } // Prepare for test ctx := context.Background() testDir, err := makeTestDir() assert.NoError(t, err) path := filepath.Join(testDir, "rclone") defer func() { _ = os.Chmod(path, 0644) _ = os.RemoveAll(testDir) }() regexVer := regexp.MustCompile(`v[0-9]\S+`) betaVer, _, err := GetVersion(ctx, true, "") assert.NoError(t, err) // Must do nothing if version isn't changing assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path, Version: fs.Version})) // Must fail on non-writable file assert.NoError(t, ioutil.WriteFile(path, []byte("test"), 0644)) assert.NoError(t, os.Chmod(path, 0000)) err = (InstallUpdate(ctx, &Options{Beta: true, Output: path})) assert.Error(t, err) assert.Contains(t, err.Error(), "run self-update as root") // Must keep non-standard permissions assert.NoError(t, os.Chmod(path, 0644)) assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path})) info, err := os.Stat(path) assert.NoError(t, err) assert.Equal(t, os.FileMode(0644), info.Mode().Perm()) // Must remove temporary files files, err := ioutil.ReadDir(testDir) assert.NoError(t, err) assert.Equal(t, 1, len(files)) // Must contain valid executable assert.NoError(t, os.Chmod(path, 0755)) cmd := exec.Command(path, "version") output, err := cmd.CombinedOutput() assert.NoError(t, err) assert.True(t, cmd.ProcessState.Success()) assert.Equal(t, betaVer, regexVer.FindString(string(output)))} func TestRenameOnWindows(t *testing.T) { testy.SkipUnreliable(t) if runtime.GOOS != "windows" { t.Skip("this is a Windows only test") } // Prepare for test ctx := context.Background() testDir, err := makeTestDir() assert.NoError(t, err) defer func() { _ = os.RemoveAll(testDir) }() path := filepath.Join(testDir, "rclone.exe") regexVer := regexp.MustCompile(`v[0-9]\S+`) stableVer, _, err := GetVersion(ctx, false, "") assert.NoError(t, err) betaVer, _, err := GetVersion(ctx, true, "") assert.NoError(t, err) // Must not create temporary files when target doesn't exist assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path})) files, err := ioutil.ReadDir(testDir) assert.NoError(t, err) assert.Equal(t, 1, len(files)) // Must save running executable as the "old" file cmdWait := exec.Command(path, "config") stdinWait, err := cmdWait.StdinPipe() // Make it run waiting for input assert.NoError(t, err) assert.NoError(t, cmdWait.Start()) assert.NoError(t, InstallUpdate(ctx, &Options{Beta: false, Output: path})) files, err = ioutil.ReadDir(testDir) assert.NoError(t, err) assert.Equal(t, 2, len(files)) pathOld := filepath.Join(testDir, "rclone.old.exe") _, err = os.Stat(pathOld) assert.NoError(t, err) cmd := exec.Command(path, "version") output, err := cmd.CombinedOutput() assert.NoError(t, err) assert.True(t, cmd.ProcessState.Success()) assert.Equal(t, stableVer, regexVer.FindString(string(output))) cmdOld := exec.Command(pathOld, "version") output, err = cmdOld.CombinedOutput() assert.NoError(t, err) assert.True(t, cmdOld.ProcessState.Success()) assert.Equal(t, betaVer, regexVer.FindString(string(output))) // Stop previous waiting executable, run new and saved executables _ = stdinWait.Close() _ = cmdWait.Wait() time.Sleep(100 * time.Millisecond) cmdWait = exec.Command(path, "config") stdinWait, err = cmdWait.StdinPipe() assert.NoError(t, err) assert.NoError(t, cmdWait.Start()) cmdWaitOld := exec.Command(pathOld, "config") stdinWaitOld, err := cmdWaitOld.StdinPipe() assert.NoError(t, err) assert.NoError(t, cmdWaitOld.Start()) // Updating when the "old" executable is running must produce a random "old" file assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path})) files, err = ioutil.ReadDir(testDir) assert.NoError(t, err) assert.Equal(t, 3, len(files)) // Stop all waiting executables _ = stdinWait.Close() _ = cmdWait.Wait() _ = stdinWaitOld.Close() _ = cmdWaitOld.Wait() time.Sleep(100 * time.Millisecond)}
// +build !noselfupdate
package selfupdate
import (
"context"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"testing"
"time"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fstest/testy"
"github.com/rclone/rclone/lib/random"
"github.com/stretchr/testify/assert"
)
func TestGetVersion(t *testing.T) {
testy.SkipUnreliable(t)
ctx := context.Background()
// a beta version can only have "v" prepended
resultVer, _, err := GetVersion(ctx, true, "1.2.3.4")
assert.NoError(t, err)
assert.Equal(t, "v1.2.3.4", resultVer)
// but a stable version syntax should be checked
_, _, err = GetVersion(ctx, false, "1")
assert.Error(t, err)
_, _, err = GetVersion(ctx, false, "1.")
assert.Error(t, err)
_, _, err = GetVersion(ctx, false, "1.2.")
assert.Error(t, err)
_, _, err = GetVersion(ctx, false, "1.2.3.4")
assert.Error(t, err)
// incomplete stable version should have micro release added
resultVer, _, err = GetVersion(ctx, false, "1.52")
assert.NoError(t, err)
assert.Equal(t, "v1.52.3", resultVer)
}
func makeTestDir() (testDir string, err error) {
const maxAttempts = 5
testDirBase := filepath.Join(os.TempDir(), "rclone-test-selfupdate.")
for attempt := 0; attempt < maxAttempts; attempt++ {
testDir = testDirBase + random.String(4)
err = os.MkdirAll(testDir, os.ModePerm)
if err == nil {
break
}
}
return
}
func TestInstallOnLinux(t *testing.T) {
testy.SkipUnreliable(t)
if runtime.GOOS != "linux" {
t.Skip("this is a Linux only test")
}
// Prepare for test
ctx := context.Background()
testDir, err := makeTestDir()
assert.NoError(t, err)
path := filepath.Join(testDir, "rclone")
defer func() {
_ = os.Chmod(path, 0644)
_ = os.RemoveAll(testDir)
}()
regexVer := regexp.MustCompile(`v[0-9]\S+`)
betaVer, _, err := GetVersion(ctx, true, "")
assert.NoError(t, err)
// Must do nothing if version isn't changing
assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path, Version: fs.Version}))
// Must fail on non-writable file
assert.NoError(t, ioutil.WriteFile(path, []byte("test"), 0644))
assert.NoError(t, os.Chmod(path, 0000))
err = (InstallUpdate(ctx, &Options{Beta: true, Output: path}))
assert.Error(t, err)
assert.Contains(t, err.Error(), "run self-update as root")
// Must keep non-standard permissions
assert.NoError(t, os.Chmod(path, 0644))
assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path}))
info, err := os.Stat(path)
assert.NoError(t, err)
assert.Equal(t, os.FileMode(0644), info.Mode().Perm())
// Must remove temporary files
files, err := ioutil.ReadDir(testDir)
assert.NoError(t, err)
assert.Equal(t, 1, len(files))
// Must contain valid executable
assert.NoError(t, os.Chmod(path, 0755))
cmd := exec.Command(path, "version")
output, err := cmd.CombinedOutput()
assert.NoError(t, err)
assert.True(t, cmd.ProcessState.Success())
assert.Equal(t, betaVer, regexVer.FindString(string(output)))
}
func TestRenameOnWindows(t *testing.T) {
testy.SkipUnreliable(t)
if runtime.GOOS != "windows" {
t.Skip("this is a Windows only test")
}
// Prepare for test
ctx := context.Background()
testDir, err := makeTestDir()
assert.NoError(t, err)
defer func() {
_ = os.RemoveAll(testDir)
}()
path := filepath.Join(testDir, "rclone.exe")
regexVer := regexp.MustCompile(`v[0-9]\S+`)
stableVer, _, err := GetVersion(ctx, false, "")
assert.NoError(t, err)
betaVer, _, err := GetVersion(ctx, true, "")
assert.NoError(t, err)
// Must not create temporary files when target doesn't exist
assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path}))
files, err := ioutil.ReadDir(testDir)
assert.NoError(t, err)
assert.Equal(t, 1, len(files))
// Must save running executable as the "old" file
cmdWait := exec.Command(path, "config")
stdinWait, err := cmdWait.StdinPipe() // Make it run waiting for input
assert.NoError(t, err)
assert.NoError(t, cmdWait.Start())
assert.NoError(t, InstallUpdate(ctx, &Options{Beta: false, Output: path}))
files, err = ioutil.ReadDir(testDir)
assert.NoError(t, err)
assert.Equal(t, 2, len(files))
pathOld := filepath.Join(testDir, "rclone.old.exe")
_, err = os.Stat(pathOld)
assert.NoError(t, err)
cmd := exec.Command(path, "version")
output, err := cmd.CombinedOutput()
assert.NoError(t, err)
assert.True(t, cmd.ProcessState.Success())
assert.Equal(t, stableVer, regexVer.FindString(string(output)))
cmdOld := exec.Command(pathOld, "version")
output, err = cmdOld.CombinedOutput()
assert.NoError(t, err)
assert.True(t, cmdOld.ProcessState.Success())
assert.Equal(t, betaVer, regexVer.FindString(string(output)))
// Stop previous waiting executable, run new and saved executables
_ = stdinWait.Close()
_ = cmdWait.Wait()
time.Sleep(100 * time.Millisecond)
cmdWait = exec.Command(path, "config")
stdinWait, err = cmdWait.StdinPipe()
assert.NoError(t, err)
assert.NoError(t, cmdWait.Start())
cmdWaitOld := exec.Command(pathOld, "config")
stdinWaitOld, err := cmdWaitOld.StdinPipe()
assert.NoError(t, err)
assert.NoError(t, cmdWaitOld.Start())
// Updating when the "old" executable is running must produce a random "old" file
assert.NoError(t, InstallUpdate(ctx, &Options{Beta: true, Output: path}))
files, err = ioutil.ReadDir(testDir)
assert.NoError(t, err)
assert.Equal(t, 3, len(files))
// Stop all waiting executables
_ = stdinWait.Close()
_ = cmdWait.Wait()
_ = stdinWaitOld.Close()
_ = cmdWaitOld.Wait()
time.Sleep(100 * time.Millisecond)
}