github.com/robryk/drone@v0.2.1-0.20140602202253-40fe4305815d/pkg/build/build_test.go (about)

     1  package build
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"net/http/httptest"
    10  	"net/url"
    11  	"os"
    12  	"path/filepath"
    13  	"testing"
    14  
    15  	"github.com/drone/drone/pkg/build/buildfile"
    16  	"github.com/drone/drone/pkg/build/docker"
    17  	"github.com/drone/drone/pkg/build/proxy"
    18  	"github.com/drone/drone/pkg/build/repo"
    19  	"github.com/drone/drone/pkg/build/script"
    20  )
    21  
    22  var (
    23  	// mux is the HTTP request multiplexer used with the test server.
    24  	mux *http.ServeMux
    25  
    26  	// server is a test HTTP server used to provide mock API responses.
    27  	server *httptest.Server
    28  
    29  	// docker client
    30  	client *docker.Client
    31  )
    32  
    33  // setup a mock docker client for testing purposes. This will use
    34  // a test http server that can return mock responses to the docker client.
    35  func setup() {
    36  	mux = http.NewServeMux()
    37  	server = httptest.NewServer(mux)
    38  
    39  	url, _ := url.Parse(server.URL)
    40  	url.Scheme = "tcp"
    41  	os.Setenv("DOCKER_HOST", url.String())
    42  	client = docker.New()
    43  }
    44  
    45  func teardown() {
    46  	server.Close()
    47  }
    48  
    49  // TestSetup will test our ability to successfully create a Docker
    50  // image for the build.
    51  func TestSetup(t *testing.T) {
    52  	setup()
    53  	defer teardown()
    54  
    55  	// Handles a request to inspect the Go 1.2 image
    56  	// This will return a dummy image ID, so that the system knows
    57  	// the build image exists, and doens't need to be downloaded.
    58  	mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) {
    59  		body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]`
    60  		w.Write([]byte(body))
    61  	})
    62  
    63  	// Handles a request to create the build image, with the build
    64  	// script injected. This will return a dummy stream.
    65  	mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) {
    66  		body := `{"stream":"Step 1..."}`
    67  		w.Write([]byte(body))
    68  	})
    69  
    70  	// Handles a request to inspect the newly created build image. Note
    71  	// that we are doing a "wildcard" url match here, since the name of
    72  	// the image will be random. This will return a dummy image ID
    73  	// to confirm the build image was created successfully.
    74  	mux.HandleFunc("/v1.9/images/", func(w http.ResponseWriter, r *http.Request) {
    75  		body := `{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }`
    76  		w.Write([]byte(body))
    77  	})
    78  
    79  	b := Builder{}
    80  	b.Repo = &repo.Repo{}
    81  	b.Repo.Path = "git://github.com/drone/drone.git"
    82  	b.Build = &script.Build{}
    83  	b.Build.Image = "go1.2"
    84  	b.dockerClient = client
    85  
    86  	if err := b.setup(); err != nil {
    87  		t.Errorf("Expected success, got %s", err)
    88  	}
    89  
    90  	// verify the Image is being correctly set
    91  	if b.image == nil {
    92  		t.Errorf("Expected image not nil")
    93  	}
    94  
    95  	expectedID := "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c"
    96  	if b.image.ID != expectedID {
    97  		t.Errorf("Expected image.ID %s, got %s", expectedID, b.image.ID)
    98  	}
    99  }
   100  
   101  // TestSetupEmptyImage will test our ability to handle a nil or
   102  // blank Docker build image. We expect this to return an error.
   103  func TestSetupEmptyImage(t *testing.T) {
   104  	b := Builder{Build: &script.Build{}}
   105  	var got, want = b.setup(), "Error: missing Docker image"
   106  
   107  	if got == nil || got.Error() != want {
   108  		t.Errorf("Expected error %s, got %s", want, got)
   109  	}
   110  }
   111  
   112  // TestSetupErrorInspectImage will test our ability to handle a
   113  // failure when inspecting an image (i.e. bradrydzewski/mysql:latest),
   114  // which should trigger a `docker pull`.
   115  func TestSetupErrorInspectImage(t *testing.T) {
   116  	t.Skip()
   117  }
   118  
   119  // TestSetupErrorPullImage will test our ability to handle a
   120  // failure when pulling an image (i.e. bradrydzewski/mysql:latest)
   121  func TestSetupErrorPullImage(t *testing.T) {
   122  	setup()
   123  	defer teardown()
   124  
   125  	mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
   126  		w.WriteHeader(http.StatusNotFound)
   127  	})
   128  
   129  }
   130  
   131  // TestSetupErrorRunDaemonPorts will test our ability to handle a
   132  // failure when starting a service (i.e. mysql) as a daemon.
   133  func TestSetupErrorRunDaemonPorts(t *testing.T) {
   134  	setup()
   135  	defer teardown()
   136  
   137  	mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
   138  		data := []byte(`{"config": { "ExposedPorts": { "6379/tcp": {}}}}`)
   139  		w.Write(data)
   140  	})
   141  
   142  	mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
   143  		w.WriteHeader(http.StatusBadRequest)
   144  	})
   145  
   146  	b := Builder{}
   147  	b.Repo = &repo.Repo{}
   148  	b.Repo.Path = "git://github.com/drone/drone.git"
   149  	b.Build = &script.Build{}
   150  	b.Build.Image = "go1.2"
   151  	b.Build.Services = append(b.Build.Services, "mysql")
   152  	b.dockerClient = client
   153  
   154  	var got, want = b.setup(), docker.ErrBadRequest
   155  	if got == nil || got != want {
   156  		t.Errorf("Expected error %s, got %s", want, got)
   157  	}
   158  }
   159  
   160  // TestSetupErrorServiceInspect will test our ability to handle a
   161  // failure when a service (i.e. mysql) is started successfully,
   162  // but cannot be queried post-start with the Docker remote API.
   163  func TestSetupErrorServiceInspect(t *testing.T) {
   164  	setup()
   165  	defer teardown()
   166  
   167  	mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
   168  		data := []byte(`{"config": { "ExposedPorts": { "6379/tcp": {}}}}`)
   169  		w.Write(data)
   170  	})
   171  
   172  	mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
   173  		body := `{ "Id":"e90e34656806", "Warnings":[] }`
   174  		w.Write([]byte(body))
   175  	})
   176  
   177  	mux.HandleFunc("/v1.9/containers/e90e34656806/start", func(w http.ResponseWriter, r *http.Request) {
   178  		w.WriteHeader(http.StatusNoContent)
   179  	})
   180  
   181  	mux.HandleFunc("/v1.9/containers/e90e34656806/json", func(w http.ResponseWriter, r *http.Request) {
   182  		w.WriteHeader(http.StatusBadRequest)
   183  	})
   184  
   185  	b := Builder{}
   186  	b.Repo = &repo.Repo{}
   187  	b.Repo.Path = "git://github.com/drone/drone.git"
   188  	b.Build = &script.Build{}
   189  	b.Build.Image = "go1.2"
   190  	b.Build.Services = append(b.Build.Services, "mysql")
   191  	b.dockerClient = client
   192  
   193  	var got, want = b.setup(), docker.ErrBadRequest
   194  	if got == nil || got != want {
   195  		t.Errorf("Expected error %s, got %s", want, got)
   196  	}
   197  }
   198  
   199  // TestSetupErrorImagePull will test our ability to handle a
   200  // failure when a the build image cannot be pulled from the index.
   201  func TestSetupErrorImagePull(t *testing.T) {
   202  	setup()
   203  	defer teardown()
   204  
   205  	mux.HandleFunc("/v1.9/images/bradrydzewski/mysql:5.5/json", func(w http.ResponseWriter, r *http.Request) {
   206  		w.WriteHeader(http.StatusNotFound)
   207  	})
   208  
   209  	mux.HandleFunc("/v1.9/images/create?fromImage=bradrydzewski/mysql&tag=5.5", func(w http.ResponseWriter, r *http.Request) {
   210  		w.WriteHeader(http.StatusBadRequest)
   211  	})
   212  
   213  	b := Builder{}
   214  	b.Repo = &repo.Repo{}
   215  	b.Repo.Path = "git://github.com/drone/drone.git"
   216  	b.Build = &script.Build{}
   217  	b.Build.Image = "go1.2"
   218  	b.Build.Services = append(b.Build.Services, "mysql")
   219  	b.dockerClient = client
   220  
   221  	var got, want = b.setup(), fmt.Errorf("Error: Unable to pull image bradrydzewski/mysql:5.5")
   222  	if got == nil || got.Error() != want.Error() {
   223  		t.Errorf("Expected error %s, got %s", want, got)
   224  	}
   225  }
   226  
   227  // TestSetupErrorBuild will test our ability to handle a failure
   228  // when creating a Docker image with the injected build script,
   229  // ssh keys, etc.
   230  func TestSetupErrorBuild(t *testing.T) {
   231  	setup()
   232  	defer teardown()
   233  
   234  	mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) {
   235  		body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]`
   236  		w.Write([]byte(body))
   237  	})
   238  
   239  	mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) {
   240  		w.WriteHeader(http.StatusBadRequest)
   241  	})
   242  
   243  	b := Builder{}
   244  	b.Repo = &repo.Repo{}
   245  	b.Repo.Path = "git://github.com/drone/drone.git"
   246  	b.Build = &script.Build{}
   247  	b.Build.Image = "go1.2"
   248  	b.dockerClient = client
   249  
   250  	var got, want = b.setup(), docker.ErrBadRequest
   251  	if got == nil || got != want {
   252  		t.Errorf("Expected error %s, got %s", want, got)
   253  	}
   254  }
   255  
   256  // TestSetupErrorBuildInspect will test our ability to handle a failure
   257  // when we successfully create a Docker image with the injected build script,
   258  // ssh keys, etc, however, we cannot inspect it post-creation using
   259  // the Docker remote API.
   260  func TestSetupErrorBuildInspect(t *testing.T) {
   261  	setup()
   262  	defer teardown()
   263  
   264  	mux.HandleFunc("/v1.9/images/bradrydzewski/go:1.2/json", func(w http.ResponseWriter, r *http.Request) {
   265  		body := `[{ "id": "7bf9ce0ffb7236ca68da0f9fed0e1682053b393db3c724ff3c5a4e8c0793b34c" }]`
   266  		w.Write([]byte(body))
   267  	})
   268  
   269  	mux.HandleFunc("/v1.9/build", func(w http.ResponseWriter, r *http.Request) {
   270  		body := `{"stream":"Step 1..."}`
   271  		w.Write([]byte(body))
   272  	})
   273  
   274  	mux.HandleFunc("/v1.9/images/", func(w http.ResponseWriter, r *http.Request) {
   275  		w.WriteHeader(http.StatusBadRequest)
   276  	})
   277  
   278  	b := Builder{}
   279  	b.Repo = &repo.Repo{}
   280  	b.Repo.Path = "git://github.com/drone/drone.git"
   281  	b.Build = &script.Build{}
   282  	b.Build.Image = "go1.2"
   283  	b.dockerClient = client
   284  
   285  	var got, want = b.setup(), docker.ErrBadRequest
   286  	if got == nil || got != want {
   287  		t.Errorf("Expected error %s, got %s", want, got)
   288  	}
   289  }
   290  
   291  // TestTeardown will test our ability to sucessfully teardown a
   292  // Docker-based build environment.
   293  func TestTeardown(t *testing.T) {
   294  	setup()
   295  	defer teardown()
   296  
   297  	var (
   298  		containerStopped = false
   299  		containerRemoved = false
   300  		serviceStopped   = false
   301  		serviceRemoved   = false
   302  		imageRemoved     = false
   303  	)
   304  
   305  	mux.HandleFunc("/v1.9/containers/7bf9ce0ffb/stop", func(w http.ResponseWriter, r *http.Request) {
   306  		containerStopped = true
   307  		w.WriteHeader(http.StatusOK)
   308  	})
   309  
   310  	mux.HandleFunc("/v1.9/containers/7bf9ce0ffb", func(w http.ResponseWriter, r *http.Request) {
   311  		containerRemoved = true
   312  		w.WriteHeader(http.StatusOK)
   313  	})
   314  
   315  	mux.HandleFunc("/v1.9/containers/ec62dcc736/stop", func(w http.ResponseWriter, r *http.Request) {
   316  		serviceStopped = true
   317  		w.WriteHeader(http.StatusOK)
   318  	})
   319  
   320  	mux.HandleFunc("/v1.9/containers/ec62dcc736", func(w http.ResponseWriter, r *http.Request) {
   321  		serviceRemoved = true
   322  		w.WriteHeader(http.StatusOK)
   323  	})
   324  
   325  	mux.HandleFunc("/v1.9/images/c3ab8ff137", func(w http.ResponseWriter, r *http.Request) {
   326  		imageRemoved = true
   327  		w.Write([]byte(`[{"Untagged":"c3ab8ff137"},{"Deleted":"c3ab8ff137"}]`))
   328  	})
   329  
   330  	b := Builder{}
   331  	b.dockerClient = client
   332  	b.services = append(b.services, &docker.Container{ID: "ec62dcc736"})
   333  	b.container = &docker.Run{ID: "7bf9ce0ffb"}
   334  	b.image = &docker.Image{ID: "c3ab8ff137"}
   335  	b.Build = &script.Build{Services: []string{"mysql"}}
   336  	b.teardown()
   337  
   338  	if !containerStopped {
   339  		t.Errorf("Expected Docker container was stopped")
   340  	}
   341  
   342  	if !containerRemoved {
   343  		t.Errorf("Expected Docker container was removed")
   344  	}
   345  
   346  	if !serviceStopped {
   347  		t.Errorf("Expected Docker mysql container was stopped")
   348  	}
   349  
   350  	if !serviceRemoved {
   351  		t.Errorf("Expected Docker mysql container was removed")
   352  	}
   353  
   354  	if !imageRemoved {
   355  		t.Errorf("Expected Docker image was removed")
   356  	}
   357  }
   358  
   359  func TestRun(t *testing.T) {
   360  	t.Skip()
   361  }
   362  
   363  func TestRunPrivileged(t *testing.T) {
   364  	setup()
   365  	defer teardown()
   366  
   367  	var conf = docker.HostConfig{}
   368  
   369  	mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
   370  		body := `{ "Id":"e90e34656806", "Warnings":[] }`
   371  		w.Write([]byte(body))
   372  	})
   373  
   374  	mux.HandleFunc("/v1.9/containers/e90e34656806/start", func(w http.ResponseWriter, r *http.Request) {
   375  		json.NewDecoder(r.Body).Decode(&conf)
   376  		w.WriteHeader(http.StatusBadRequest)
   377  	})
   378  
   379  	b := Builder{}
   380  	b.BuildState = &BuildState{}
   381  	b.dockerClient = client
   382  	b.Stdout = new(bytes.Buffer)
   383  	b.image = &docker.Image{ID: "c3ab8ff137"}
   384  	b.Build = &script.Build{}
   385  	b.Repo = &repo.Repo{}
   386  	b.run()
   387  
   388  	if conf.Privileged != false {
   389  		t.Errorf("Expected container NOT started in Privileged mode")
   390  	}
   391  
   392  	// now lets set priviliged mode
   393  	b.Privileged = true
   394  	b.run()
   395  
   396  	if conf.Privileged != true {
   397  		t.Errorf("Expected container IS started in Privileged mode")
   398  	}
   399  
   400  	// now lets set priviliged mode but for a pull request
   401  	b.Privileged = true
   402  	b.Repo.PR = "55"
   403  	b.run()
   404  
   405  	if conf.Privileged != false {
   406  		t.Errorf("Expected container NOT started in Privileged mode when PR")
   407  	}
   408  }
   409  
   410  func TestRunErrorCreate(t *testing.T) {
   411  	setup()
   412  	defer teardown()
   413  
   414  	mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
   415  		w.WriteHeader(http.StatusBadRequest)
   416  	})
   417  
   418  	b := Builder{}
   419  	b.BuildState = &BuildState{}
   420  	b.dockerClient = client
   421  	b.Stdout = new(bytes.Buffer)
   422  	b.image = &docker.Image{ID: "c3ab8ff137"}
   423  	b.Build = &script.Build{}
   424  	b.Repo = &repo.Repo{}
   425  
   426  	if err := b.run(); err != docker.ErrBadRequest {
   427  		t.Errorf("Expected error when trying to create build container")
   428  	}
   429  }
   430  
   431  func TestRunErrorStart(t *testing.T) {
   432  	setup()
   433  	defer teardown()
   434  
   435  	var (
   436  		containerCreated = false
   437  		containerStarted = false
   438  	)
   439  
   440  	mux.HandleFunc("/v1.9/containers/create", func(w http.ResponseWriter, r *http.Request) {
   441  		containerCreated = true
   442  		body := `{ "Id":"e90e34656806", "Warnings":[] }`
   443  		w.Write([]byte(body))
   444  	})
   445  
   446  	mux.HandleFunc("/v1.9/containers/e90e34656806/start", func(w http.ResponseWriter, r *http.Request) {
   447  		containerStarted = true
   448  		w.WriteHeader(http.StatusBadRequest)
   449  	})
   450  
   451  	b := Builder{}
   452  	b.BuildState = &BuildState{}
   453  	b.dockerClient = client
   454  	b.Stdout = new(bytes.Buffer)
   455  	b.image = &docker.Image{ID: "c3ab8ff137"}
   456  	b.Build = &script.Build{}
   457  	b.Repo = &repo.Repo{}
   458  
   459  	if err := b.run(); err != docker.ErrBadRequest {
   460  		t.Errorf("Expected error when trying to start build container")
   461  	}
   462  
   463  	if !containerCreated {
   464  		t.Errorf("Expected Docker endpoint was invoked to create container")
   465  	}
   466  
   467  	if !containerStarted {
   468  		t.Errorf("Expected Docker endpoint was invoked to start container")
   469  	}
   470  
   471  	if b.container == nil || b.container.ID != "e90e34656806" {
   472  		t.Errorf("Expected build container was created with ID e90e34656806")
   473  	}
   474  }
   475  
   476  func TestRunErrorWait(t *testing.T) {
   477  	t.Skip()
   478  }
   479  
   480  func TestWriteIdentifyFile(t *testing.T) {
   481  	// temporary directory to store file
   482  	dir, _ := ioutil.TempDir("", "drone-test-")
   483  	defer os.RemoveAll(dir)
   484  
   485  	b := Builder{}
   486  	b.Key = []byte("ssh-rsa AAA...")
   487  	b.writeIdentifyFile(dir)
   488  
   489  	// persist a dummy id_rsa keyfile to disk
   490  	keyfile, err := ioutil.ReadFile(filepath.Join(dir, "id_rsa"))
   491  	if err != nil {
   492  		t.Errorf("Expected id_rsa file saved to disk")
   493  	}
   494  
   495  	if string(keyfile) != string(b.Key) {
   496  		t.Errorf("Expected id_rsa value saved as %s, got %s", b.Key, keyfile)
   497  	}
   498  }
   499  
   500  func TestWriteProxyScript(t *testing.T) {
   501  	// temporary directory to store file
   502  	dir, _ := ioutil.TempDir("", "drone-test-")
   503  	defer os.RemoveAll(dir)
   504  
   505  	// fake service container that we'll assume was part of the yaml
   506  	// and should be attached to the build container.
   507  	c := docker.Container{
   508  		NetworkSettings: &docker.NetworkSettings{
   509  			IPAddress: "172.1.4.5",
   510  			Ports: map[docker.Port][]docker.PortBinding{
   511  				docker.NewPort("tcp", "3306"): nil,
   512  			},
   513  		},
   514  	}
   515  
   516  	// this should generate the following proxy file
   517  	p := proxy.Proxy{}
   518  	p.Set("3306", "172.1.4.5")
   519  	want := p.String()
   520  
   521  	b := Builder{}
   522  	b.services = append(b.services, &c)
   523  	b.writeProxyScript(dir)
   524  
   525  	// persist a dummy proxy script to disk
   526  	got, err := ioutil.ReadFile(filepath.Join(dir, "proxy.sh"))
   527  	if err != nil {
   528  		t.Errorf("Expected proxy.sh file saved to disk")
   529  	}
   530  
   531  	if string(got) != want {
   532  		t.Errorf("Expected proxy.sh value saved as %s, got %s", want, got)
   533  	}
   534  }
   535  
   536  func TestWriteBuildScript(t *testing.T) {
   537  	// temporary directory to store file
   538  	dir, _ := ioutil.TempDir("", "drone-test-")
   539  	defer os.RemoveAll(dir)
   540  
   541  	b := Builder{}
   542  	b.Build = &script.Build{
   543  		Hosts: []string{"127.0.0.1"}}
   544  	b.Repo = &repo.Repo{
   545  		Path:   "git://github.com/drone/drone.git",
   546  		Branch: "master",
   547  		Commit: "e7e046b35",
   548  		PR:     "123",
   549  		Dir:    "/var/cache/drone/github.com/drone/drone"}
   550  	b.writeBuildScript(dir)
   551  
   552  	// persist a dummy build script to disk
   553  	script, err := ioutil.ReadFile(filepath.Join(dir, "drone"))
   554  	if err != nil {
   555  		t.Errorf("Expected id_rsa file saved to disk")
   556  	}
   557  
   558  	f := buildfile.New()
   559  	f.WriteEnv("CI", "true")
   560  	f.WriteEnv("DRONE", "true")
   561  	f.WriteEnv("DRONE_BRANCH", "master")
   562  	f.WriteEnv("DRONE_COMMIT", "e7e046b35")
   563  	f.WriteEnv("DRONE_PR", "123")
   564  	f.WriteEnv("DRONE_BUILD_DIR", "/var/cache/drone/github.com/drone/drone")
   565  	f.WriteEnv("CI_NAME", "DRONE")
   566  	f.WriteEnv("CI_BUILD_NUMBER", "e7e046b35")
   567  	f.WriteEnv("CI_BUILD_URL", "")
   568  	f.WriteEnv("CI_BRANCH", "master")
   569  	f.WriteEnv("CI_PULL_REQUEST", "123")
   570  	f.WriteHost("127.0.0.1")
   571  	f.WriteCmd("git clone --depth=0 --recursive --branch=master git://github.com/drone/drone.git /var/cache/drone/github.com/drone/drone")
   572  	f.WriteCmd("git fetch origin +refs/pull/123/head:refs/remotes/origin/pr/123")
   573  	f.WriteCmd("git checkout -qf -b pr/123 origin/pr/123")
   574  
   575  	if string(script) != f.String() {
   576  		t.Errorf("Expected build script value saved as %s, got %s", f.String(), script)
   577  	}
   578  }