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 }