123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- /*
- * EliasDB
- *
- * Copyright 2016 Matthias Ladkau. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
- package server
- import (
- "errors"
- "flag"
- "fmt"
- "io/ioutil"
- "log"
- "net/http"
- "os"
- "runtime"
- "strings"
- "testing"
- "time"
- "devt.de/krotik/common/fileutil"
- "devt.de/krotik/common/httputil"
- "devt.de/krotik/eliasdb/api"
- "devt.de/krotik/eliasdb/api/ac"
- "devt.de/krotik/eliasdb/cluster"
- "devt.de/krotik/eliasdb/cluster/manager"
- "devt.de/krotik/eliasdb/config"
- "devt.de/krotik/eliasdb/graph"
- "devt.de/krotik/eliasdb/graph/graphstorage"
- )
- /*
- Flag to enable / disable long running tests.
- (Only used for test development - should never be false)
- */
- const RunLongRunningTests = true
- const testdb = "testdb"
- const invalidFileName = "**" + "\x00"
- var printLog = []string{}
- var errorLog = []string{}
- var printLogging = false
- func TestMain(m *testing.M) {
- flag.Parse()
- basepath = testdb + "/"
- // Log all print and error messages
- print = func(v ...interface{}) {
- if printLogging {
- fmt.Println(v...)
- }
- printLog = append(printLog, fmt.Sprint(v...))
- }
- fatal = func(v ...interface{}) {
- if printLogging {
- fmt.Println(v...)
- }
- errorLog = append(errorLog, fmt.Sprint(v...))
- }
- defer func() {
- fatal = log.Fatal
- basepath = ""
- }()
- if res, _ := fileutil.PathExists(testdb); res {
- if err := os.RemoveAll(testdb); err != nil {
- fmt.Print("Could not remove test directory:", err.Error())
- }
- }
- ensurePath(testdb)
- // Run the tests
- res := m.Run()
- if res, _ := fileutil.PathExists(testdb); res {
- if err := os.RemoveAll(testdb); err != nil {
- fmt.Print("Could not remove test directory:", err.Error())
- }
- }
- os.Exit(res)
- }
- func TestMainNormalCase(t *testing.T) {
- if !RunLongRunningTests {
- return
- }
- // Make sure to reset the DefaultServeMux
- defer func() { http.DefaultServeMux = http.NewServeMux() }()
- // Make sure to remove any files
- defer func() {
- if err := os.RemoveAll(testdb); err != nil {
- fmt.Print("Could not remove test directory:", err.Error())
- }
- time.Sleep(time.Duration(100) * time.Millisecond)
- ensurePath(testdb)
- }()
- // Reset logs
- printLog = []string{}
- errorLog = []string{}
- errorChan := make(chan error)
- // Load default configuration
- config.LoadDefaultConfig()
- // Start ECAL scripting by default
- config.Config[config.EnableECALScripts] = true
- // Start cluster by default
- config.Config[config.EnableCluster] = true
- config.Config[config.EnableClusterTerminal] = true
- // Enable access control
- config.Config[config.EnableAccessControl] = true
- // Kick off main function
- go func() {
- out, _ := runServer()
- config.Config[config.EnableCluster] = false
- config.Config[config.EnableClusterTerminal] = false
- config.Config[config.EnableAccessControl] = false
- ac.ACL.Close()
- os.Remove("access.db")
- lines := strings.Split(strings.TrimSpace(out), "\n")
- errorChan <- nil
- // stderr should contain one line from the rpc code
- if len(lines) != 1 || !strings.Contains(lines[0], "rpc.Serve: accept") {
- t.Error("Unexpected stderr:", out)
- return
- }
- }()
- // To exit the main function the lock watcher thread
- // has to recognise that the lockfile was modified
- shutdown := false
- go func() {
- filename := basepath + config.Str(config.LockFile)
- for !shutdown {
- // Do a normal shutdown with a log file - don't check for errors
- shutdownWithLogFile(filename)
- time.Sleep(time.Duration(200) * time.Millisecond)
- }
- }()
- // Wait for the main function to end
- if err := <-errorChan; err != nil || len(errorLog) != 0 {
- t.Error("Unexpected ending of main thread:", err, errorLog)
- return
- }
- shutdown = true
- // Check the print log
- logString := strings.Join(printLog, "\n")
- if runtime.GOOS == "windows" {
- // Very primitive but good enough
- logString = strings.Replace(logString, "\\", "/", -1)
- }
- if logString != `
- EliasDB `[1:]+config.ProductVersion+`
- Starting datastore in testdb/db
- Reading cluster config
- Opening cluster state info
- Starting cluster (log history: 100)
- [Cluster] member1: Starting member manager member1 rpc server on: 127.0.0.1:9030
- Creating GraphManager instance
- Loading ECAL scripts in testdb/scripts
- Creating key (key.pem) and certificate (cert.pem) in: ssl
- Ensuring web folder: testdb/web
- Ensuring login page: testdb/web/login.html
- Ensuring web terminal: testdb/web/db/term.html
- Ensuring cluster terminal: testdb/web/db/cluster.html
- Starting HTTPS server on: 127.0.0.1:9090
- Writing fingerprint file: testdb/web/fingerprint.json
- Waiting for shutdown
- Lockfile was modified
- Shutting down
- [Cluster] member1: Housekeeping stopped
- [Cluster] member1: Shutdown rpc server on: 127.0.0.1:9030
- [Cluster] member1: Connection closed: 127.0.0.1:9030
- Closing datastore` {
- t.Error("Unexpected log:", logString)
- return
- }
- }
- func TestMainErrorCases(t *testing.T) {
- if !RunLongRunningTests {
- return
- }
- // Make sure to reset the DefaultServeMux
- defer func() { http.DefaultServeMux = http.NewServeMux() }()
- // Make sure to remove any files
- defer func() {
- if err := os.RemoveAll(testdb); err != nil {
- fmt.Print("Could not remove test directory:", err.Error())
- }
- time.Sleep(time.Duration(100) * time.Millisecond)
- ensurePath(testdb)
- }()
- // Setup config and logs
- data := make(map[string]interface{})
- for k, v := range config.DefaultConfig {
- data[k] = v
- }
- config.Config = data
- printLog = []string{}
- errorLog = []string{}
- // Test db access error
- config.Config[config.MemoryOnlyStorage] = true
- config.Config[config.EnableReadOnly] = true
- config.Config[config.EnableECALScripts] = true
- config.Config[config.ECALEntryScript] = invalidFileName
- runServer()
- // Check that an error happened
- if len(errorLog) != 1 ||
- !strings.Contains(errorLog[0], "Failed to start ECAL scripting interpreter") {
- t.Error("Unexpected error:", errorLog)
- return
- }
- // Set back logs
- printLog = []string{}
- errorLog = []string{}
- // Test db access error
- config.Config[config.EnableECALScripts] = false
- config.Config[config.MemoryOnlyStorage] = false
- config.Config[config.LocationDatastore] = invalidFileName
- config.Config[config.EnableReadOnly] = true
- runServer()
- // Check that an error happened
- if len(errorLog) != 2 ||
- !strings.Contains(errorLog[0], "Could not create directory") ||
- !strings.Contains(errorLog[1], "Failed to open graph storage") {
- t.Error("Unexpected error:", errorLog)
- return
- }
- // Set back logs
- printLog = []string{}
- errorLog = []string{}
- // Use memory only storage and the ignored readonly flag
- config.Config[config.MemoryOnlyStorage] = true
- config.Config[config.EnableReadOnly] = true
- // Test failed ssl key generation
- config.Config[config.HTTPSKey] = invalidFileName
- runServer()
- // Check that an error happened
- if len(errorLog) != 1 ||
- !strings.Contains(errorLog[0], "Failed to generate ssl key and certificate") {
- t.Error("Unexpected error:", errorLog)
- return
- }
- config.Config[config.HTTPSKey] = config.DefaultConfig[config.HTTPSKey]
- // Set back logs
- printLog = []string{}
- errorLog = []string{}
- // Special error when closing the store
- graphstorage.MgsRetClose = errors.New("Testerror")
- // Use 9090
- config.Config[config.HTTPSPort] = "9090"
- ths := httputil.HTTPServer{}
- go ths.RunHTTPServer(":9090", nil)
- time.Sleep(time.Duration(1) * time.Second)
- runServer()
- ths.Shutdown()
- time.Sleep(time.Duration(1) * time.Second)
- if ths.Running {
- t.Error("Server should not be running")
- return
- }
- if len(errorLog) != 2 || (errorLog[0] != "listen tcp :9090"+
- ": bind: address already in use" && errorLog[0] != "listen tcp :9090"+
- ": bind: Only one usage of each socket address (protocol/network address/port) is normally permitted.") ||
- errorLog[1] != "Testerror" {
- t.Error("Unexpected error:", errorLog)
- return
- }
- // Set back logs
- printLog = []string{}
- errorLog = []string{}
- config.Config[config.EnableCluster] = true
- cluster.DSRetNew = errors.New("testerror")
- defer func() {
- cluster.DSRetNew = nil
- }()
- runServer()
- if len(errorLog) != 1 ||
- !strings.Contains(errorLog[0], "testerror") {
- t.Error("Unexpected error:", errorLog)
- return
- }
- // Set back logs
- printLog = []string{}
- errorLog = []string{}
- config.Config[config.EnableCluster] = true
- config.Config[config.ClusterStateInfoFile] = invalidFileName
- runServer()
- if len(errorLog) != 1 ||
- !strings.Contains(errorLog[0], "Failed to load cluster state info") {
- t.Error("Unexpected error:", errorLog)
- return
- }
- // Set back logs
- printLog = []string{}
- errorLog = []string{}
- config.Config[config.ClusterConfigFile] = invalidFileName
- runServer()
- if len(errorLog) != 1 ||
- !strings.Contains(errorLog[0], "Failed to load cluster config") {
- t.Error("Unexpected error:", errorLog)
- return
- }
- // Call to debug log function
- manager.LogDebug("test debug text")
- if logout := api.DDLog.String(); !strings.Contains(logout, "test debug text") {
- t.Error("Unexpected error:", logout)
- return
- }
- config.Config = nil
- SOPExecuted := false
- // Test single operation
- StartServerWithSingleOp(func(gm *graph.Manager) bool {
- SOPExecuted = true
- return true
- })
- if !SOPExecuted {
- t.Error("Single operation function was not executed")
- return
- }
- config.Config = nil
- }
- func shutdownWithLogFile(filename string) error {
- file, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660)
- defer file.Close()
- if err != nil {
- fmt.Println(errorLog)
- return err
- }
- _, err = file.Write([]byte("a"))
- if err != nil {
- return err
- }
- return nil
- }
- /*
- Run the server and capture the output.
- */
- func runServer() (string, error) {
- defer func() {
- if r := recover(); r != nil {
- fmt.Println("Server execution caused a panic.")
- out, err := ioutil.ReadFile("out.txt")
- if err != nil {
- fmt.Println(err)
- }
- fmt.Println(out)
- }
- }()
- // Exchange stderr to a file
- origStdErr := os.Stderr
- outFile, err := os.Create("out.txt")
- if err != nil {
- return "", err
- }
- defer func() {
- outFile.Close()
- os.RemoveAll("out.txt")
- // Put Stderr back
- os.Stderr = origStdErr
- log.SetOutput(os.Stderr)
- }()
- os.Stderr = outFile
- log.SetOutput(outFile)
- StartServer()
- // Reset flags
- outFile.Sync()
- out, err := ioutil.ReadFile("out.txt")
- if err != nil {
- return "", err
- }
- return string(out), nil
- }
|