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