mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-02-25 14:03:09 -06:00
common/test: Add some server init and client request utilities
This commit is contained in:
parent
85fcef2968
commit
f48c293cb0
151
src/github.com/matrix-org/dendrite/common/test/client.go
Normal file
151
src/github.com/matrix-org/dendrite/common/test/client.go
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Request contains the information necessary to issue a request and test its result
|
||||||
|
type Request struct {
|
||||||
|
Req *http.Request
|
||||||
|
WantedBody string
|
||||||
|
WantedStatusCode int
|
||||||
|
LastErr *LastRequestErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastRequestErr is a synchronized error wrapper
|
||||||
|
// Useful for obtaining the last error from a set of requests
|
||||||
|
type LastRequestErr struct {
|
||||||
|
sync.Mutex
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the error
|
||||||
|
func (r *LastRequestErr) Set(err error) {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
r.Err = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the error
|
||||||
|
func (r *LastRequestErr) Get() error {
|
||||||
|
r.Lock()
|
||||||
|
defer r.Unlock()
|
||||||
|
return r.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanonicalJSONInput canonicalises a slice of JSON strings
|
||||||
|
// Useful for test input
|
||||||
|
func CanonicalJSONInput(jsonData []string) []string {
|
||||||
|
for i := range jsonData {
|
||||||
|
jsonBytes, err := gomatrixserverlib.CanonicalJSON([]byte(jsonData[i]))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
jsonData[i] = string(jsonBytes)
|
||||||
|
}
|
||||||
|
return jsonData
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do issues a request and checks the status code and body of the response
|
||||||
|
func (r *Request) Do() error {
|
||||||
|
client := &http.Client{
|
||||||
|
Timeout: 5 * time.Second,
|
||||||
|
}
|
||||||
|
fmt.Println("Attempting request", r.Req)
|
||||||
|
res, err := client.Do(r.Req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != r.WantedStatusCode {
|
||||||
|
return fmt.Errorf("incorrect status code. Expected: %d Got: %d", r.WantedStatusCode, res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.WantedBody != "" {
|
||||||
|
resBytes, err := ioutil.ReadAll(res.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jsonBytes, err := gomatrixserverlib.CanonicalJSON(resBytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if string(jsonBytes) != r.WantedBody {
|
||||||
|
return fmt.Errorf("returned wrong bytes. Expected:\n%s\n\nGot:\n%s", r.WantedBody, string(jsonBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoUntilSuccess blocks and repeats the same request until the response returns the desired status code and body.
|
||||||
|
// It then closes the given channel and returns.
|
||||||
|
func (r *Request) DoUntilSuccess(done chan error) {
|
||||||
|
for {
|
||||||
|
if err := r.Do(); err != nil {
|
||||||
|
r.LastErr.Set(err)
|
||||||
|
time.Sleep(1 * time.Second) // don't tightloop
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run repeatedly issues a request until success, error or a timeout is reached
|
||||||
|
func (r *Request) Run(label string, timeout time.Duration, serverCmdChan chan error) {
|
||||||
|
fmt.Printf("==TESTING== %v (timeout: %v)\n", label, timeout)
|
||||||
|
done := make(chan error, 1)
|
||||||
|
|
||||||
|
// We need to wait for the server to:
|
||||||
|
// - have connected to the database
|
||||||
|
// - have created the tables
|
||||||
|
// - be listening on the given port
|
||||||
|
go r.DoUntilSuccess(done)
|
||||||
|
|
||||||
|
// wait for one of:
|
||||||
|
// - the test to pass (done channel is closed)
|
||||||
|
// - the server to exit with an error (error sent on serverCmdChan)
|
||||||
|
// - our test timeout to expire
|
||||||
|
// We don't need to clean up since the main() function handles that in the event we panic
|
||||||
|
select {
|
||||||
|
case <-time.After(timeout):
|
||||||
|
fmt.Printf("==TESTING== %v TIMEOUT\n", label)
|
||||||
|
if reqErr := r.LastErr.Get(); reqErr != nil {
|
||||||
|
fmt.Println("Last /sync request error:")
|
||||||
|
fmt.Println(reqErr)
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("%v server timed out", label))
|
||||||
|
case err := <-serverCmdChan:
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("=============================================================================================")
|
||||||
|
fmt.Printf("%v server failed to run. If failing with 'pq: password authentication failed for user' try:", label)
|
||||||
|
fmt.Println(" export PGHOST=/var/run/postgresql")
|
||||||
|
fmt.Println("=============================================================================================")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
case <-done:
|
||||||
|
fmt.Printf("==TESTING== %v PASSED\n", label)
|
||||||
|
}
|
||||||
|
}
|
||||||
101
src/github.com/matrix-org/dendrite/common/test/server.go
Normal file
101
src/github.com/matrix-org/dendrite/common/test/server.go
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2017 Vector Creations Ltd
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defaulting allows assignment of string variables with a fallback default value
|
||||||
|
// Useful for use with os.Getenv() for example
|
||||||
|
func Defaulting(value, defaultValue string) string {
|
||||||
|
if value == "" {
|
||||||
|
value = defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDatabase creates a new database, dropping it first if it exists
|
||||||
|
func CreateDatabase(command string, args []string, database string) error {
|
||||||
|
cmd := exec.Command(command, args...)
|
||||||
|
cmd.Stdin = strings.NewReader(
|
||||||
|
fmt.Sprintf("DROP DATABASE IF EXISTS %s; CREATE DATABASE %s;", database, database),
|
||||||
|
)
|
||||||
|
// Send stdout and stderr to our stderr so that we see error messages from
|
||||||
|
// the psql process
|
||||||
|
cmd.Stdout = os.Stderr
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateBackgroundCommand creates an executable command
|
||||||
|
// The Cmd being executed is returned. A channel is also returned,
|
||||||
|
// which will have any termination errors sent down it, followed immediately by the channel being closed.
|
||||||
|
func CreateBackgroundCommand(command string, args []string, suffix string) (*exec.Cmd, chan error) {
|
||||||
|
cmd := exec.Command(command, args...)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
panic("failed to start server: " + err.Error())
|
||||||
|
}
|
||||||
|
cmdChan := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
cmdChan <- cmd.Wait()
|
||||||
|
close(cmdChan)
|
||||||
|
}()
|
||||||
|
return cmd, cmdChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartServer creates the database and config file needed for the server to run and
|
||||||
|
// then starts the server. The Cmd being executed is returned. A channel is also returned,
|
||||||
|
// which will have any termination errors sent down it, followed immediately by the channel being closed.
|
||||||
|
func StartServer(serverType string, serverArgs []string, suffix, configFilename, configFileContents, postgresDatabase, postgresContainerName string, databases []string) (*exec.Cmd, chan error) {
|
||||||
|
if len(databases) > 0 {
|
||||||
|
var dbCmd string
|
||||||
|
var dbArgs []string
|
||||||
|
if postgresContainerName == "" {
|
||||||
|
dbCmd = "psql"
|
||||||
|
dbArgs = []string{postgresDatabase}
|
||||||
|
} else {
|
||||||
|
dbCmd = "docker"
|
||||||
|
dbArgs = []string{
|
||||||
|
"exec", "-i", postgresContainerName, "psql", "-U", "postgres", postgresDatabase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, database := range databases {
|
||||||
|
if err := CreateDatabase(dbCmd, dbArgs, database); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if configFilename != "" {
|
||||||
|
if err := ioutil.WriteFile(configFilename, []byte(configFileContents), 0644); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateBackgroundCommand(
|
||||||
|
filepath.Join(filepath.Dir(os.Args[0]), "dendrite-"+serverType+"-server"),
|
||||||
|
serverArgs,
|
||||||
|
suffix,
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue