d4e62101 |
package docker
import ( |
c001a5af |
"bytes" |
ed8f75d2 |
"fmt" |
d4e62101 |
"io"
"io/ioutil" |
afe23071 |
"net/http"
"net/http/httptest" |
d4e62101 |
"os"
"path" |
6e92dfdf |
"path/filepath" |
d4e62101 |
"strings"
"testing" |
afe23071 |
"time" |
92e61f89 |
|
b3ee9ac7 |
"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" |
e304e3a6 |
|
b3ee9ac7 |
"github.com/docker/docker/builtins"
"github.com/docker/docker/daemon"
"github.com/docker/docker/engine" |
9aa71549 |
flag "github.com/docker/docker/pkg/mflag" |
568f86eb |
"github.com/docker/docker/registry" |
b3ee9ac7 |
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils" |
d4e62101 |
)
|
7c62cee5 |
type Fataler interface {
Fatal(...interface{})
}
|
d4e62101 |
// This file contains utility functions for docker's unit test suite.
// It has to be named XXX_test.go, apparently, in other to access private functions
// from other XXX_test.go functions.
|
359b7df5 |
// Create a temporary daemon suitable for unit testing. |
d4e62101 |
// Call t.Fatal() at the first error. |
7c62cee5 |
func mkDaemon(f Fataler) *daemon.Daemon { |
92c3927b |
eng := newTestEngine(f, false, "") |
359b7df5 |
return mkDaemonFromEngine(eng, f) |
d4e62101 |
}
|
7c62cee5 |
func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) { |
e5f8ab61 |
job := eng.Job("create", name)
if err := job.ImportEnv(config); err != nil {
f.Fatal(err)
} |
e304e3a6 |
var outputBuffer = bytes.NewBuffer(nil)
job.Stdout.Add(outputBuffer) |
e5f8ab61 |
if err := job.Run(); err != nil {
f.Fatal(err)
} |
e304e3a6 |
return engine.Tail(outputBuffer, 1) |
6bdb6f22 |
}
|
7c62cee5 |
func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler) (shortId string) { |
e5f8ab61 |
return createNamedTestContainer(eng, config, f, "")
}
|
7c62cee5 |
func startContainer(eng *engine.Engine, id string, t Fataler) { |
afe23071 |
job := eng.Job("start", id)
if err := job.Run(); err != nil {
t.Fatal(err)
}
}
|
7c62cee5 |
func containerRun(eng *engine.Engine, id string, t Fataler) { |
afe23071 |
startContainer(eng, id, t)
containerWait(eng, id, t)
}
|
7c62cee5 |
func containerFileExists(eng *engine.Engine, id, dir string, t Fataler) bool { |
afe23071 |
c := getContainer(eng, id, t) |
191aa17d |
if err := c.Mount(); err != nil { |
afe23071 |
t.Fatal(err)
} |
bcaf6c23 |
defer c.Unmount() |
bf1b27df |
if _, err := os.Stat(path.Join(c.RootfsPath(), dir)); err != nil { |
afe23071 |
if os.IsNotExist(err) {
return false
}
t.Fatal(err)
}
return true
}
|
7c62cee5 |
func containerAttach(eng *engine.Engine, id string, t Fataler) (io.WriteCloser, io.ReadCloser) { |
afe23071 |
c := getContainer(eng, id, t) |
21e44d7a |
i := c.StdinPipe()
o := c.StdoutPipe() |
afe23071 |
return i, o
}
|
7c62cee5 |
func containerWait(eng *engine.Engine, id string, t Fataler) int { |
e0339d4b |
ex, _ := getContainer(eng, id, t).WaitStop(-1 * time.Second) |
57d86a56 |
return ex |
afe23071 |
}
|
7c62cee5 |
func containerWaitTimeout(eng *engine.Engine, id string, t Fataler) error { |
e0339d4b |
_, err := getContainer(eng, id, t).WaitStop(500 * time.Millisecond) |
57d86a56 |
return err |
afe23071 |
}
|
7c62cee5 |
func containerKill(eng *engine.Engine, id string, t Fataler) { |
2546a2c6 |
if err := eng.Job("kill", id).Run(); err != nil { |
afe23071 |
t.Fatal(err)
}
}
|
7c62cee5 |
func containerRunning(eng *engine.Engine, id string, t Fataler) bool { |
e0339d4b |
return getContainer(eng, id, t).IsRunning() |
afe23071 |
}
|
7c62cee5 |
func containerAssertExists(eng *engine.Engine, id string, t Fataler) { |
afe23071 |
getContainer(eng, id, t)
}
|
7c62cee5 |
func containerAssertNotExists(eng *engine.Engine, id string, t Fataler) { |
359b7df5 |
daemon := mkDaemonFromEngine(eng, t) |
d25a6537 |
if c, _ := daemon.Get(id); c != nil { |
afe23071 |
t.Fatal(fmt.Errorf("Container %s should not exist", id))
}
}
// assertHttpNotError expect the given response to not have an error.
// Otherwise the it causes the test to fail. |
7c62cee5 |
func assertHttpNotError(r *httptest.ResponseRecorder, t Fataler) { |
afe23071 |
// Non-error http status are [200, 400)
if r.Code < http.StatusOK || r.Code >= http.StatusBadRequest {
t.Fatal(fmt.Errorf("Unexpected http error: %v", r.Code))
}
}
// assertHttpError expect the given response to have an error.
// Otherwise the it causes the test to fail. |
7c62cee5 |
func assertHttpError(r *httptest.ResponseRecorder, t Fataler) { |
afe23071 |
// Non-error http status are [200, 400)
if !(r.Code < http.StatusOK || r.Code >= http.StatusBadRequest) {
t.Fatal(fmt.Errorf("Unexpected http success code: %v", r.Code))
}
}
|
7c62cee5 |
func getContainer(eng *engine.Engine, id string, t Fataler) *daemon.Container { |
359b7df5 |
daemon := mkDaemonFromEngine(eng, t) |
d25a6537 |
c, err := daemon.Get(id)
if err != nil {
t.Fatal(err) |
afe23071 |
}
return c
}
|
7c62cee5 |
func mkDaemonFromEngine(eng *engine.Engine, t Fataler) *daemon.Daemon { |
359b7df5 |
iDaemon := eng.Hack_GetGlobalVar("httpapi.daemon")
if iDaemon == nil {
panic("Legacy daemon field not set in engine") |
c001a5af |
} |
359b7df5 |
daemon, ok := iDaemon.(*daemon.Daemon) |
c001a5af |
if !ok { |
359b7df5 |
panic("Legacy daemon field in engine does not cast to *daemon.Daemon") |
c001a5af |
} |
359b7df5 |
return daemon |
c001a5af |
}
|
7c62cee5 |
func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine { |
92c3927b |
if root == "" {
if dir, err := newTestDirectory(unitTestStoreBase); err != nil {
t.Fatal(err)
} else {
root = dir
} |
6bdb6f22 |
} |
87e8d775 |
os.MkdirAll(root, 0700) |
7100ace4 |
eng := engine.New() |
8a9e8272 |
eng.Logging = false |
5a85456d |
// Load default plugins |
568f86eb |
if err := builtins.Register(eng); err != nil {
t.Fatal(err)
}
// load registry service
if err := registry.NewService(nil).Install(eng); err != nil {
t.Fatal(err)
}
|
5a85456d |
// (This is manually copied and modified from main() until we have a more generic plugin system) |
a4befff5 |
cfg := &daemon.Config{ |
63503caf |
Root: root,
AutoRestart: autorestart,
ExecDriver: "native", |
a4befff5 |
// Either InterContainerCommunication or EnableIptables must be set,
// otherwise NewDaemon will fail because of conflicting settings.
InterContainerCommunication: true, |
6e92dfdf |
TrustKeyPath: filepath.Join(root, "key.json"), |
47a6afb9 |
LogConfig: runconfig.LogConfig{Type: "json-file"}, |
63503caf |
}
d, err := daemon.NewDaemon(cfg, eng)
if err != nil {
t.Fatal(err)
}
if err := d.Install(eng); err != nil { |
5a85456d |
t.Fatal(err) |
6bdb6f22 |
} |
5a85456d |
return eng
} |
6bdb6f22 |
|
7c62cee5 |
func NewTestEngine(t Fataler) *engine.Engine { |
92c3927b |
return newTestEngine(t, false, "")
}
|
5a85456d |
func newTestDirectory(templateDir string) (dir string, err error) { |
c001a5af |
return utils.TestDirectory(templateDir) |
5a85456d |
}
func getCallerName(depth int) string { |
c001a5af |
return utils.GetCallerName(depth) |
6bdb6f22 |
}
|
d4e62101 |
// Write `content` to the file at path `dst`, creating it if necessary,
// as well as any missing directories.
// The file is truncated if it already exists.
// Call t.Fatal() at the first error.
func writeFile(dst, content string, t *testing.T) {
// Create subdirectories if necessary
if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) {
t.Fatal(err)
} |
46a9f29b |
f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) |
d4e62101 |
if err != nil {
t.Fatal(err)
}
// Write content (truncate if it exists)
if _, err := io.Copy(f, strings.NewReader(content)); err != nil {
t.Fatal(err)
}
}
// Return the contents of file at path `src`.
// Call t.Fatal() at the first error (including if the file doesn't exist)
func readFile(src string, t *testing.T) (content string) {
f, err := os.Open(src)
if err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadAll(f)
if err != nil {
t.Fatal(err)
}
return string(data)
}
|
359b7df5 |
// Create a test container from the given daemon `r` and run arguments `args`. |
080243f0 |
// If the image name is "_", (eg. []string{"-i", "-t", "_", "bash"}, it is
// dynamically replaced by the current test image. |
d4e62101 |
// The caller is responsible for destroying the container.
// Call t.Fatal() at the first error. |
359b7df5 |
func mkContainer(r *daemon.Daemon, args []string, t *testing.T) (*daemon.Container, *runconfig.HostConfig, error) { |
e45b0f92 |
config, hc, _, err := parseRun(args) |
080243f0 |
defer func() {
if err != nil && t != nil {
t.Fatal(err)
}
}() |
d4e62101 |
if err != nil { |
661a8a0e |
return nil, nil, err |
080243f0 |
}
if config.Image == "_" {
config.Image = GetTestImage(r).ID |
d4e62101 |
} |
1df87b95 |
c, _, err := r.Create(config, nil, "") |
d4e62101 |
if err != nil { |
661a8a0e |
return nil, nil, err |
d4e62101 |
} |
c001a5af |
// NOTE: hostConfig is ignored.
// If `args` specify privileged mode, custom lxc conf, external mount binds,
// port redirects etc. they will be ignored.
// This is because the correct way to set these things is to pass environment
// to the `start` job.
// FIXME: this helper function should be deprecated in favor of calling
// `create` and `start` jobs directly. |
661a8a0e |
return c, hc, nil |
d4e62101 |
}
// Create a test container, start it, wait for it to complete, destroy it,
// and return its standard output as a string. |
46a9f29b |
// The image name (eg. the XXX in []string{"-i", "-t", "XXX", "bash"}, is dynamically replaced by the current test image. |
d4e62101 |
// If t is not nil, call t.Fatal() at the first error. Otherwise return errors normally. |
359b7df5 |
func runContainer(eng *engine.Engine, r *daemon.Daemon, args []string, t *testing.T) (output string, err error) { |
d4e62101 |
defer func() {
if err != nil && t != nil {
t.Fatal(err)
}
}() |
661a8a0e |
container, hc, err := mkContainer(r, args, t) |
080243f0 |
if err != nil {
return "", err
} |
ba93f831 |
defer r.Rm(container) |
21e44d7a |
stdout := container.StdoutPipe() |
d4e62101 |
defer stdout.Close() |
661a8a0e |
job := eng.Job("start", container.ID)
if err := job.ImportEnv(hc); err != nil { |
d4e62101 |
return "", err
} |
661a8a0e |
if err := job.Run(); err != nil {
return "", err
}
|
e0339d4b |
container.WaitStop(-1 * time.Second) |
d4e62101 |
data, err := ioutil.ReadAll(stdout)
if err != nil {
return "", err
}
output = string(data)
return
} |
193a7e1d |
|
c001a5af |
// FIXME: this is duplicated from graph_test.go in the docker package. |
f198ee52 |
func fakeTar() (io.ReadCloser, error) { |
c001a5af |
content := []byte("Hello world!\n")
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
for _, name := range []string{"/etc/postgres/postgres.conf", "/etc/passwd", "/var/log/postgres/postgres.conf"} {
hdr := new(tar.Header)
hdr.Size = int64(len(content))
hdr.Name = name
if err := tw.WriteHeader(hdr); err != nil {
return nil, err |
551092f9 |
} |
c001a5af |
tw.Write([]byte(content)) |
551092f9 |
} |
c001a5af |
tw.Close() |
f198ee52 |
return ioutil.NopCloser(buf), nil |
551092f9 |
} |
17a806c8 |
func getAllImages(eng *engine.Engine, t *testing.T) *engine.Table {
return getImages(eng, t, true, "")
}
func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) *engine.Table {
job := eng.Job("images")
job.SetenvBool("all", all)
job.Setenv("filter", filter) |
e3461bc8 |
images, err := job.Stdout.AddListTable() |
17a806c8 |
if err != nil {
t.Fatal(err)
}
if err := job.Run(); err != nil {
t.Fatal(err)
}
return images
} |
9aa71549 |
|
e45b0f92 |
func parseRun(args []string) (*runconfig.Config, *runconfig.HostConfig, *flag.FlagSet, error) { |
9aa71549 |
cmd := flag.NewFlagSet("run", flag.ContinueOnError)
cmd.SetOutput(ioutil.Discard)
cmd.Usage = nil |
e45b0f92 |
return runconfig.Parse(cmd, args) |
9aa71549 |
} |