github.com/vmware/transport-go@v1.3.4/plank/pkg/server/test_suite_harness.go (about)

     1  // Copyright 2021 VMware, Inc.
     2  // SPDX-License-Identifier: BSD-2-Clause
     3  
     4  package server
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"github.com/sirupsen/logrus"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/suite"
    12  	"github.com/vmware/transport-go/bus"
    13  	"github.com/vmware/transport-go/model"
    14  	"github.com/vmware/transport-go/plank/utils"
    15  	svc "github.com/vmware/transport-go/service"
    16  	"io/ioutil"
    17  	"net"
    18  	"os"
    19  	"path/filepath"
    20  	"testing"
    21  	"time"
    22  )
    23  
    24  var testSuitePortMap = make(map[string]int)
    25  
    26  // PlankIntegrationTestSuite makes it easy to set up integration tests for services, that use plank and transport
    27  // In a realistic manner. This is a convenience mechanism to avoid having to rig this harnessing up yourself.
    28  type PlankIntegrationTestSuite struct {
    29  
    30  	// suite.Suite is a reference to a testify test suite.
    31  	suite.Suite
    32  
    33  	// server.PlatformServer is a reference to plank
    34  	PlatformServer
    35  
    36  	// os.Signal is the signal channel passed into plan for OS level notifications.
    37  	Syschan chan os.Signal
    38  
    39  	// bus.ChannelManager is a reference to the Transport's Channel Manager.
    40  	bus.ChannelManager
    41  
    42  	// bus.EventBus is a reference to Transport.
    43  	bus.EventBus
    44  }
    45  
    46  // PlankIntegrationTest allows test suites that use PlankIntegrationTestSuite as an embedded struct to set everything
    47  // we need on our test. This is the only contract required to use this harness.
    48  type PlankIntegrationTest interface {
    49  	SetPlatformServer(PlatformServer)
    50  	SetSysChan(chan os.Signal)
    51  	SetChannelManager(bus.ChannelManager)
    52  	SetBus(eventBus bus.EventBus)
    53  }
    54  
    55  // SetupPlankTestSuiteForTest will copy over everything from the newly set up test suite, to a suite being run.
    56  func SetupPlankTestSuiteForTest(suite *PlankIntegrationTestSuite, test PlankIntegrationTest) {
    57  	test.SetPlatformServer(suite.PlatformServer)
    58  	test.SetSysChan(suite.Syschan)
    59  	test.SetChannelManager(suite.ChannelManager)
    60  	test.SetBus(suite.EventBus)
    61  }
    62  
    63  // GetBasicTestServerConfig will generate a simple platform server config, ready to use in a test.
    64  func GetBasicTestServerConfig(rootDir, outLog, accessLog, errLog string, port int, noBanner bool) *PlatformServerConfig {
    65  	cfg := &PlatformServerConfig{
    66  		RootDir:           rootDir,
    67  		Host:              "localhost",
    68  		Port:              port,
    69  		RestBridgeTimeout: time.Minute,
    70  		LogConfig: &utils.LogConfig{
    71  			OutputLog:     outLog,
    72  			AccessLog:     accessLog,
    73  			ErrorLog:      errLog,
    74  			FormatOptions: &utils.LogFormatOption{},
    75  		},
    76  		NoBanner:        noBanner,
    77  		ShutdownTimeout: time.Minute,
    78  	}
    79  	return cfg
    80  }
    81  
    82  // SetupPlankTestSuite will boot a new instance of plank on your chosen port and will also fire up your service
    83  // Ready to be tested. This always runs on localhost.
    84  func SetupPlankTestSuite(service svc.FabricService, serviceChannel string, port int,
    85  	config *PlatformServerConfig) (*PlankIntegrationTestSuite, error) {
    86  
    87  	s := &PlankIntegrationTestSuite{}
    88  
    89  	customFormatter := new(logrus.TextFormatter)
    90  	utils.Log.SetFormatter(customFormatter)
    91  	customFormatter.DisableTimestamp = true
    92  
    93  	// check if config has been supplied, if not, generate default one.
    94  	if config == nil {
    95  		config = GetBasicTestServerConfig("/", "stdout", "stdout", "stderr", port, true)
    96  	}
    97  
    98  	s.PlatformServer = NewPlatformServer(config)
    99  	if err := s.PlatformServer.RegisterService(service, serviceChannel); err != nil {
   100  		return nil, errors.New("cannot create printing press service, test failed")
   101  	}
   102  
   103  	s.Syschan = make(chan os.Signal, 1)
   104  	go s.PlatformServer.StartServer(s.Syschan)
   105  
   106  	s.EventBus = bus.ResetBus()
   107  	svc.ResetServiceRegistry()
   108  
   109  	// get a pointer to the channel manager
   110  	s.ChannelManager = s.EventBus.GetChannelManager()
   111  
   112  	// wait, service may be slow loading, rest mapping happens last.
   113  	wait := make(chan bool)
   114  	go func() {
   115  		time.Sleep(10 * time.Millisecond)
   116  		wait <- true // Sending something to the channel to let the main thread continue
   117  
   118  	}()
   119  
   120  	<-wait
   121  	return s, nil
   122  }
   123  
   124  // CreateTestServer consumes *server.PlatformServerConfig and returns a new instance of PlatformServer along with
   125  // its base URL and log file name
   126  func CreateTestServer(config *PlatformServerConfig) (baseUrl, logFile string, s PlatformServer) {
   127  	testServer := NewPlatformServer(config)
   128  
   129  	protocol := "http"
   130  	if config.TLSCertConfig != nil {
   131  		protocol += "s"
   132  	}
   133  
   134  	baseUrl = fmt.Sprintf("%s://%s:%d", protocol, config.Host, config.Port)
   135  	return baseUrl, config.LogConfig.OutputLog, testServer
   136  }
   137  
   138  // RunWhenServerReady runs test function fn after Plank has booted up
   139  func RunWhenServerReady(t *testing.T, eventBus bus.EventBus, fn func(*testing.T)) {
   140  	handler, _ := eventBus.ListenOnce(PLANK_SERVER_ONLINE_CHANNEL)
   141  	handler.Handle(func(message *model.Message) {
   142  		fn(t)
   143  	}, func(err error) {
   144  		assert.FailNow(t, err.Error())
   145  	})
   146  }
   147  
   148  // GetTestPort returns an available port for use by Plank tests
   149  func GetTestPort() int {
   150  	minPort := 9980
   151  	fr := utils.GetCallerStackFrame()
   152  	port, exists := testSuitePortMap[fr.File]
   153  	if exists {
   154  		return port
   155  	}
   156  
   157  	// try 5 more times with every failure advancing port by one
   158  	tryPort := minPort
   159  	for i := 0; i <= 4; i++ {
   160  		tryPort = minPort + len(testSuitePortMap) + i
   161  		_, err := net.Dial("tcp", fmt.Sprintf(":%d", tryPort))
   162  		if err == nil { // port in use, try next one
   163  			continue
   164  		}
   165  
   166  		testSuitePortMap[fr.File] = tryPort
   167  		break
   168  	}
   169  
   170  	if testSuitePortMap[fr.File] == 0 { // port could not be assigned
   171  		panic(fmt.Errorf("could not assign a port for tests. last tried port is %d", tryPort))
   172  	}
   173  
   174  	return testSuitePortMap[fr.File]
   175  }
   176  
   177  // GetTestTLSCertConfig returns a new &TLSCertConfig for testing.
   178  func GetTestTLSCertConfig(testRootPath string) *TLSCertConfig {
   179  	crtFile := filepath.Join(testRootPath, "test_server.crt")
   180  	keyFile := filepath.Join(testRootPath, "test_server.key")
   181  	_ = ioutil.WriteFile(crtFile, []byte(testServerCertTmpl), 0700)
   182  	_ = ioutil.WriteFile(keyFile, []byte(testServerKeyTmpl), 0700)
   183  	return &TLSCertConfig{
   184  		CertFile:                  crtFile,
   185  		KeyFile:                   keyFile,
   186  		SkipCertificateValidation: true,
   187  	}
   188  }
   189  
   190  // GetTestFabricBrokerConfig returns a basic fabric broker config.
   191  func GetTestFabricBrokerConfig() *FabricBrokerConfig {
   192  	return &FabricBrokerConfig{
   193  		FabricEndpoint: "/ws",
   194  		UseTCP:         false,
   195  		TCPPort:        61613,
   196  		EndpointConfig: &bus.EndpointConfig{
   197  			TopicPrefix:           "/topic",
   198  			UserQueuePrefix:       "/queue",
   199  			AppRequestPrefix:      "/pub",
   200  			AppRequestQueuePrefix: "/pub/queue",
   201  			Heartbeat:             30000,
   202  		},
   203  	}
   204  }
   205  
   206  // CreateConfigJsonForTest creates and returns the path to a file containing the plank configuration in JSON format
   207  func CreateConfigJsonForTest() (string, error) {
   208  	configJsonContent := `{
   209    "debug": true,
   210    "no_banner": true,
   211    "root_dir": "./",
   212    "host": "localhost",
   213    "port": 31234,
   214    "log_config": {
   215      "root": ".",
   216      "access_log": "access.log",
   217      "error_log": "errors.log",
   218      "output_log": "stdout",
   219      "format_options": {
   220        "force_colors": false,
   221        "disable_colors": true,
   222        "force_quote": false,
   223        "disable_quote": false,
   224        "environment_override_colors": false,
   225        "disable_timestamp": false,
   226        "full_timestamp": true,
   227        "timestamp_format": "",
   228        "disable_sorting": false,
   229        "disable_level_truncation": false,
   230        "pad_level_text": false,
   231        "quote_empty_fields": false,
   232        "is_terminal": true
   233      }
   234    },
   235    "spa_config": {
   236      "root_folder": "public/",
   237      "base_uri": "/",
   238      "static_assets": [
   239        "public/assets:/assets"
   240      ],
   241      "cache_control_rules": {
   242        "*.{js,css}": "public, max-age=86400",
   243        "*.{ico,jpg,jpeg,svg,png,gif,tiff}": "public, max-age=604800"
   244      }
   245    },
   246    "shutdown_timeout_in_minutes": -1,
   247    "rest_bridge_timeout_in_minutes": 0,
   248    "fabric_config": {
   249      "fabric_endpoint": "/ws",
   250      "endpoint_config": {
   251        "TopicPrefix": "/topic",
   252        "UserQueuePrefix": "/queue",
   253        "AppRequestPrefix": "/pub",
   254        "AppRequestQueuePrefix": "/pub/queue",
   255        "Heartbeat": 60000
   256      }
   257    },
   258    "enable_prometheus": true,
   259    "tls_config": {
   260      "cert_file": "cert/fullchain.pem",
   261      "key_file": "cert/server.key"
   262    }
   263  }`
   264  	testDir := filepath.Join(os.TempDir(), "plank-tests")
   265  	testFile := filepath.Join(testDir, "test-config.json")
   266  	err := os.MkdirAll(testDir, 0744)
   267  	if err != nil {
   268  		return "", err
   269  	}
   270  	err = ioutil.WriteFile(testFile, []byte(configJsonContent), 0744)
   271  	if err != nil {
   272  		return "", err
   273  	}
   274  	return testFile, nil
   275  }