rclone/cmdtest/cmdtest_test.go

218 lines
6.8 KiB
Go

// cmdtest_test creates a testable interface to rclone main
//
// The interface is used to perform end-to-end test of
// commands, flags, environment variables etc.
package cmdtest
import (
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/rclone/rclone/fs"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestMain is initially called by go test to initiate the testing.
// TestMain is also called during the tests to start rclone main in a fresh context (using exec.Command).
// The context is determined by setting/finding the environment variable RCLONE_TEST_MAIN
func TestMain(m *testing.M) {
_, found := os.LookupEnv(rcloneTestMain)
if !found {
// started by Go test => execute tests
err := os.Setenv(rcloneTestMain, "true")
if err != nil {
fs.Fatalf(nil, "Unable to set %s: %s", rcloneTestMain, err.Error())
}
os.Exit(m.Run())
} else {
// started by func rcloneExecMain => call rclone main in cmdtest.go
err := os.Unsetenv(rcloneTestMain)
if err != nil {
fs.Fatalf(nil, "Unable to unset %s: %s", rcloneTestMain, err.Error())
}
main()
}
}
const rcloneTestMain = "RCLONE_TEST_MAIN"
// rcloneExecMain calls rclone with the given environment and arguments.
// The environment variables are in a single string separated by ;
// The terminal output is returned as a string.
func rcloneExecMain(env string, args ...string) (string, error) {
_, found := os.LookupEnv(rcloneTestMain)
if !found {
fs.Fatalf(nil, "Unexpected execution path: %s is missing.", rcloneTestMain)
}
// make a call to self to execute rclone main in a predefined environment (enters TestMain above)
command := exec.Command(os.Args[0], args...)
command.Env = getEnvInitial()
if env != "" {
command.Env = append(command.Env, strings.Split(env, ";")...)
}
out, err := command.CombinedOutput()
return string(out), err
}
// rcloneEnv calls rclone with the given environment and arguments.
// The environment variables are in a single string separated by ;
// The test config file is automatically configured in RCLONE_CONFIG.
// The terminal output is returned as a string.
func rcloneEnv(env string, args ...string) (string, error) {
envConfig := env
if testConfig != "" {
if envConfig != "" {
envConfig += ";"
}
envConfig += "RCLONE_CONFIG=" + testConfig
}
return rcloneExecMain(envConfig, args...)
}
// rclone calls rclone with the given arguments, E.g. "version","--help".
// The test config file is automatically configured in RCLONE_CONFIG.
// The terminal output is returned as a string.
func rclone(args ...string) (string, error) {
return rcloneEnv("", args...)
}
// getEnvInitial returns the os environment variables cleaned for RCLONE_ vars (except RCLONE_TEST_MAIN).
func getEnvInitial() []string {
if envInitial == nil {
// Set initial environment variables
osEnv := os.Environ()
for i := range osEnv {
if !strings.HasPrefix(osEnv[i], "RCLONE_") || strings.HasPrefix(osEnv[i], rcloneTestMain) {
envInitial = append(envInitial, osEnv[i])
}
}
}
return envInitial
}
var envInitial []string
// createTestEnvironment creates a temporary testFolder and
// sets testConfig to testFolder/rclone.config.
func createTestEnvironment(t *testing.T) {
//Set temporary folder for config and test data
tempFolder := t.TempDir()
testFolder = filepath.ToSlash(tempFolder)
// Set path to temporary config file
testConfig = testFolder + "/rclone.config"
}
var testFolder string
var testConfig string
// createTestFile creates the file testFolder/name
func createTestFile(name string, t *testing.T) string {
err := os.WriteFile(testFolder+"/"+name, []byte("content_of_"+name), 0666)
require.NoError(t, err)
return testFolder + "/" + name
}
// createTestFolder creates the folder testFolder/name
func createTestFolder(name string, t *testing.T) string {
err := os.Mkdir(testFolder+"/"+name, 0777)
require.NoError(t, err)
return testFolder + "/" + name
}
// createSimpleTestData creates simple test data in testFolder/subFolder
func createSimpleTestData(t *testing.T) string {
createTestFolder("testdata", t)
createTestFile("testdata/file1.txt", t)
createTestFile("testdata/file2.txt", t)
createTestFolder("testdata/folderA", t)
createTestFile("testdata/folderA/fileA1.txt", t)
createTestFile("testdata/folderA/fileA2.txt", t)
createTestFolder("testdata/folderA/folderAA", t)
createTestFile("testdata/folderA/folderAA/fileAA1.txt", t)
createTestFile("testdata/folderA/folderAA/fileAA2.txt", t)
createTestFolder("testdata/folderB", t)
createTestFile("testdata/folderB/fileB1.txt", t)
createTestFile("testdata/folderB/fileB2.txt", t)
t.Cleanup(func() {
err := os.RemoveAll(testFolder + "/testdata")
require.NoError(t, err)
})
return testFolder + "/testdata"
}
// TestCmdTest demonstrates and verifies the test functions for end-to-end testing of rclone
func TestCmdTest(t *testing.T) {
createTestEnvironment(t)
// Test simple call and output from rclone
out, err := rclone("version")
t.Log("rclone version\n" + out)
if assert.NoError(t, err) {
assert.Contains(t, out, "rclone v")
assert.Contains(t, out, "version: ")
assert.NotContains(t, out, "Error:")
assert.NotContains(t, out, "--help")
assert.NotContains(t, out, " DEBUG : ")
assert.Regexp(t, "rclone\\s+v\\d+\\.\\d+", out) // rclone v_.__
}
// Test multiple arguments and DEBUG output
out, err = rclone("version", "-vv")
if assert.NoError(t, err) {
assert.Contains(t, out, "rclone v")
assert.Contains(t, out, " DEBUG : ")
}
// Test error and error output
out, err = rclone("version", "--provoke-an-error")
if assert.Error(t, err) {
assert.Contains(t, err.Error(), "exit status 2")
assert.Contains(t, out, "Error: unknown flag")
}
// Test effect of environment variable
env := "RCLONE_LOG_LEVEL=DEBUG"
out, err = rcloneEnv(env, "version")
if assert.NoError(t, err) {
assert.Contains(t, out, "rclone v")
assert.Contains(t, out, " DEBUG : ")
}
// Test effect of multiple environment variables, including one with ,
env = "RCLONE_LOG_LEVEL=DEBUG;RCLONE_LOG_FORMAT=date,shortfile;RCLONE_STATS=173ms"
out, err = rcloneEnv(env, "version")
if assert.NoError(t, err) {
assert.Contains(t, out, "rclone v")
assert.Contains(t, out, " DEBUG : ")
assert.Regexp(t, "[^\\s]+\\.go:\\d+:", out) // ___.go:__:
assert.Contains(t, out, "173ms")
}
// Test setup of config file
out, err = rclone("config", "create", "myLocal", "local")
if assert.NoError(t, err) {
assert.Contains(t, out, "[myLocal]")
assert.Contains(t, out, "type = local")
}
// Test creation of simple test data
createSimpleTestData(t)
// Test access to config file and simple test data
out, err = rclone("lsl", "myLocal:"+testFolder)
t.Log("rclone lsl myLocal:testFolder\n" + out)
if assert.NoError(t, err) {
assert.Contains(t, out, "rclone.config")
assert.Contains(t, out, "testdata/folderA/fileA1.txt")
}
}