framework.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. package framework
  2. import (
  3. "bytes"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "regexp"
  8. "strings"
  9. "text/template"
  10. "github.com/onsi/ginkgo/v2"
  11. "github.com/fatedier/frp/test/e2e/mock/server"
  12. "github.com/fatedier/frp/test/e2e/pkg/port"
  13. "github.com/fatedier/frp/test/e2e/pkg/process"
  14. )
  15. type Options struct {
  16. TotalParallelNode int
  17. CurrentNodeIndex int
  18. FromPortIndex int
  19. ToPortIndex int
  20. }
  21. type Framework struct {
  22. TempDirectory string
  23. // ports used in this framework indexed by port name.
  24. usedPorts map[string]int
  25. // record ports allocated by this framework and release them after each test
  26. allocatedPorts []int
  27. // portAllocator to alloc port for this test case.
  28. portAllocator *port.Allocator
  29. // Multiple default mock servers used for e2e testing.
  30. mockServers *MockServers
  31. // To make sure that this framework cleans up after itself, no matter what,
  32. // we install a Cleanup action before each test and clear it after. If we
  33. // should abort, the AfterSuite hook should run all Cleanup actions.
  34. cleanupHandle CleanupActionHandle
  35. // beforeEachStarted indicates that BeforeEach has started
  36. beforeEachStarted bool
  37. serverConfPaths []string
  38. serverProcesses []*process.Process
  39. clientConfPaths []string
  40. clientProcesses []*process.Process
  41. // Manual registered mock servers.
  42. servers []server.Server
  43. // used to generate unique config file name.
  44. configFileIndex int64
  45. // envs used to start processes, the form is `key=value`.
  46. osEnvs []string
  47. }
  48. func NewDefaultFramework() *Framework {
  49. suiteConfig, _ := ginkgo.GinkgoConfiguration()
  50. options := Options{
  51. TotalParallelNode: suiteConfig.ParallelTotal,
  52. CurrentNodeIndex: suiteConfig.ParallelProcess,
  53. FromPortIndex: 10000,
  54. ToPortIndex: 30000,
  55. }
  56. return NewFramework(options)
  57. }
  58. func NewFramework(opt Options) *Framework {
  59. f := &Framework{
  60. portAllocator: port.NewAllocator(opt.FromPortIndex, opt.ToPortIndex, opt.TotalParallelNode, opt.CurrentNodeIndex-1),
  61. usedPorts: make(map[string]int),
  62. }
  63. ginkgo.BeforeEach(f.BeforeEach)
  64. ginkgo.AfterEach(f.AfterEach)
  65. return f
  66. }
  67. // BeforeEach create a temp directory.
  68. func (f *Framework) BeforeEach() {
  69. f.beforeEachStarted = true
  70. f.cleanupHandle = AddCleanupAction(f.AfterEach)
  71. dir, err := os.MkdirTemp(os.TempDir(), "frp-e2e-test-*")
  72. ExpectNoError(err)
  73. f.TempDirectory = dir
  74. f.mockServers = NewMockServers(f.portAllocator)
  75. if err := f.mockServers.Run(); err != nil {
  76. Failf("%v", err)
  77. }
  78. params := f.mockServers.GetTemplateParams()
  79. for k, v := range params {
  80. switch t := v.(type) {
  81. case int:
  82. f.usedPorts[k] = t
  83. default:
  84. }
  85. }
  86. }
  87. func (f *Framework) AfterEach() {
  88. if !f.beforeEachStarted {
  89. return
  90. }
  91. RemoveCleanupAction(f.cleanupHandle)
  92. // stop processor
  93. for _, p := range f.serverProcesses {
  94. _ = p.Stop()
  95. if TestContext.Debug || ginkgo.CurrentSpecReport().Failed() {
  96. fmt.Println(p.ErrorOutput())
  97. fmt.Println(p.StdOutput())
  98. }
  99. }
  100. for _, p := range f.clientProcesses {
  101. _ = p.Stop()
  102. if TestContext.Debug || ginkgo.CurrentSpecReport().Failed() {
  103. fmt.Println(p.ErrorOutput())
  104. fmt.Println(p.StdOutput())
  105. }
  106. }
  107. f.serverProcesses = nil
  108. f.clientProcesses = nil
  109. // close default mock servers
  110. f.mockServers.Close()
  111. // close manual registered mock servers
  112. for _, s := range f.servers {
  113. s.Close()
  114. }
  115. // clean directory
  116. os.RemoveAll(f.TempDirectory)
  117. f.TempDirectory = ""
  118. f.serverConfPaths = []string{}
  119. f.clientConfPaths = []string{}
  120. // release used ports
  121. for _, port := range f.usedPorts {
  122. f.portAllocator.Release(port)
  123. }
  124. f.usedPorts = make(map[string]int)
  125. // release allocated ports
  126. for _, port := range f.allocatedPorts {
  127. f.portAllocator.Release(port)
  128. }
  129. f.allocatedPorts = make([]int, 0)
  130. // clear os envs
  131. f.osEnvs = make([]string, 0)
  132. }
  133. var portRegex = regexp.MustCompile(`{{ \.Port.*? }}`)
  134. // RenderPortsTemplate render templates with ports.
  135. //
  136. // Local: {{ .Port1 }}
  137. // Target: {{ .Port2 }}
  138. //
  139. // return rendered content and all allocated ports.
  140. func (f *Framework) genPortsFromTemplates(templates []string) (ports map[string]int, err error) {
  141. ports = make(map[string]int)
  142. for _, t := range templates {
  143. arrs := portRegex.FindAllString(t, -1)
  144. for _, str := range arrs {
  145. str = strings.TrimPrefix(str, "{{ .")
  146. str = strings.TrimSuffix(str, " }}")
  147. str = strings.TrimSpace(str)
  148. ports[str] = 0
  149. }
  150. }
  151. defer func() {
  152. if err != nil {
  153. for _, port := range ports {
  154. f.portAllocator.Release(port)
  155. }
  156. }
  157. }()
  158. for name := range ports {
  159. port := f.portAllocator.GetByName(name)
  160. if port <= 0 {
  161. return nil, fmt.Errorf("can't allocate port")
  162. }
  163. ports[name] = port
  164. }
  165. return
  166. }
  167. // RenderTemplates alloc all ports for port names placeholder.
  168. func (f *Framework) RenderTemplates(templates []string) (outs []string, ports map[string]int, err error) {
  169. ports, err = f.genPortsFromTemplates(templates)
  170. if err != nil {
  171. return
  172. }
  173. params := f.mockServers.GetTemplateParams()
  174. for name, port := range ports {
  175. params[name] = port
  176. }
  177. for name, port := range f.usedPorts {
  178. params[name] = port
  179. }
  180. for _, t := range templates {
  181. tmpl, err := template.New("frp-e2e").Parse(t)
  182. if err != nil {
  183. return nil, nil, err
  184. }
  185. buffer := bytes.NewBuffer(nil)
  186. if err = tmpl.Execute(buffer, params); err != nil {
  187. return nil, nil, err
  188. }
  189. outs = append(outs, buffer.String())
  190. }
  191. return
  192. }
  193. func (f *Framework) PortByName(name string) int {
  194. return f.usedPorts[name]
  195. }
  196. func (f *Framework) AllocPort() int {
  197. port := f.portAllocator.Get()
  198. ExpectTrue(port > 0, "alloc port failed")
  199. f.allocatedPorts = append(f.allocatedPorts, port)
  200. return port
  201. }
  202. func (f *Framework) ReleasePort(port int) {
  203. f.portAllocator.Release(port)
  204. }
  205. func (f *Framework) RunServer(portName string, s server.Server) {
  206. f.servers = append(f.servers, s)
  207. if s.BindPort() > 0 && portName != "" {
  208. f.usedPorts[portName] = s.BindPort()
  209. }
  210. err := s.Run()
  211. ExpectNoError(err, "RunServer: with PortName %s", portName)
  212. }
  213. func (f *Framework) SetEnvs(envs []string) {
  214. f.osEnvs = envs
  215. }
  216. func (f *Framework) WriteTempFile(name string, content string) string {
  217. filePath := filepath.Join(f.TempDirectory, name)
  218. err := os.WriteFile(filePath, []byte(content), 0o600)
  219. ExpectNoError(err)
  220. return filePath
  221. }