Browse Source

Add basic integration test infrastructure (and new endpoint `/api/v1/version` for testing it) (#741)

* Implement '/api/v1/version'

* Cleanup and various fixes

* Enhance run.sh

* Add install_test.go

* Add parameter utils.Config for testing handlers

* Re-organize TestVersion.go

* Rename functions

* handling process cleanup properly

* Fix missing function renaming

* Cleanup the 'retry' logic

* Cleanup

* Remove unneeded logging code

* Logging messages tweaking

* Logging message tweaking

* Fix logging messages

* Use 'const' instead of hardwired numbers

* We don't really need retries anymore

* Move constant ServerHttpPort to install_test.go

* Restore mistakenly removed constant

* Add required comments to make the linter happy.

* Fix comments and naming to address linter's complaints

* Detect Gitea executale version automatically

* Remove tests/run.sh, `go test` suffices.

* Make `make build` a prerequisite of `make test`

* Do not sleep before trying

* Speedup the server pinging loop

* Use defined const instead of hardwired numbers

* Remove redundant error handling

* Use a dedicated target for running code.gitea.io/tests

* Do not make 'test' depend on 'build' target

* Rectify the excluded package list

* Remove redundant 'exit 1'

* Change the API to allow passing test.T to test handlers

* Make testing.T an embedded field

* Use assert.Equal to comparing results

* Add copyright info

* Parametrized logging output

* Use tmpdir instead

* Eliminate redundant casting

* Remove unneeded variable

* Fix last commit

* Add missing copyright info

* Replace fmt.Fprintf with fmt.Fprint

* rename the xtest to integration-test

* Use Symlink instead of hard-link for cross-device linking

* Turn debugging logs on

* Follow the existing framework for APIs

* Output logs only if test.v is true

* Re-order import statements

* Enhance the error message

* Fix comment which breaks the linter's rule

* Rename 'integration-test' to 'e2e-test' for saving keystrokes

* Add comment to avoid possible confusion

* Rename tests -> integration-tests

Also change back the Makefile to use `make integration-test`.

* Use tests/integration for now

* tests/integration -> integrations

Slightly flattened directory hierarchy is better.

* Update Makefile accordingly

* Fix a missing change in Makefile

* govendor update code.gitea.io/sdk/gitea

* Fix comment of struct fields

* Fix conditional nonsense

* Fix missing updates regarding version string changes

* Make variable naming more consistent

* Check http status code

* Rectify error messages
tags/v1.1.0
Mura Li 7 years ago
parent
commit
848293671b

+ 6
- 1
Makefile View File

@@ -14,7 +14,7 @@ JAVASCRIPTS :=
LDFLAGS := -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')" -X "main.Tags=$(TAGS)"

TARGETS ?= linux/*,darwin/*,windows/*
PACKAGES ?= $(shell go list ./... | grep -v /vendor/)
PACKAGES ?= $(filter-out code.gitea.io/gitea/integrations,$(shell go list ./... | grep -v /vendor/))
SOURCES ?= $(shell find . -name "*.go" -type f)

TAGS ?=
@@ -66,6 +66,11 @@ lint:
fi
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;

.PHONY: integrations
integrations: TAGS=bindata sqlite
integrations: build
go test code.gitea.io/gitea/integrations

.PHONY: test
test:
for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done;

+ 97
- 0
integrations/install_test.go View File

@@ -0,0 +1,97 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integration

import (
"fmt"
"net/http"
"os"
"os/user"
"path/filepath"
"testing"
"time"

"code.gitea.io/gitea/integrations/internal/utils"
)

// The HTTP port listened by the Gitea server.
const ServerHTTPPort = "3001"

const _RetryLimit = 10

func makeSimpleSettings(user, workdir, port string) map[string][]string {
return map[string][]string{
"db_type": {"SQLite3"},
"db_host": {"localhost"},
"db_path": {workdir + "data/gitea.db"},
"app_name": {"Gitea: Git with a cup of tea"},
"repo_root_path": {workdir + "repositories"},
"run_user": {user},
"domain": {"localhost"},
"ssh_port": {"22"},
"http_port": {port},
"app_url": {"http://localhost:" + port},
"log_root_path": {workdir + "log"},
}
}

func install(t *utils.T) error {
var r *http.Response
var err error

for i := 1; i <= _RetryLimit; i++ {

r, err = http.Get("http://:" + ServerHTTPPort + "/")
if err == nil {
fmt.Fprintln(os.Stderr)
break
}

// Give the server some amount of time to warm up.
time.Sleep(100 * time.Millisecond)
fmt.Fprint(os.Stderr, ".")
}

if err != nil {
return err
}

defer r.Body.Close()

_user, err := user.Current()
if err != nil {
return err
}

path, err := filepath.Abs(t.Config.WorkDir)
if err != nil {
return err
}

settings := makeSimpleSettings(_user.Username, path, ServerHTTPPort)
r, err = http.PostForm("http://:"+ServerHTTPPort+"/install", settings)
if err != nil {
return err
}
defer r.Body.Close()

if r.StatusCode != http.StatusOK {
return fmt.Errorf("'/install': %s", r.Status)
}
return nil
}

func TestInstall(t *testing.T) {
conf := utils.Config{
Program: "../gitea",
WorkDir: "",
Args: []string{"web", "--port", ServerHTTPPort},
LogFile: os.Stderr,
}

if err := utils.New(t, &conf).RunTest(install); err != nil {
t.Fatal(err)
}
}

+ 125
- 0
integrations/internal/utils/utils.go View File

@@ -0,0 +1,125 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package utils

import (
"errors"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"syscall"
"testing"
)

// T wraps testing.T and the configurations of the testing instance.
type T struct {
*testing.T
Config *Config
}

// New create an instance of T
func New(t *testing.T, c *Config) *T {
return &T{T: t, Config: c}
}

// Config Settings of the testing program
type Config struct {
// The executable path of the tested program.
Program string
// Working directory prepared for the tested program.
// If empty, a directory named with random suffixes is picked, and created under the platform-dependent default temporary directory.
// The directory will be removed when the test finishes.
WorkDir string
// Command-line arguments passed to the tested program.
Args []string

// Where to redirect the stdout/stderr to. For debugging purposes.
LogFile *os.File
}

func redirect(cmd *exec.Cmd, f *os.File) error {
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}

stderr, err := cmd.StderrPipe()
if err != nil {
return err
}

go io.Copy(f, stdout)
go io.Copy(f, stderr)
return nil
}

// RunTest Helper function for setting up a running Gitea server for functional testing and then gracefully terminating it.
func (t *T) RunTest(tests ...func(*T) error) (err error) {
if t.Config.Program == "" {
return errors.New("Need input file")
}

path, err := filepath.Abs(t.Config.Program)
if err != nil {
return err
}

workdir := t.Config.WorkDir
if workdir == "" {
workdir, err = ioutil.TempDir(os.TempDir(), "gitea_tests-")
if err != nil {
return err
}
defer os.RemoveAll(workdir)
}

newpath := filepath.Join(workdir, filepath.Base(path))
if err := os.Symlink(path, newpath); err != nil {
return err
}

log.Printf("Starting the server: %s args:%s workdir:%s", newpath, t.Config.Args, workdir)

cmd := exec.Command(newpath, t.Config.Args...)
cmd.Dir = workdir

if t.Config.LogFile != nil && testing.Verbose() {
if err := redirect(cmd, t.Config.LogFile); err != nil {
return err
}
}

if err := cmd.Start(); err != nil {
return err
}

log.Println("Server started.")

defer func() {
// Do not early return. We have to call Wait anyway.
_ = cmd.Process.Signal(syscall.SIGTERM)

if _err := cmd.Wait(); _err != nil {
if _err.Error() != "signal: terminated" {
err = _err
return
}
}

log.Println("Server exited")
}()

for _, fn := range tests {
if err := fn(t); err != nil {
return err
}
}

// Note that the return value 'err' may be updated by the 'defer' statement before despite it's returning nil here.
return nil
}

+ 82
- 0
integrations/version_test.go View File

@@ -0,0 +1,82 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integration

import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"code.gitea.io/gitea/integrations/internal/utils"
"code.gitea.io/sdk/gitea"

"github.com/stretchr/testify/assert"
)

func version(t *utils.T) error {
var err error

path, err := filepath.Abs(t.Config.Program)
if err != nil {
return err
}

cmd := exec.Command(path, "--version")
out, err := cmd.Output()
if err != nil {
return err
}

fields := strings.Fields(string(out))
if !strings.HasPrefix(string(out), "Gitea version") {
return fmt.Errorf("unexpected version string '%s' of the gitea executable", out)
}

expected := fields[2]

var r *http.Response
r, err = http.Get("http://:" + ServerHTTPPort + "/api/v1/version")
if err != nil {
return err
}
defer r.Body.Close()

if r.StatusCode != http.StatusOK {
return fmt.Errorf("'/api/v1/version': %s\n", r.Status)
}

var v gitea.ServerVersion

dec := json.NewDecoder(r.Body)
if err := dec.Decode(&v); err != nil {
return err
}

actual := v.Version

log.Printf("Actual: \"%s\" Expected: \"%s\"\n", actual, expected)
assert.Equal(t, expected, actual)

return nil
}

func TestVersion(t *testing.T) {
conf := utils.Config{
Program: "../gitea",
WorkDir: "",
Args: []string{"web", "--port", ServerHTTPPort},
LogFile: os.Stderr,
}

if err := utils.New(t, &conf).RunTest(install, version); err != nil {
t.Fatal(err)
}
}

+ 1
- 0
routers/api/v1/api.go View File

@@ -232,6 +232,7 @@ func RegisterRoutes(m *macaron.Macaron) {

m.Group("/v1", func() {
// Miscellaneous
m.Get("/version", misc.Version)
m.Post("/markdown", bind(api.MarkdownOption{}), misc.Markdown)
m.Post("/markdown/raw", misc.MarkdownRaw)


+ 16
- 0
routers/api/v1/misc/version.go View File

@@ -0,0 +1,16 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package misc

import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/sdk/gitea"
)

// Version shows the version of the Gitea server
func Version(ctx *context.APIContext) {
ctx.JSON(200, &gitea.ServerVersion{Version: setting.AppVer})
}

+ 11
- 0
vendor/code.gitea.io/sdk/gitea/miscellaneous.go View File

@@ -11,3 +11,14 @@ type MarkdownOption struct {
Context string
Wiki bool
}

// ServerVersion wraps the version of the server
type ServerVersion struct {
Version string
}

// ServerVersion returns the version of the server
func (c *Client) ServerVersion() (string, error) {
v := ServerVersion{}
return v.Version, c.getParsedResponse("GET", "/api/v1/version", nil, nil, &v)
}

+ 3
- 3
vendor/vendor.json View File

@@ -9,10 +9,10 @@
"revisionTime": "2017-02-22T02:52:05Z"
},
{
"checksumSHA1": "K0VWBaa3ZUE598zVFGavdLB7vW4=",
"checksumSHA1": "qXD1HI8bTn7qNJZJOeZqQgxo354=",
"path": "code.gitea.io/sdk/gitea",
"revision": "06902fe19508c7ede2be38b71287c665efa1f10d",
"revisionTime": "2017-02-19T11:17:32Z"
"revision": "8807a1d2ced513880b288a5e2add39df6bf72144",
"revisionTime": "2017-03-04T10:22:44Z"
},
{
"checksumSHA1": "IyfS7Rbl6OgR83QR7TOfKdDCq+M=",

Loading…
Cancel
Save