rclone/cmdtest/cmdtest_test.go
albertony 5d6b8141ec Replace deprecated ioutil
As of Go 1.16, the same functionality is now provided by package io or
package os, and those implementations should be preferred in new code.
2022-11-07 11:41:47 +00:00

227 lines
7.1 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 (
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"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 {
log.Fatalf("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 {
log.Fatalf("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 retuned as a string.
func rcloneExecMain(env string, args ...string) (string, error) {
_, found := os.LookupEnv(rcloneTestMain)
if !found {
log.Fatalf("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 retuned 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 retuned 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
// removeTestEnvironment removes the test environment created by createTestEnvironment
func removeTestEnvironment(t *testing.T) {
// Remove temporary folder with all contents
err := os.RemoveAll(testFolder)
require.NoError(t, err)
}
// 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)
return testFolder + "/testdata"
}
// removeSimpleTestData removes the test data created by createSimpleTestData
func removeSimpleTestData(t *testing.T) {
err := os.RemoveAll(testFolder + "/testdata")
require.NoError(t, err)
}
// TestCmdTest demonstrates and verifies the test functions for end-to-end testing of rclone
func TestCmdTest(t *testing.T) {
createTestEnvironment(t)
defer removeTestEnvironment(t)
// Test simple call and output from rclone
out, err := rclone("version")
t.Logf("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 1")
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)
defer removeSimpleTestData(t)
// Test access to config file and simple test data
out, err = rclone("lsl", "myLocal:"+testFolder)
t.Logf("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")
}
}