mirror of https://github.com/fatedier/frp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
266 lines
6.1 KiB
266 lines
6.1 KiB
package framework |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"os" |
|
"path/filepath" |
|
"regexp" |
|
"strings" |
|
"text/template" |
|
|
|
"github.com/onsi/ginkgo/v2" |
|
|
|
"github.com/fatedier/frp/test/e2e/mock/server" |
|
"github.com/fatedier/frp/test/e2e/pkg/port" |
|
"github.com/fatedier/frp/test/e2e/pkg/process" |
|
) |
|
|
|
type Options struct { |
|
TotalParallelNode int |
|
CurrentNodeIndex int |
|
FromPortIndex int |
|
ToPortIndex int |
|
} |
|
|
|
type Framework struct { |
|
TempDirectory string |
|
|
|
// ports used in this framework indexed by port name. |
|
usedPorts map[string]int |
|
|
|
// record ports allocated by this framework and release them after each test |
|
allocatedPorts []int |
|
|
|
// portAllocator to alloc port for this test case. |
|
portAllocator *port.Allocator |
|
|
|
// Multiple default mock servers used for e2e testing. |
|
mockServers *MockServers |
|
|
|
// To make sure that this framework cleans up after itself, no matter what, |
|
// we install a Cleanup action before each test and clear it after. If we |
|
// should abort, the AfterSuite hook should run all Cleanup actions. |
|
cleanupHandle CleanupActionHandle |
|
|
|
// beforeEachStarted indicates that BeforeEach has started |
|
beforeEachStarted bool |
|
|
|
serverConfPaths []string |
|
serverProcesses []*process.Process |
|
clientConfPaths []string |
|
clientProcesses []*process.Process |
|
|
|
// Manual registered mock servers. |
|
servers []server.Server |
|
|
|
// used to generate unique config file name. |
|
configFileIndex int64 |
|
|
|
// envs used to start processes, the form is `key=value`. |
|
osEnvs []string |
|
} |
|
|
|
func NewDefaultFramework() *Framework { |
|
suiteConfig, _ := ginkgo.GinkgoConfiguration() |
|
options := Options{ |
|
TotalParallelNode: suiteConfig.ParallelTotal, |
|
CurrentNodeIndex: suiteConfig.ParallelProcess, |
|
FromPortIndex: 10000, |
|
ToPortIndex: 30000, |
|
} |
|
return NewFramework(options) |
|
} |
|
|
|
func NewFramework(opt Options) *Framework { |
|
f := &Framework{ |
|
portAllocator: port.NewAllocator(opt.FromPortIndex, opt.ToPortIndex, opt.TotalParallelNode, opt.CurrentNodeIndex-1), |
|
usedPorts: make(map[string]int), |
|
} |
|
|
|
ginkgo.BeforeEach(f.BeforeEach) |
|
ginkgo.AfterEach(f.AfterEach) |
|
return f |
|
} |
|
|
|
// BeforeEach create a temp directory. |
|
func (f *Framework) BeforeEach() { |
|
f.beforeEachStarted = true |
|
|
|
f.cleanupHandle = AddCleanupAction(f.AfterEach) |
|
|
|
dir, err := os.MkdirTemp(os.TempDir(), "frp-e2e-test-*") |
|
ExpectNoError(err) |
|
f.TempDirectory = dir |
|
|
|
f.mockServers = NewMockServers(f.portAllocator) |
|
if err := f.mockServers.Run(); err != nil { |
|
Failf("%v", err) |
|
} |
|
|
|
params := f.mockServers.GetTemplateParams() |
|
for k, v := range params { |
|
switch t := v.(type) { |
|
case int: |
|
f.usedPorts[k] = t |
|
default: |
|
} |
|
} |
|
} |
|
|
|
func (f *Framework) AfterEach() { |
|
if !f.beforeEachStarted { |
|
return |
|
} |
|
|
|
RemoveCleanupAction(f.cleanupHandle) |
|
|
|
// stop processor |
|
for _, p := range f.serverProcesses { |
|
_ = p.Stop() |
|
if TestContext.Debug || ginkgo.CurrentSpecReport().Failed() { |
|
fmt.Println(p.ErrorOutput()) |
|
fmt.Println(p.StdOutput()) |
|
} |
|
} |
|
for _, p := range f.clientProcesses { |
|
_ = p.Stop() |
|
if TestContext.Debug || ginkgo.CurrentSpecReport().Failed() { |
|
fmt.Println(p.ErrorOutput()) |
|
fmt.Println(p.StdOutput()) |
|
} |
|
} |
|
f.serverProcesses = nil |
|
f.clientProcesses = nil |
|
|
|
// close default mock servers |
|
f.mockServers.Close() |
|
|
|
// close manual registered mock servers |
|
for _, s := range f.servers { |
|
s.Close() |
|
} |
|
|
|
// clean directory |
|
os.RemoveAll(f.TempDirectory) |
|
f.TempDirectory = "" |
|
f.serverConfPaths = []string{} |
|
f.clientConfPaths = []string{} |
|
|
|
// release used ports |
|
for _, port := range f.usedPorts { |
|
f.portAllocator.Release(port) |
|
} |
|
f.usedPorts = make(map[string]int) |
|
|
|
// release allocated ports |
|
for _, port := range f.allocatedPorts { |
|
f.portAllocator.Release(port) |
|
} |
|
f.allocatedPorts = make([]int, 0) |
|
|
|
// clear os envs |
|
f.osEnvs = make([]string, 0) |
|
} |
|
|
|
var portRegex = regexp.MustCompile(`{{ \.Port.*? }}`) |
|
|
|
// RenderPortsTemplate render templates with ports. |
|
// |
|
// Local: {{ .Port1 }} |
|
// Target: {{ .Port2 }} |
|
// |
|
// return rendered content and all allocated ports. |
|
func (f *Framework) genPortsFromTemplates(templates []string) (ports map[string]int, err error) { |
|
ports = make(map[string]int) |
|
for _, t := range templates { |
|
arrs := portRegex.FindAllString(t, -1) |
|
for _, str := range arrs { |
|
str = strings.TrimPrefix(str, "{{ .") |
|
str = strings.TrimSuffix(str, " }}") |
|
str = strings.TrimSpace(str) |
|
ports[str] = 0 |
|
} |
|
} |
|
defer func() { |
|
if err != nil { |
|
for _, port := range ports { |
|
f.portAllocator.Release(port) |
|
} |
|
} |
|
}() |
|
|
|
for name := range ports { |
|
port := f.portAllocator.GetByName(name) |
|
if port <= 0 { |
|
return nil, fmt.Errorf("can't allocate port") |
|
} |
|
ports[name] = port |
|
} |
|
return |
|
} |
|
|
|
// RenderTemplates alloc all ports for port names placeholder. |
|
func (f *Framework) RenderTemplates(templates []string) (outs []string, ports map[string]int, err error) { |
|
ports, err = f.genPortsFromTemplates(templates) |
|
if err != nil { |
|
return |
|
} |
|
|
|
params := f.mockServers.GetTemplateParams() |
|
for name, port := range ports { |
|
params[name] = port |
|
} |
|
|
|
for name, port := range f.usedPorts { |
|
params[name] = port |
|
} |
|
|
|
for _, t := range templates { |
|
tmpl, err := template.New("frp-e2e").Parse(t) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
buffer := bytes.NewBuffer(nil) |
|
if err = tmpl.Execute(buffer, params); err != nil { |
|
return nil, nil, err |
|
} |
|
outs = append(outs, buffer.String()) |
|
} |
|
return |
|
} |
|
|
|
func (f *Framework) PortByName(name string) int { |
|
return f.usedPorts[name] |
|
} |
|
|
|
func (f *Framework) AllocPort() int { |
|
port := f.portAllocator.Get() |
|
ExpectTrue(port > 0, "alloc port failed") |
|
f.allocatedPorts = append(f.allocatedPorts, port) |
|
return port |
|
} |
|
|
|
func (f *Framework) ReleasePort(port int) { |
|
f.portAllocator.Release(port) |
|
} |
|
|
|
func (f *Framework) RunServer(portName string, s server.Server) { |
|
f.servers = append(f.servers, s) |
|
if s.BindPort() > 0 && portName != "" { |
|
f.usedPorts[portName] = s.BindPort() |
|
} |
|
err := s.Run() |
|
ExpectNoError(err, "RunServer: with PortName %s", portName) |
|
} |
|
|
|
func (f *Framework) SetEnvs(envs []string) { |
|
f.osEnvs = envs |
|
} |
|
|
|
func (f *Framework) WriteTempFile(name string, content string) string { |
|
filePath := filepath.Join(f.TempDirectory, name) |
|
err := os.WriteFile(filePath, []byte(content), 0o600) |
|
ExpectNoError(err) |
|
return filePath |
|
}
|
|
|