216 lines · 5.2 KB · Go
RawDownload
// Package testserver starts and stops test servers if requiredpackage testserver import (	"bytes"	"errors"	"fmt"	"net"	"os"	"os/exec"	"path/filepath"	"regexp"	"strings"	"sync"	"time" 	"github.com/rclone/rclone/fs"	"github.com/rclone/rclone/fs/fspath") var (	once      sync.Once	configDir string // where the config is stored	// Note of running servers	runningMu   sync.Mutex	running     = map[string]int{}	errNotFound = errors.New("command not found")) // Assume we are run somewhere within the rclone rootfunc findConfig() (string, error) {	dir := filepath.Join("fstest", "testserver", "init.d")	for range 5 {		fi, err := os.Stat(dir)		if err == nil && fi.IsDir() {			return filepath.Abs(dir)		} else if !os.IsNotExist(err) {			return "", err		}		dir = filepath.Join("..", dir)	}	return "", errors.New("couldn't find testserver config files - run from within rclone source")} // run the command returning the output and an errorfunc run(name, command string) (out []byte, err error) {	cmdPath := filepath.Join(configDir, name)	fi, err := os.Stat(cmdPath)	if err != nil || fi.IsDir() {		return nil, errNotFound	}	cmd := exec.Command(cmdPath, command)	out, err = cmd.CombinedOutput()	if err != nil {		err = fmt.Errorf("failed to run %s %s\n%s: %w", cmdPath, command, string(out), err)	}	return out, err} // Check to see if the server is runningfunc isRunning(name string) bool {	_, err := run(name, "status")	return err == nil} // envKey returns the environment variable name to set name, keyfunc envKey(name, key string) string {	return fmt.Sprintf("RCLONE_CONFIG_%s_%s", strings.ToUpper(name), strings.ToUpper(key))} // match a line of config var=valuevar matchLine = regexp.MustCompile(`^([a-zA-Z_]+)=(.*)$`) // Start the server and set its env vars// Call with the mutex heldfunc start(name string) error {	fs.Logf(name, "Starting server")	out, err := run(name, "start")	if err != nil {		return err	}	// parse the output and set environment vars from it	var connect string	var connectDelay time.Duration	for _, line := range bytes.Split(out, []byte("\n")) {		line = bytes.TrimSpace(line)		part := matchLine.FindSubmatch(line)		if part != nil {			key, value := part[1], part[2]			if string(key) == "_connect" {				connect = string(value)				continue			} else if string(key) == "_connect_delay" {				connectDelay, err = time.ParseDuration(string(value))				if err != nil {					return fmt.Errorf("bad _connect_delay: %w", err)				}				continue			} 			// fs.Debugf(name, "key = %q, envKey = %q, value = %q", key, envKey(name, string(key)), value)			err = os.Setenv(envKey(name, string(key)), string(value))			if err != nil {				return err			}		}	}	if connect == "" {		fs.Logf(name, "Started server")		return nil	}	// If we got a _connect value then try to connect to it	const maxTries = 30	var rdBuf = make([]byte, 1)	for i := 1; i <= maxTries; i++ {		if i != 0 {			time.Sleep(time.Second)		}		fs.Logf(name, "Attempting to connect to %q try %d/%d", connect, i, maxTries)		conn, err := net.DialTimeout("tcp", connect, time.Second)		if err != nil {			fs.Debugf(name, "Connection to %q failed try %d/%d: %v", connect, i, maxTries, err)			continue		} 		err = conn.SetReadDeadline(time.Now().Add(time.Second))		if err != nil {			return fmt.Errorf("failed to set deadline: %w", err)		}		n, err := conn.Read(rdBuf)		_ = conn.Close()		fs.Debugf(name, "Read %d, error: %v", n, err)		if err != nil && !errors.Is(err, os.ErrDeadlineExceeded) {			// Try again			continue		}		if connectDelay > 0 {			fs.Logf(name, "Connect delay %v", connectDelay)			time.Sleep(connectDelay)		}		fs.Logf(name, "Started server and connected to %q", connect)		return nil	}	return fmt.Errorf("failed to connect to %q on %q", name, connect)} // Start starts the named test server which can be stopped by the// function returned.func Start(remoteName string) (fn func(), err error) {	if remoteName == "" {		// don't start the local backend		return func() {}, nil	}	parsed, err := fspath.Parse(remoteName)	if err != nil {		return nil, err	}	name := parsed.ConfigString	if name == "" {		// don't start the local backend		return func() {}, nil	} 	// Make sure we know where the config is	once.Do(func() {		configDir, err = findConfig()	})	if err != nil {		return nil, err	} 	runningMu.Lock()	defer runningMu.Unlock() 	if running[name] <= 0 {		// if server isn't running check to see if this server has		// been started already but not by us and stop it if so		if os.Getenv(envKey(name, "type")) == "" && isRunning(name) {			stop(name)		}		if !isRunning(name) {			err = start(name)			if err == errNotFound {				// if no file found then don't start or stop				return func() {}, nil			} else if err != nil {				return nil, err			}			running[name] = 0		} else {			running[name] = 1		}	}	running[name]++ 	return func() {		runningMu.Lock()		defer runningMu.Unlock()		stop(name)	}, nil } // Stops the named test server// Call with the mutex heldfunc stop(name string) {	running[name]--	if running[name] <= 0 {		_, err := run(name, "stop")		if err != nil {			fs.Errorf(name, "Failed to stop server: %v", err)		}		running[name] = 0		fs.Logf(name, "Stopped server")	}}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t