From 85fcef29685d6de62a46190fe8f49012842a4a75 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Wed, 7 Jun 2017 14:47:31 +0200 Subject: [PATCH] cmd/mediaapi-integration-tests: Add foundation for testing --- .../cmd/dendrite-media-api-server/main.go | 38 ++-- .../cmd/mediaapi-integration-tests/TESTS.md | 50 +++++ .../cmd/mediaapi-integration-tests/main.go | 178 ++++++++++++++++++ 3 files changed, 250 insertions(+), 16 deletions(-) create mode 100644 src/github.com/matrix-org/dendrite/cmd/mediaapi-integration-tests/TESTS.md create mode 100644 src/github.com/matrix-org/dendrite/cmd/mediaapi-integration-tests/main.go diff --git a/src/github.com/matrix-org/dendrite/cmd/dendrite-media-api-server/main.go b/src/github.com/matrix-org/dendrite/cmd/dendrite-media-api-server/main.go index e50179374..fb39a03a6 100644 --- a/src/github.com/matrix-org/dendrite/cmd/dendrite-media-api-server/main.go +++ b/src/github.com/matrix-org/dendrite/cmd/dendrite-media-api-server/main.go @@ -15,6 +15,7 @@ package main import ( + "flag" "fmt" "io/ioutil" "net/http" @@ -36,27 +37,29 @@ import ( ) var ( - bindAddr = os.Getenv("BIND_ADDRESS") + bindAddr = flag.String("listen", "", "The port to listen on.") dataSource = os.Getenv("DATABASE") logDir = os.Getenv("LOG_DIR") serverName = os.Getenv("SERVER_NAME") basePath = os.Getenv("BASE_PATH") // Note: if the MAX_FILE_SIZE_BYTES is set to 0, it will be unlimited maxFileSizeBytesString = os.Getenv("MAX_FILE_SIZE_BYTES") - configPath = os.Getenv("CONFIG_PATH") + configPath = flag.String("config", "", "The path to the config file. For more information, see the config file in this repository.") ) func main() { common.SetupLogging(logDir) + flag.Parse() + log.WithFields(log.Fields{ - "BIND_ADDRESS": bindAddr, + "listen": *bindAddr, "DATABASE": dataSource, "LOG_DIR": logDir, "SERVER_NAME": serverName, "BASE_PATH": basePath, "MAX_FILE_SIZE_BYTES": maxFileSizeBytesString, - "CONFIG_PATH": configPath, + "config": *configPath, }).Info("Loading configuration based on config file and environment variables") cfg, err := configureServer() @@ -64,15 +67,10 @@ func main() { log.WithError(err).Fatal("Invalid configuration") } - db, err := storage.Open(cfg.DataSource) - if err != nil { - log.WithError(err).Panic("Failed to open database") - } - log.WithFields(log.Fields{ - "BIND_ADDRESS": bindAddr, + "listen": *bindAddr, "LOG_DIR": logDir, - "CONFIG_PATH": configPath, + "CONFIG_PATH": *configPath, "ServerName": cfg.ServerName, "AbsBasePath": cfg.AbsBasePath, "MaxFileSizeBytes": *cfg.MaxFileSizeBytes, @@ -82,13 +80,21 @@ func main() { "ThumbnailSizes": cfg.ThumbnailSizes, }).Info("Starting mediaapi server with configuration") + db, err := storage.Open(cfg.DataSource) + if err != nil { + log.WithError(err).Panic("Failed to open database") + } + routing.Setup(http.DefaultServeMux, http.DefaultClient, cfg, db) - log.Fatal(http.ListenAndServe(bindAddr, nil)) + log.Fatal(http.ListenAndServe(*bindAddr, nil)) } // configureServer loads configuration from a yaml file and overrides with environment variables func configureServer() (*config.MediaAPI, error) { - cfg, err := loadConfig(configPath) + if *configPath == "" { + log.Fatal("--config must be supplied") + } + cfg, err := loadConfig(*configPath) if err != nil { log.WithError(err).Fatal("Invalid config file") } @@ -172,14 +178,14 @@ func applyOverrides(cfg *config.MediaAPI) { if cfg.MaxThumbnailGenerators == 0 { log.WithField( "max_thumbnail_generators", cfg.MaxThumbnailGenerators, - ).Info("Using default max_thumbnail_generators") + ).Info("Using default max_thumbnail_generators value of 10") cfg.MaxThumbnailGenerators = 10 } } func validateConfig(cfg *config.MediaAPI) error { - if bindAddr == "" { - return fmt.Errorf("no BIND_ADDRESS environment variable found") + if *bindAddr == "" { + log.Fatal("--listen must be supplied") } absBasePath, err := getAbsolutePath(cfg.BasePath) diff --git a/src/github.com/matrix-org/dendrite/cmd/mediaapi-integration-tests/TESTS.md b/src/github.com/matrix-org/dendrite/cmd/mediaapi-integration-tests/TESTS.md new file mode 100644 index 000000000..722c46afd --- /dev/null +++ b/src/github.com/matrix-org/dendrite/cmd/mediaapi-integration-tests/TESTS.md @@ -0,0 +1,50 @@ +* functional + * upload + * normal case + * file too large + * 0-byte file? + * invalid filename + * invalid content-type + * download + * invalid origin + * invalid media id + * local file + * existing + * non-existing + * remote file + * existing + * non-existing + * thumbnail + * original file formats + * JPEG + * GIF + * PNG + * BMP + * SVG + * PDF + * local file + * existing + * non-existing + * remote file + * existing + * non-existing + * cache + * cold + * hot + * pre-generation according to configuration + * manual verification + hash check for regressions? + * dynamic generation + * cold cache + * hot cache + * larger than original + * limit on dimensions? + * 0x0 + * scale + * crop +* load + * 100 parallel requests + * same file + * different local files + * different remote files + * pre-generated thumbnails + * non-pre-generated thumbnails diff --git a/src/github.com/matrix-org/dendrite/cmd/mediaapi-integration-tests/main.go b/src/github.com/matrix-org/dendrite/cmd/mediaapi-integration-tests/main.go new file mode 100644 index 000000000..91a328590 --- /dev/null +++ b/src/github.com/matrix-org/dendrite/cmd/mediaapi-integration-tests/main.go @@ -0,0 +1,178 @@ +// 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 main + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path" + "strconv" + "time" + + "github.com/matrix-org/dendrite/common/test" +) + +var ( + // How long to wait for the server to write the expected output messages. + // This needs to be high enough to account for the time it takes to create + // the postgres database tables which can take a while on travis. + timeoutString = test.Defaulting(os.Getenv("TIMEOUT"), "10s") + // The name of maintenance database to connect to in order to create the test database. + postgresDatabase = test.Defaulting(os.Getenv("POSTGRES_DATABASE"), "postgres") + // The name of the test database to create. + testDatabaseName = test.Defaulting(os.Getenv("DATABASE_NAME"), "mediaapi_test") + // Postgres docker container name (for running psql) + postgresContainerName = os.Getenv("POSTGRES_CONTAINER") +) + +var thumbnailPregenerationConfig = (` +thumbnail_sizes: +- width: 32 + height: 32 + method: crop +- width: 96 + height: 96 + method: crop +- width: 320 + height: 240 + method: scale +- width: 640 + height: 480 + method: scale +- width: 800 + height: 600 + method: scale +`) + +const serverType = "media-api" + +var testDatabaseTemplate = "dbname=%s sslmode=disable binary_parameters=yes" + +var timeout time.Duration + +func startMediaAPI(suffix string, dynamicThumbnails bool) (*exec.Cmd, chan error, string) { + dir, err := ioutil.TempDir("", serverType+"-server-test"+suffix) + if err != nil { + panic(err) + } + + configFilename := serverType + "-server-test-config" + suffix + ".yaml" + configFileContents := makeConfig(suffix, dir, dynamicThumbnails) + + serverArgs := []string{ + "--config", configFilename, + "--listen", "localhost:1777" + suffix, + } + + databases := []string{ + testDatabaseName + suffix, + } + + cmd, cmdChan := test.StartServer( + serverType, + serverArgs, + suffix, + configFilename, + configFileContents, + postgresDatabase, + postgresContainerName, + databases, + ) + return cmd, cmdChan, dir +} + +func makeConfig(suffix, basePath string, dynamicThumbnails bool) string { + return fmt.Sprintf( + ` +server_name: "%s" +base_path: %s +max_file_size_bytes: %s +database: "%s" +dynamic_thumbnails: %s +%s`, + "localhost:1777"+suffix, + basePath, + "10485760", + fmt.Sprintf(testDatabaseTemplate, testDatabaseName+suffix), + strconv.FormatBool(dynamicThumbnails), + thumbnailPregenerationConfig, + ) +} + +func cleanUpServer(cmd *exec.Cmd, dir string) { + cmd.Process.Kill() // ensure server is dead, only cleaning up so don't care about errors this returns. + if err := os.RemoveAll(dir); err != nil { + fmt.Printf("WARNING: Failed to remove temporary directory %v: %q\n", dir, err) + } +} + +// Runs a battery of media API server tests +// The tests will pause at various points in this list to conduct tests on the HTTP responses before continuing. +func main() { + fmt.Println("==TESTING==", os.Args[0]) + + var err error + timeout, err = time.ParseDuration(timeoutString) + if err != nil { + fmt.Printf("ERROR: Invalid timeout string %v: %q\n", timeoutString, err) + return + } + + // create server1 with only pre-generated thumbnails allowed + server1Cmd, server1CmdChan, server1Dir := startMediaAPI("1", false) + defer cleanUpServer(server1Cmd, server1Dir) + testDownload("1", "localhost:17771", "doesnotexist", "", 404, server1CmdChan) + + // create server2 with dynamic thumbnail generation + server2Cmd, server2CmdChan, server2Dir := startMediaAPI("2", true) + defer cleanUpServer(server2Cmd, server2Dir) + testDownload("2", "localhost:17772", "doesnotexist", "", 404, server2CmdChan) +} + +func getMediaURI(scheme, host, endpoint string, components []string) string { + pathComponents := []string{ + host, + "api", + "_matrix", + "media", + "v1", + endpoint, + } + pathComponents = append(pathComponents, components...) + return scheme + path.Join(pathComponents...) +} + +func testDownload(suffix, origin, mediaID, wantedBody string, wantedStatusCode int, serverCmdChan chan error) { + req, err := http.NewRequest( + "GET", + getMediaURI("http://", "localhost:1777"+suffix, "download", []string{ + origin, + mediaID, + }), + nil, + ) + if err != nil { + panic(err) + } + testReq := &test.Request{ + Req: req, + WantedStatusCode: wantedStatusCode, + WantedBody: wantedBody, + } + testReq.Run("media-api", timeout, serverCmdChan) +}