github.com/altipla-consulting/ravendb-go-client@v0.1.3/tests/raven_test_driver_test.go (about)

     1  package tests
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"crypto/tls"
     7  	"crypto/x509"
     8  	"encoding/pem"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"math/rand"
    13  	"net/http"
    14  	"net/url"
    15  	"os"
    16  	"os/exec"
    17  	"path/filepath"
    18  	"runtime/debug"
    19  	"runtime/pprof"
    20  	"strconv"
    21  	"strings"
    22  	"sync"
    23  	"sync/atomic"
    24  	"testing"
    25  	"time"
    26  
    27  	ravendb "github.com/altipla-consulting/ravendb-go-client"
    28  	"github.com/stretchr/testify/assert"
    29  )
    30  
    31  var (
    32  	// if 1 - no cluster
    33  	// should be 3, 5, 7 etc.
    34  	// can be changed via NODES_IN_CLUSTER env variable
    35  	numClusterNodes = 1
    36  
    37  	// a value in range 0..100
    38  	// it's a percentage chance that we'll kill the server
    39  	// when getting a store for a sub-test
    40  	// 0 means killing is disabled, 5 means it's a 5% chance
    41  	// 100 or more means it's certain
    42  	// can be changed via KILL_SERVER_CHANCE env variable
    43  	randomlyKillServersChance = 0
    44  
    45  	// can be changed via SHUFFLE_CLUSTER_NODES=true env variable
    46  	shuffleClusterNodes = false
    47  
    48  	// ravendbWindowsDownloadURL = "https://daily-builds.s3.amazonaws.com/RavenDB-4.1.3-windows-x64.zip"
    49  	ravendbWindowsDownloadURL = "https://hibernatingrhinos.com/downloads/RavenDB%20for%20Windows%20x64/latest?buildType=nightly&version=4.1"
    50  
    51  	ravenWindowsZipPath = "ravendb-latest.zip"
    52  )
    53  
    54  type ravenProcess struct {
    55  	cmd          *exec.Cmd
    56  	pid          int
    57  	stdoutReader io.ReadCloser
    58  
    59  	// auto-detected url on which to contact the server
    60  	uri string
    61  }
    62  
    63  // Note: Java's RemoteTestBase is folded into RavenTestDriver
    64  type RavenTestDriver struct {
    65  	documentStores sync.Map // *DocumentStore => bool
    66  
    67  	dbNameCounter int32 // atomic
    68  
    69  	store           *ravendb.DocumentStore
    70  	serverProcesses []*ravenProcess
    71  
    72  	isSecure bool
    73  
    74  	customize func(*ravendb.DatabaseRecord)
    75  
    76  	profData    bytes.Buffer
    77  	isProfiling bool
    78  }
    79  
    80  var (
    81  	// if true, enables flaky tests
    82  	// can be enabled by setting ENABLE_FLAKY_TESTS env variable to "true"
    83  	enableFlakyTests = false
    84  
    85  	// if true, enable failing tests
    86  	// can be enabled by setting ENABLE_FAILING_TESTS env variable to "true"
    87  	enableFailingTests   = false
    88  	testsWereInitialized bool
    89  	muInitializeTests    sync.Mutex
    90  
    91  	ravendbServerExePath string
    92  
    93  	// passed to the server as --Security.Certificate.Path
    94  	certificatePath string
    95  
    96  	caCertificate     *x509.Certificate
    97  	clientCertificate *tls.Certificate
    98  
    99  	httpsServerURL string
   100  
   101  	tcpServerPort int32 = 38880 // atomic
   102  )
   103  
   104  func must(err error) {
   105  	if err != nil {
   106  		panic(err.Error())
   107  	}
   108  }
   109  
   110  func panicIf(cond bool, format string, args ...interface{}) {
   111  	if cond {
   112  		err := fmt.Errorf(format, args...)
   113  		must(err)
   114  	}
   115  }
   116  
   117  var (
   118  	balanceBehaviors = []ravendb.ReadBalanceBehavior{
   119  		// ravendb.ReadBalanceBehaviorNone,
   120  		ravendb.ReadBalanceBehaviorRoundRobin,
   121  		ravendb.ReadBalanceBehaviorFastestNode,
   122  	}
   123  )
   124  
   125  func pickRandomBalanceBehavior() ravendb.ReadBalanceBehavior {
   126  	n := rand.Intn(len(balanceBehaviors))
   127  	return balanceBehaviors[n]
   128  }
   129  
   130  func killServer(proc *ravenProcess) {
   131  	if proc.cmd.ProcessState != nil && proc.cmd.ProcessState.Exited() {
   132  		fmt.Printf("RavenDB process has already exited with '%s'\n", proc.cmd.ProcessState)
   133  	}
   134  	err := proc.cmd.Process.Kill()
   135  	if err != nil {
   136  		fmt.Printf("cmd.Process.Kill() failed with '%s'\n", err)
   137  	} else {
   138  		s := strings.Join(proc.cmd.Args, " ")
   139  		fmt.Printf("Killed RavenDB process %d '%s' on '%s'\n", proc.pid, s, proc.uri)
   140  	}
   141  }
   142  
   143  func getNextTcpPort() int {
   144  	n := atomic.AddInt32(&tcpServerPort, 1)
   145  	return int(n)
   146  }
   147  
   148  func startRavenServer(secure bool) (*ravenProcess, error) {
   149  	serverURL := "http://127.0.0.1:0"
   150  	// we run potentially multiple server so need to make the port unique
   151  	tcpServerURL := fmt.Sprintf("tcp://127.0.0.1:%d", getNextTcpPort())
   152  
   153  	if secure {
   154  		serverURL = httpsServerURL
   155  		parsed, err := url.Parse(httpsServerURL)
   156  		must(err)
   157  		host := parsed.Host
   158  		parts := strings.Split(host, ":")
   159  		panicIf(len(parts) > 2, "invalid https URL '%s'", httpsServerURL)
   160  		// host can be name:port, extract "name" part
   161  		host = parts[0]
   162  		tcpServerURL = "tcp://" + host + ":38882"
   163  	}
   164  
   165  	args := []string{
   166  		"--ServerUrl=" + serverURL,
   167  		"--ServerUrl.Tcp=" + tcpServerURL,
   168  		"--RunInMemory=true",
   169  		"--License.Eula.Accepted=true",
   170  		"--Setup.Mode=None",
   171  		"--Testing.ParentProcessId=" + getProcessId(),
   172  		// "--non-interactive",
   173  	}
   174  
   175  	if secure {
   176  		secureArgs := []string{
   177  			"--Security.Certificate.Path=" + certificatePath,
   178  			"--Security.Certificate.Password=pwd1234",
   179  		}
   180  		args = append(args, secureArgs...)
   181  	}
   182  
   183  	cmd := exec.Command(ravendbServerExePath, args...)
   184  	stdoutReader, err := cmd.StdoutPipe()
   185  
   186  	if false && ravenServerVerbose {
   187  		cmd.Stderr = os.Stderr
   188  		// cmd.StdoutPipe() sets cmd.Stdout to a pipe writer
   189  		// we multi-plex it into os.Stdout
   190  		// TODO: this doesn't seem to work. It makes reading from stdoutReader
   191  		// immediately fail. Maybe it's becuse writer returned by
   192  		// os.Pipe() (cmd.Stdout) blocks and MultiWriter() doesn't
   193  		cmd.Stdout = io.MultiWriter(cmd.Stdout, os.Stdout)
   194  	}
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	err = cmd.Start()
   199  	if err != nil {
   200  		fmt.Printf("exec.Command(%s, %v) failed with %s\n", ravendbServerExePath, args, err)
   201  		return nil, err
   202  	}
   203  
   204  	proc := &ravenProcess{
   205  		cmd:          cmd,
   206  		stdoutReader: stdoutReader,
   207  		pid:          cmd.Process.Pid,
   208  	}
   209  
   210  	// parse stdout of the server to extract server listening port from line:
   211  	// Server available on: http://127.0.0.1:50386
   212  	wantedPrefix := "Server available on: "
   213  	scanner := bufio.NewScanner(stdoutReader)
   214  	startTime := time.Now()
   215  	var outputCopy bytes.Buffer
   216  	for scanner.Scan() {
   217  		dur := time.Since(startTime)
   218  		if dur > time.Minute {
   219  			break
   220  		}
   221  		s := scanner.Text()
   222  		if ravenServerVerbose {
   223  			fmt.Printf("server: %s\n", s)
   224  		}
   225  		outputCopy.WriteString(s + "\n")
   226  		if !strings.HasPrefix(s, wantedPrefix) {
   227  			continue
   228  		}
   229  		s = strings.TrimPrefix(s, wantedPrefix)
   230  		proc.uri = strings.TrimSpace(s)
   231  		break
   232  	}
   233  	if scanner.Err() != nil {
   234  		fmt.Printf("startRavenServer: scanner.Err() returned '%s'\n", err)
   235  		killServer(proc)
   236  		return nil, scanner.Err()
   237  	}
   238  	if proc.uri == "" {
   239  		s := string(outputCopy.Bytes())
   240  		fmt.Printf("startRavenServer: couldn't detect start url. Server output: %s\n", s)
   241  		killServer(proc)
   242  		return nil, fmt.Errorf("Unable to start server")
   243  	}
   244  	if ravenServerVerbose {
   245  		go func() {
   246  			_, err = io.Copy(os.Stdout, stdoutReader)
   247  			if !(err == nil || err == io.EOF) {
   248  				fmt.Printf("io.Copy() failed with %s\n", err)
   249  			}
   250  		}()
   251  	}
   252  
   253  	time.Sleep(time.Millisecond * 100) // TODO: probably not necessary
   254  
   255  	return proc, nil
   256  }
   257  
   258  func runServersMust(n int, secure bool) []*ravenProcess {
   259  	if secure {
   260  		n = 1
   261  	}
   262  	var procs []*ravenProcess
   263  	for i := 0; i < n; i++ {
   264  		proc, err := startRavenServer(secure)
   265  		must(err)
   266  		args := strings.Join(proc.cmd.Args, " ")
   267  		fmt.Printf("Started server '%s' on port '%s' pid: %d\n", args, proc.uri, proc.pid)
   268  		procs = append(procs, proc)
   269  	}
   270  	return procs
   271  }
   272  
   273  func setupRevisions(store *ravendb.DocumentStore, purgeOnDelete bool, minimumRevisionsToKeep int64) (*ravendb.ConfigureRevisionsOperationResult, error) {
   274  
   275  	revisionsConfiguration := &ravendb.RevisionsConfiguration{}
   276  	defaultCollection := &ravendb.RevisionsCollectionConfiguration{}
   277  	defaultCollection.PurgeOnDelete = purgeOnDelete
   278  	defaultCollection.MinimumRevisionsToKeep = minimumRevisionsToKeep
   279  
   280  	revisionsConfiguration.DefaultConfig = defaultCollection
   281  	operation := ravendb.NewConfigureRevisionsOperation(revisionsConfiguration)
   282  
   283  	err := store.Maintenance().Send(operation)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  
   288  	return operation.Command.Result, nil
   289  }
   290  
   291  func (d *RavenTestDriver) customizeDbRecord(dbRecord *ravendb.DatabaseRecord) {
   292  	if d.customize != nil {
   293  		d.customize(dbRecord)
   294  	}
   295  }
   296  
   297  func (d *RavenTestDriver) maybeKillServer() bool {
   298  	if len(d.serverProcesses) < numClusterNodes || len(d.serverProcesses) < 2 {
   299  		return false
   300  	}
   301  	// randomly kill a server
   302  	n := rand.Intn(100)
   303  	if n >= randomlyKillServersChance {
   304  		return false
   305  	}
   306  	// don't kill the first server as it's used by main store to create
   307  	// databases / store for other commands
   308  	idx := 1
   309  	proc := d.serverProcesses[idx]
   310  	d.serverProcesses = append(d.serverProcesses[:idx], d.serverProcesses[idx+1:]...)
   311  	fmt.Printf("Randomly killing a server with pid %d\n", proc.pid)
   312  	killServer(proc)
   313  	return true
   314  }
   315  
   316  func (d *RavenTestDriver) getDocumentStore2(dbName string, waitForIndexingTimeout time.Duration) (*ravendb.DocumentStore, error) {
   317  
   318  	var err error
   319  
   320  	// we're called for each sub-test
   321  	if d.store == nil {
   322  		d.store, err = d.createMainStore()
   323  		if err != nil {
   324  			return nil, err
   325  		}
   326  	} else {
   327  		d.maybeKillServer()
   328  	}
   329  
   330  	n := int(atomic.AddInt32(&d.dbNameCounter, 1))
   331  	name := fmt.Sprintf("%s_%d", dbName, n)
   332  	databaseRecord := ravendb.NewDatabaseRecord()
   333  	databaseRecord.DatabaseName = name
   334  	d.customizeDbRecord(databaseRecord)
   335  
   336  	// replicationFactor seems to be a minimum number of nodes with the data
   337  	// so it must be less than 3 (we have 3 nodes and might kill one, leaving
   338  	// only 2)
   339  	createDatabaseOperation := ravendb.NewCreateDatabaseOperation(databaseRecord, 1)
   340  	err = d.store.Maintenance().Server().Send(createDatabaseOperation)
   341  	if err != nil {
   342  		fmt.Printf("d.store.Maintenance().Server().Send(createDatabaseOperation) failed with %s\n", err)
   343  		return nil, err
   344  	}
   345  
   346  	uris := d.store.GetUrls()
   347  	var store *ravendb.DocumentStore
   348  	if shuffleClusterNodes {
   349  		// randomly shuffle urls so that if we kill a server, there's a higher
   350  		// chance we'll hit it
   351  		var shuffledURIs []string
   352  		r := rand.New(rand.NewSource(time.Now().Unix()))
   353  		for _, i := range r.Perm(len(uris)) {
   354  			shuffledURIs = append(shuffledURIs, uris[i])
   355  		}
   356  		store = ravendb.NewDocumentStore(shuffledURIs, name)
   357  	} else {
   358  		store = ravendb.NewDocumentStore(uris, name)
   359  	}
   360  	conventions := store.GetConventions()
   361  	conventions.ReadBalanceBehavior = pickRandomBalanceBehavior()
   362  	store.SetConventions(conventions)
   363  
   364  	if d.isSecure {
   365  		store.Certificate = clientCertificate
   366  		store.TrustStore = caCertificate
   367  	}
   368  
   369  	// TODO: is over-written by CustomSerializationTest
   370  	// customizeStore(Store);
   371  	d.hookLeakedConnectionCheck(store)
   372  
   373  	d.setupDatabase(store)
   374  	err = store.Initialize()
   375  	if err != nil {
   376  		fmt.Printf("getDocumentStore2: store.Initialize() failed with '%s'\n", err)
   377  		return nil, err
   378  	}
   379  
   380  	fn := func(store *ravendb.DocumentStore) {
   381  		_, ok := d.documentStores.Load(store)
   382  		if !ok {
   383  			// TODO: shouldn't happen?
   384  			return
   385  		}
   386  
   387  		operation := ravendb.NewDeleteDatabasesOperation(store.GetDatabase(), true)
   388  		err = store.Maintenance().Server().Send(operation)
   389  	}
   390  
   391  	store.AddAfterCloseListener(fn)
   392  
   393  	if waitForIndexingTimeout > 0 {
   394  		err = d.waitForIndexing(store, name, waitForIndexingTimeout)
   395  		if err != nil {
   396  			fmt.Printf("getDocumentStore2:  d.waitForIndexing() failed with '%s'\n", err)
   397  			store.Close()
   398  			return nil, err
   399  		}
   400  	}
   401  
   402  	d.documentStores.Store(store, true)
   403  	d.maybeStartProfiling()
   404  
   405  	return store, nil
   406  }
   407  
   408  func (d *RavenTestDriver) hookLeakedConnectionCheck(store *ravendb.DocumentStore) {
   409  	// TODO: no-op for now. Not sure if I have enough info
   410  	// to replicate this functionality in Go
   411  }
   412  
   413  // Note: it's virtual in Java but there's only one implementation
   414  // that is a no-op
   415  func (d *RavenTestDriver) setupDatabase(documentStore *ravendb.DocumentStore) {
   416  	// empty by design
   417  }
   418  
   419  func setupCluster(store *ravendb.DocumentStore) error {
   420  	uris := store.GetUrls()
   421  	if len(uris) < 2 {
   422  		return nil
   423  	}
   424  
   425  	re := store.GetRequestExecutor(store.GetDatabase())
   426  	httpClient, err := re.GetHTTPClient()
   427  	if err != nil {
   428  		fmt.Printf("setupCluster: re.GetHTTPClient() failed with '%s'\n", err)
   429  		return err
   430  	}
   431  	firstServerURL := uris[0]
   432  	for _, uri := range uris[1:] {
   433  		// https://ravendb.net/docs/article-page/4.1/csharp/server/clustering/cluster-api#delete-node-from-the-cluster
   434  		cmdURI := firstServerURL + "/admin/cluster/node?url=" + url.QueryEscape(uri)
   435  		req, err := newHttpPut(cmdURI, nil)
   436  		if err != nil {
   437  			fmt.Printf("setupCluster: newHttpPutt() failed with '%s'\n", err)
   438  		}
   439  		rsp, err := httpClient.Do(req)
   440  		if err != nil {
   441  			fmt.Printf("setupCluster: httpClient.Do() failed with '%s' for url '%s'\n", err, cmdURI)
   442  		}
   443  		defer rsp.Body.Close()
   444  		if rsp.StatusCode >= 400 {
   445  			fmt.Printf("setupCluster: httpClient.Do() returned status code '%s' for url '%s'\n", rsp.Status, cmdURI)
   446  			return fmt.Errorf("setupCluster: httpClient.Do() returned status code '%s' for url '%s'\n", rsp.Status, cmdURI)
   447  		}
   448  
   449  		fmt.Printf("Added node to cluster with '%s', status code: %d\n", cmdURI, rsp.StatusCode)
   450  	}
   451  	return nil
   452  }
   453  
   454  func newHttpPut(uri string, data []byte) (*http.Request, error) {
   455  	var body io.Reader
   456  	if len(data) > 0 {
   457  		body = bytes.NewReader(data)
   458  	}
   459  	req, err := http.NewRequest(http.MethodPut, uri, body)
   460  	if err != nil {
   461  		return nil, err
   462  	}
   463  	req.Header.Add("User-Agent", "ravendb-go-client/4.0.0")
   464  	if len(data) > 0 {
   465  		req.Header.Add("Content-Type", "application/json; charset=UTF-8")
   466  	}
   467  	return req, nil
   468  }
   469  
   470  func (d *RavenTestDriver) createMainStore() (*ravendb.DocumentStore, error) {
   471  	panicIf(len(d.serverProcesses) > 0, "len(d.serverProcesses): %d", len(d.serverProcesses))
   472  
   473  	d.serverProcesses = runServersMust(numClusterNodes, d.isSecure)
   474  
   475  	var uris []string
   476  	for _, proc := range d.serverProcesses {
   477  		uris = append(uris, proc.uri)
   478  	}
   479  
   480  	mainStoreURLS := uris
   481  	if len(mainStoreURLS) > 1 {
   482  		mainStoreURLS = mainStoreURLS[1:]
   483  	}
   484  	store := ravendb.NewDocumentStore(uris, "test.manager")
   485  
   486  	conventions := store.GetConventions()
   487  	// main store is only used to create databases / other stores
   488  	// so we don't want cluster behavior
   489  	conventions.SetDisableTopologyUpdates(true)
   490  	conventions.ReadBalanceBehavior = pickRandomBalanceBehavior()
   491  
   492  	if d.isSecure {
   493  		store.Certificate = clientCertificate
   494  		store.TrustStore = caCertificate
   495  	}
   496  	err := store.Initialize()
   497  	if err != nil {
   498  		fmt.Printf("createMainStore: store.Initialize() failed with '%s'\n", err)
   499  		store.Close()
   500  		return nil, err
   501  	}
   502  	err = setupCluster(store)
   503  	if err != nil {
   504  		store.Close()
   505  		return nil, err
   506  	}
   507  	return store, nil
   508  }
   509  
   510  func (d *RavenTestDriver) waitForIndexing(store *ravendb.DocumentStore, database string, timeout time.Duration) error {
   511  	return waitForIndexing(store, database, timeout)
   512  }
   513  
   514  func waitForIndexing(store *ravendb.DocumentStore, database string, timeout time.Duration) error {
   515  	admin := store.Maintenance().ForDatabase(database)
   516  	if timeout == 0 {
   517  		timeout = time.Minute
   518  	}
   519  
   520  	sp := time.Now()
   521  	for time.Since(sp) < timeout {
   522  		op := ravendb.NewGetStatisticsOperation("")
   523  		err := admin.Send(op)
   524  		if err != nil {
   525  			return err
   526  		}
   527  		databaseStatistics := op.Command.Result
   528  		isDone := true
   529  		hasError := false
   530  		for _, index := range databaseStatistics.Indexes {
   531  			if index.State == ravendb.IndexStateDisabled {
   532  				continue
   533  			}
   534  			if index.IsStale || strings.HasPrefix(index.Name, ravendb.IndexingSideBySideIndexNamePrefix) {
   535  				isDone = false
   536  			}
   537  			if index.State == ravendb.IndexStateError {
   538  				hasError = true
   539  			}
   540  		}
   541  		if isDone {
   542  			return nil
   543  		}
   544  		if hasError {
   545  			break
   546  		}
   547  		time.Sleep(time.Millisecond * 100)
   548  	}
   549  
   550  	op := ravendb.NewGetIndexErrorsOperation(nil)
   551  	err := admin.Send(op)
   552  	if err != nil {
   553  		return err
   554  	}
   555  	allIndexErrorsText := ""
   556  	/*
   557  		// TODO: port this
   558  		Function<IndexErrors, String> formatIndexErrors = indexErrors -> {
   559  				String errorsListText = Arrays.stream(indexErrors.getErrors()).map(x -> "-" + x).collect(Collectors.joining(System.lineSeparator()));
   560  				return "Index " + indexErrors.GetName() + " (" + indexErrors.getErrors().length + " errors): "+ System.lineSeparator() + errorsListText;
   561  			};
   562  
   563  			if (errors != null && errors.length > 0) {
   564  				allIndexErrorsText = Arrays.stream(errors).map(x -> formatIndexErrors.apply(x)).collect(Collectors.joining(System.lineSeparator()));
   565  			}
   566  	*/
   567  	return ravendb.NewTimeoutError("The indexes stayed stale for more than %s.%s", timeout, allIndexErrorsText)
   568  }
   569  
   570  func (d *RavenTestDriver) killServerProcesses() {
   571  	for _, proc := range d.serverProcesses {
   572  		killServer(proc)
   573  	}
   574  	d.serverProcesses = nil
   575  
   576  	d.store = nil
   577  }
   578  
   579  func (d *RavenTestDriver) getDocumentStoreMust(t *testing.T) *ravendb.DocumentStore {
   580  	d.isSecure = false
   581  	store, err := d.getDocumentStore2("test_db", 0)
   582  	assert.NoError(t, err)
   583  	assert.NotNil(t, store)
   584  	return store
   585  }
   586  
   587  func (d *RavenTestDriver) getSecuredDocumentStoreMust(t *testing.T) *ravendb.DocumentStore {
   588  	d.isSecure = true
   589  	store, err := d.getDocumentStore2("test_db", 0)
   590  	assert.NoError(t, err)
   591  	assert.NotNil(t, store)
   592  	return store
   593  }
   594  
   595  func (d *RavenTestDriver) Close() {
   596  	// fmt.Print("RavenTestDriver.Close()\n")
   597  	// debug.PrintStack()
   598  
   599  	fn := func(key, value interface{}) bool {
   600  		documentStore := key.(*ravendb.DocumentStore)
   601  		documentStore.Close()
   602  		d.documentStores.Delete(key)
   603  		return true
   604  	}
   605  	d.documentStores.Range(fn)
   606  	if d.store != nil {
   607  		d.store.Close()
   608  	}
   609  	d.killServerProcesses()
   610  }
   611  
   612  func shutdownTests() {
   613  	// no-op
   614  }
   615  
   616  func openSessionMust(t *testing.T, store *ravendb.DocumentStore) *ravendb.DocumentSession {
   617  	session, err := store.OpenSession("")
   618  	assert.NoError(t, err)
   619  	assert.NotNil(t, session)
   620  	return session
   621  }
   622  
   623  func isUpper(c byte) bool {
   624  	return c >= 'A' && c <= 'Z'
   625  }
   626  
   627  // converts "TestIndexesFromClient" => "indexes_from_client"
   628  func testNameToFileName(s string) string {
   629  	s = strings.TrimPrefix(s, "Test")
   630  	lower := strings.ToLower(s)
   631  	var res []byte
   632  	n := len(s)
   633  	for i := 0; i < n; i++ {
   634  		c := s[i]
   635  		if i > 0 && isUpper(c) {
   636  			res = append(res, '_')
   637  		}
   638  		res = append(res, lower[i])
   639  	}
   640  	return string(res)
   641  }
   642  
   643  func getLogDir() string {
   644  	// if this is not full path, raven will put it in it's own Logs directory
   645  	// next to server executable
   646  	cwd, _ := os.Getwd()
   647  	dir, file := filepath.Split(cwd)
   648  	if file != "tests" {
   649  		dir = cwd
   650  	}
   651  	dir = filepath.Join(dir, "logs")
   652  	_ = os.MkdirAll(dir, 0755)
   653  	return dir
   654  }
   655  
   656  func (d *RavenTestDriver) maybeStartProfiling() {
   657  	if !isEnvVarTrue("ENABLE_PROFILING") || d.isProfiling {
   658  		return
   659  	}
   660  	if err := pprof.StartCPUProfile(&d.profData); err != nil {
   661  		fmt.Printf("pprof.StartCPUProfile() failed with '%s'\n", err)
   662  	} else {
   663  		d.isProfiling = true
   664  		fmt.Printf("started cpu profiling\n")
   665  	}
   666  }
   667  
   668  func (d *RavenTestDriver) maybeStopProfiling() {
   669  	if !d.isProfiling {
   670  		return
   671  	}
   672  	pprof.StopCPUProfile()
   673  	path := "cpu.prof"
   674  	pd := d.profData.Bytes()
   675  	err := ioutil.WriteFile(path, pd, 0644)
   676  	if err != nil {
   677  		fmt.Printf("failed to write cpu profile data to '%s'. Error: '%s'\n", path, err)
   678  	} else {
   679  		fmt.Printf("wrote cpu profile data to '%s'\n", path)
   680  	}
   681  }
   682  
   683  // called for every Test* function
   684  func createTestDriver(t *testing.T) *RavenTestDriver {
   685  	fmt.Printf("\nStarting test %s\n", t.Name())
   686  	setupLogging(t)
   687  	driver := &RavenTestDriver{}
   688  	return driver
   689  }
   690  
   691  func destroyDriver(t *testing.T, d *RavenTestDriver) {
   692  	if t.Failed() {
   693  		maybePrintFailedRequestsLog()
   694  	}
   695  	if d != nil {
   696  		d.Close()
   697  	}
   698  	d.maybeStopProfiling()
   699  	finishLogging()
   700  }
   701  
   702  func recoverTest(t *testing.T, destroyDriver func()) {
   703  	r := recover()
   704  	destroyDriver()
   705  	if r != nil {
   706  		fmt.Printf("Panic: '%v'\n", r)
   707  		debug.PrintStack()
   708  		t.Fail()
   709  	}
   710  }
   711  
   712  func downloadServerIfNeededWindows() {
   713  	// hacky: if we're in tests directory, cd .. for duration of this function
   714  	panicIf(ravendbServerExePath != "", "ravendb exe already found in %s", ravendbServerExePath)
   715  
   716  	cwd, err := os.Getwd()
   717  	must(err)
   718  	if strings.HasSuffix(cwd, "tests") {
   719  		path := filepath.Clean(filepath.Join(cwd, ".."))
   720  		err = os.Chdir(path)
   721  		must(err)
   722  		defer func() {
   723  			err := os.Chdir(cwd)
   724  			must(err)
   725  		}()
   726  	}
   727  
   728  	exists := fileExists(ravenWindowsZipPath)
   729  	if !exists {
   730  		fmt.Printf("Downloading %s...", ravendbWindowsDownloadURL)
   731  		startTime := time.Now()
   732  		err = httpDl(ravendbWindowsDownloadURL, ravenWindowsZipPath)
   733  		must(err)
   734  		fmt.Printf(" took %s\n", time.Since(startTime))
   735  	}
   736  	destDir := "RavenDB"
   737  	fmt.Printf("Unzipping %s to %s...", ravenWindowsZipPath, destDir)
   738  	startTime := time.Now()
   739  	err = unzip(ravenWindowsZipPath, destDir)
   740  	must(err)
   741  	fmt.Printf(" took %s\n", time.Since(startTime))
   742  }
   743  
   744  func detectRavendbExePath() string {
   745  	// auto-detect env variables if not explicitly set
   746  	path := os.Getenv("RAVENDB_SERVER_PATH")
   747  
   748  	defer func() {
   749  		if path != "" {
   750  			fmt.Printf("Server exe: %s\n", path)
   751  		}
   752  	}()
   753  
   754  	if fileExists(path) {
   755  		return path
   756  	}
   757  
   758  	cwd, err := os.Getwd()
   759  	must(err)
   760  
   761  	path = filepath.Join(cwd, "..", "RavenDB", "Server", "Raven.Server")
   762  	if isWindows() {
   763  		path += ".exe"
   764  	}
   765  	path = filepath.Clean(path)
   766  	if fileExists(path) {
   767  		return path
   768  	}
   769  
   770  	path = filepath.Join(cwd, "RavenDB", "Server", "Raven.Server")
   771  	if isWindows() {
   772  		path += ".exe"
   773  	}
   774  	path = filepath.Clean(path)
   775  	if fileExists(path) {
   776  		return path
   777  	}
   778  	return ""
   779  }
   780  
   781  func loadTestClientCertificate(path string) *tls.Certificate {
   782  	cert, err := loadCertficateAndKeyFromFile(path)
   783  	must(err)
   784  	return cert
   785  }
   786  
   787  func loadTestCaCertificate(path string) *x509.Certificate {
   788  	certPEM, err := ioutil.ReadFile(path)
   789  	must(err)
   790  	block, _ := pem.Decode([]byte(certPEM))
   791  	panicIf(block == nil, "failed to decode cert PEM from %s", path)
   792  	cert, err := x509.ParseCertificate(block.Bytes)
   793  	must(err)
   794  	return cert
   795  }
   796  
   797  // for CI we set RAVEN_License env variable to dev license, so that
   798  // we can run replication tests. On local machines I have dev license
   799  // as a file raven_license.json
   800  func detectRavenDevLicense() {
   801  	if len(os.Getenv("RAVEN_License")) > 0 {
   802  		fmt.Print("RAVEN_License env variable is set\n")
   803  		return
   804  	}
   805  
   806  	path := os.Getenv("RAVEN_License_Path")
   807  	cwd, err := os.Getwd()
   808  	must(err)
   809  	if !fileExists(path) {
   810  		path = filepath.Clean(filepath.Join(cwd, "..", "raven_license.json"))
   811  		if !fileExists(path) {
   812  			path = filepath.Clean(filepath.Join(cwd, "..", "..", "raven_license.json"))
   813  			if !fileExists(path) {
   814  				fmt.Printf("Replication tests are disabled because RAVEN_License_Path not set and file %s doesn't exist.\n", path)
   815  				return
   816  			}
   817  		}
   818  		_ = os.Setenv("RAVEN_License_Path", path)
   819  		fmt.Printf("Setting RAVEN_License_Path to '%s'\n", path)
   820  	}
   821  }
   822  
   823  // note: in Java for tests marked as @DisabledOn41Server
   824  func isRunningOn41Server() bool {
   825  	v := os.Getenv("RAVENDB_SERVER_VERSION")
   826  	return strings.HasPrefix(v, "4.1")
   827  }
   828  
   829  func initializeTests() {
   830  	muInitializeTests.Lock()
   831  	defer muInitializeTests.Unlock()
   832  	if testsWereInitialized {
   833  		return
   834  	}
   835  
   836  	if !enableFlakyTests && isEnvVarTrue("ENABLE_FLAKY_TESTS") {
   837  		enableFlakyTests = true
   838  		fmt.Printf("Setting enableFlakyTests to true\n")
   839  	}
   840  
   841  	if !enableFailingTests && isEnvVarTrue("ENABLE_FAILING_TESTS") {
   842  		enableFailingTests = true
   843  		fmt.Printf("Setting enableFailingTests to true\n")
   844  	}
   845  
   846  	{
   847  		s := os.Getenv("NODES_IN_CLUSTER")
   848  		n, err := strconv.Atoi(s)
   849  		if err == nil && n > 1 {
   850  			numClusterNodes = n
   851  			fmt.Printf("Setting numClusterNodes=%d from NODES_IN_CLUSTER env variable\n", n)
   852  		}
   853  	}
   854  
   855  	{
   856  		s := os.Getenv("KILL_SERVER_CHANCE")
   857  		n, err := strconv.Atoi(s)
   858  		if err == nil {
   859  			randomlyKillServersChance = n
   860  			fmt.Printf("Setting randomlyKillServersChance=%d from KILL_SERVER_CHANCE env variable\n", n)
   861  		}
   862  	}
   863  
   864  	if !shuffleClusterNodes && isEnvVarTrue("SHUFFLE_CLUSTER_NODES") {
   865  		shuffleClusterNodes = true
   866  		fmt.Printf("Setting shuffleClusterNodes to true because SHUFFLE_CLUSTER_NODES env variable is %s\n", os.Getenv("SHUFFLE_CLUSTER_NODES"))
   867  	}
   868  
   869  	setLoggingStateFromEnv()
   870  	detectRavenDevLicense()
   871  
   872  	ravendbServerExePath = detectRavendbExePath()
   873  	if ravendbServerExePath == "" {
   874  		if isWindows() {
   875  			downloadServerIfNeededWindows()
   876  			ravendbServerExePath = detectRavendbExePath()
   877  		}
   878  	}
   879  
   880  	if ravendbServerExePath == "" {
   881  		fmt.Printf("Didn't find ravendb server exe. Set RAVENDB_SERVER_PATH env variable\n")
   882  		os.Exit(1)
   883  	}
   884  
   885  	// find top-level directory
   886  	// wd should be "tests" sub-directory
   887  	wd, _ := os.Getwd()
   888  	rootDir := filepath.Clean(filepath.Join(wd, ".."))
   889  	path := filepath.Join(rootDir, "certs", "server.pfx")
   890  	if !fileExists(path) {
   891  		fmt.Printf("rootDir '%s' doesn't seem correct because can't find file '%s'\n", rootDir, path)
   892  		os.Exit(1)
   893  	}
   894  
   895  	// detect paths of files needed to run the tests
   896  	// either get them from env variables (set by test scripts)
   897  	// or try to auto-detect (helps running tests from within
   898  	// Visual Studio Code or GoLand where env variables are not set)
   899  	{
   900  		path := os.Getenv("RAVENDB_TEST_CERTIFICATE_PATH")
   901  		// wd should be
   902  		if !fileExists(path) {
   903  			path = filepath.Join(rootDir, "certs", "server.pfx")
   904  		}
   905  		if !fileExists(path) {
   906  			fmt.Printf("Didn't find server.pfx file at '%s'. Set RAVENDB_TEST_CERTIFICATE_PATH env variable\n", path)
   907  			os.Exit(1)
   908  		}
   909  		certificatePath = path
   910  		fmt.Printf("Server ertificate file found at '%s'\n", certificatePath)
   911  	}
   912  
   913  	{
   914  		path := os.Getenv("RAVENDB_TEST_CA_PATH")
   915  		if !fileExists(path) {
   916  			path = filepath.Join(rootDir, "certs", "ca.crt")
   917  		}
   918  		if !fileExists(path) {
   919  			fmt.Printf("Didn't find ca.cert file at '%s'. Set RAVENDB_TEST_CA_PATH env variable\n", path)
   920  			os.Exit(1)
   921  		}
   922  		caCertificate = loadTestCaCertificate(path)
   923  		fmt.Printf("Loaded ca certificate from '%s'\n", path)
   924  	}
   925  
   926  	{
   927  		path := os.Getenv("RAVENDB_TEST_CLIENT_CERTIFICATE_PATH")
   928  		if !fileExists(path) {
   929  			path = filepath.Join(rootDir, "certs", "cert.pem")
   930  		}
   931  		if !fileExists(path) {
   932  			fmt.Printf("Didn't find cert.pem file at '%s'. Set RAVENDB_TEST_CLIENT_CERTIFICATE_PATH env variable\n", path)
   933  			os.Exit(1)
   934  		}
   935  		clientCertificate = loadTestClientCertificate(path)
   936  		fmt.Printf("Loaded client certificate from '%s'\n", path)
   937  	}
   938  
   939  	{
   940  		uri := os.Getenv("RAVENDB_TEST_HTTPS_SERVER_URL")
   941  		if uri == "" {
   942  			uri = "https://a.javatest11.development.run:8085"
   943  		}
   944  		httpsServerURL = uri
   945  		fmt.Printf("HTTPS url: '%s'\n", httpsServerURL)
   946  	}
   947  
   948  	testsWereInitialized = true
   949  }
   950  
   951  func TestMain(m *testing.M) {
   952  
   953  	//ravenServerVerbose = true
   954  
   955  	var code int
   956  
   957  	// make sure it's called even if panic happens
   958  	defer func() {
   959  		shutdownTests()
   960  
   961  		//logGoroutines()
   962  		os.Exit(code)
   963  	}()
   964  
   965  	initializeTests()
   966  
   967  	code = m.Run()
   968  }