github.com/localhostbase/localhostdev@v1.4.1/pkg/ddevapp/ddevapp_test.go (about) 1 package ddevapp_test 2 3 import ( 4 "fmt" 5 "github.com/stretchr/testify/require" 6 "io/ioutil" 7 "os" 8 "path" 9 "path/filepath" 10 "regexp" 11 "runtime" 12 "strconv" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/drud/ddev/pkg/archive" 18 "github.com/drud/ddev/pkg/ddevapp" 19 "github.com/drud/ddev/pkg/dockerutil" 20 "github.com/drud/ddev/pkg/exec" 21 "github.com/drud/ddev/pkg/fileutil" 22 "github.com/drud/ddev/pkg/output" 23 "github.com/drud/ddev/pkg/testcommon" 24 "github.com/drud/ddev/pkg/util" 25 "github.com/drud/ddev/pkg/version" 26 27 "github.com/fsouza/go-dockerclient" 28 "github.com/google/uuid" 29 "github.com/lunixbochs/vtclean" 30 log "github.com/sirupsen/logrus" 31 asrt "github.com/stretchr/testify/assert" 32 ) 33 34 var ( 35 TestSites = []testcommon.TestSite{ 36 { 37 Name: "TestPkgWordpress", 38 SourceURL: "https://github.com/drud/wordpress/archive/v0.4.0.tar.gz", 39 ArchiveInternalExtractionPath: "wordpress-0.4.0/", 40 FilesTarballURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.0/wordpress_files.tar.gz", 41 DBTarURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.0/wordpress_db.tar.gz", 42 Docroot: "htdocs", 43 Type: ddevapp.AppTypeWordPress, 44 Safe200URIWithExpectation: testcommon.URIWithExpect{URI: "/readme.html", Expect: "Welcome. WordPress is a very special project to me."}, 45 DynamicURI: testcommon.URIWithExpect{URI: "/", Expect: "this post has a photo"}, 46 FilesImageURI: "/wp-content/uploads/2017/04/pexels-photo-265186-1024x683.jpeg", 47 }, 48 { 49 Name: "TestPkgDrupal8", 50 SourceURL: "https://ftp.drupal.org/files/projects/drupal-8.6.1.tar.gz", 51 ArchiveInternalExtractionPath: "drupal-8.6.1/", 52 FilesTarballURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.1/drupal8_6_1_files.tar.gz", 53 FilesZipballURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.0/drupal8_files.zip", 54 DBTarURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.1/drupal8_6_1_db.tar.gz", 55 DBZipURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.0/drupal8_db.zip", 56 FullSiteTarballURL: "", 57 Type: ddevapp.AppTypeDrupal8, 58 Docroot: "", 59 Safe200URIWithExpectation: testcommon.URIWithExpect{URI: "/README.txt", Expect: "Drupal is an open source content management platform"}, 60 DynamicURI: testcommon.URIWithExpect{URI: "/node/1", Expect: "this is a post with an image"}, 61 FilesImageURI: "/sites/default/files//2017-04/pexels-photo-265186.jpeg", 62 }, 63 { 64 Name: "TestPkgDrupal7", // Drupal Kickstart on D7 65 SourceURL: "https://ftp.drupal.org/files/projects/drupal-7.61.tar.gz", 66 ArchiveInternalExtractionPath: "drupal-7.61/", 67 FilesTarballURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.1/d7test-7.59.files.tar.gz", 68 DBTarURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.1/d7test-7.59-db.tar.gz", 69 FullSiteTarballURL: "", 70 Docroot: "", 71 Type: ddevapp.AppTypeDrupal7, 72 Safe200URIWithExpectation: testcommon.URIWithExpect{URI: "/README.txt", Expect: "Drupal is an open source content management platform"}, 73 DynamicURI: testcommon.URIWithExpect{URI: "/node/1", Expect: "D7 test project, kittens edition"}, 74 FilesImageURI: "/sites/default/files/field/image/kittens-large.jpg", 75 FullSiteArchiveExtPath: "docroot/sites/default/files", 76 }, 77 { 78 Name: "TestPkgDrupal6", 79 SourceURL: "https://ftp.drupal.org/files/projects/drupal-6.38.tar.gz", 80 ArchiveInternalExtractionPath: "drupal-6.38/", 81 DBTarURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.1/drupal6.38_db.tar.gz", 82 FullSiteTarballURL: "", 83 FilesTarballURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.1/drupal6_files.tar.gz", 84 Docroot: "", 85 Type: ddevapp.AppTypeDrupal6, 86 Safe200URIWithExpectation: testcommon.URIWithExpect{URI: "/CHANGELOG.txt", Expect: "Drupal 6.38, 2016-02-24"}, 87 DynamicURI: testcommon.URIWithExpect{URI: "/node/2", Expect: "This is a story. The story is somewhat shaky"}, 88 FilesImageURI: "/sites/default/files/garland_logo.jpg", 89 }, 90 { 91 Name: "TestPkgBackdrop", 92 SourceURL: "https://github.com/backdrop/backdrop/archive/1.11.0.tar.gz", 93 ArchiveInternalExtractionPath: "backdrop-1.11.0/", 94 DBTarURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.1/backdrop_db.11.0.tar.gz", 95 FilesTarballURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.1/backdrop_files.11.0.tar.gz", 96 FullSiteTarballURL: "", 97 Docroot: "", 98 Type: ddevapp.AppTypeBackdrop, 99 Safe200URIWithExpectation: testcommon.URIWithExpect{URI: "/README.md", Expect: "Backdrop is a full-featured content management system"}, 100 DynamicURI: testcommon.URIWithExpect{URI: "/posts/first-post-all-about-kittens", Expect: "Lots of kittens are a good thing"}, 101 FilesImageURI: "/files/styles/large/public/field/image/kittens-large.jpg", 102 }, 103 { 104 Name: "TestPkgTypo3", 105 SourceURL: "https://github.com/drud/typo3-v9-test/archive/v0.2.2.tar.gz", 106 ArchiveInternalExtractionPath: "typo3-v9-test-0.2.2/", 107 DBTarURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.1/typo3_v9.5_introduction_db.tar.gz", 108 FilesTarballURL: "https://github.com/drud/ddev_test_tarballs/releases/download/v1.1/typo3_v9.5_introduction_files.tar.gz", 109 FullSiteTarballURL: "", 110 Docroot: "public", 111 Type: ddevapp.AppTypeTYPO3, 112 Safe200URIWithExpectation: testcommon.URIWithExpect{URI: "/README.txt", Expect: "junk readme simply for reading"}, 113 DynamicURI: testcommon.URIWithExpect{URI: "/index.php?id=65", Expect: "Boxed Content"}, 114 FilesImageURI: "/fileadmin/introduction/images/streets/nikita-maru-70928.jpg", 115 }, 116 } 117 FullTestSites = TestSites 118 ) 119 120 func TestMain(m *testing.M) { 121 output.LogSetUp() 122 123 // Ensure the ddev directory is created before tests run. 124 _ = util.GetGlobalDdevDir() 125 126 // Since this may be first time ddev has been used, we need the 127 // ddev_default network available. 128 dockerutil.EnsureDdevNetwork() 129 130 // Avoid having sudo try to add to /etc/hosts. 131 // This is normally done by Testsite.Prepare() 132 _ = os.Setenv("DRUD_NONINTERACTIVE", "true") 133 134 // Attempt to remove all running containers before starting a test. 135 // If no projects are running, this will exit silently and without error. 136 // If a system doesn't have `ddev` in its $PATH, this will emit a warning but will not fail the test. 137 if _, err := exec.RunCommand("ddev", []string{"remove", "--all", "--stop-ssh-agent"}); err != nil { 138 log.Warnf("Failed to remove all running projects: %v", err) 139 } 140 141 count := len(ddevapp.GetApps()) 142 if count > 0 { 143 log.Fatalf("ddevapp tests require no projects running. You have %v project(s) running.", count) 144 } 145 146 // If GOTEST_SHORT is an integer, then use it as index for a single usage 147 // in the array. Any value can be used, it will default to just using the 148 // first site in the array. 149 gotestShort := os.Getenv("GOTEST_SHORT") 150 if gotestShort != "" { 151 useSite := 0 152 if site, err := strconv.Atoi(gotestShort); err == nil && site >= 0 && site < len(TestSites) { 153 useSite = site 154 } 155 TestSites = []testcommon.TestSite{TestSites[useSite]} 156 } 157 158 // testRun is the exit result we'll provide. 159 // Start with a clean exit result, it will be changed if we have trouble. 160 testRun := 0 161 for i := range TestSites { 162 err := TestSites[i].Prepare() 163 if err != nil { 164 log.Fatalf("Prepare() failed on TestSite.Prepare() site=%s, err=%v", TestSites[i].Name, err) 165 } 166 167 switchDir := TestSites[i].Chdir() 168 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s Start", TestSites[i].Name)) 169 170 testcommon.ClearDockerEnv() 171 172 app := &ddevapp.DdevApp{} 173 174 err = app.Init(TestSites[i].Dir) 175 if err != nil { 176 testRun = -1 177 log.Errorf("TestMain startup: app.Init() failed on site %s in dir %s, err=%v", TestSites[i].Name, TestSites[i].Dir, err) 178 continue 179 } 180 181 runTime() 182 switchDir() 183 } 184 185 if testRun == 0 { 186 log.Debugln("Running tests.") 187 testRun = m.Run() 188 } 189 190 for i, site := range TestSites { 191 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s Remove", site.Name)) 192 193 testcommon.ClearDockerEnv() 194 195 app := &ddevapp.DdevApp{} 196 197 err := app.Init(site.Dir) 198 if err != nil { 199 log.Fatalf("TestMain shutdown: app.Init() failed on site %s in dir %s, err=%v", TestSites[i].Name, TestSites[i].Dir, err) 200 } 201 202 if app.SiteStatus() != ddevapp.SiteNotFound { 203 err = app.Down(true, false) 204 if err != nil { 205 log.Fatalf("TestMain shutdown: app.Down() failed on site %s, err=%v", TestSites[i].Name, err) 206 } 207 } 208 209 runTime() 210 site.Cleanup() 211 } 212 213 os.Exit(testRun) 214 } 215 216 // TestDdevStart tests the functionality that is called when "ddev start" is executed 217 func TestDdevStart(t *testing.T) { 218 assert := asrt.New(t) 219 app := &ddevapp.DdevApp{} 220 221 // Make sure this leaves us in the original test directory 222 testDir, _ := os.Getwd() 223 //nolint: errcheck 224 defer os.Chdir(testDir) 225 226 for _, site := range TestSites { 227 switchDir := site.Chdir() 228 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevStart", site.Name)) 229 230 err := app.Init(site.Dir) 231 assert.NoError(err) 232 233 err = app.Start() 234 assert.NoError(err) 235 236 // ensure docker-compose.yaml exists inside .ddev site folder 237 composeFile := fileutil.FileExists(app.DockerComposeYAMLPath()) 238 assert.True(composeFile) 239 240 for _, containerType := range [3]string{"web", "db", "dba"} { 241 //nolint: vetshadow 242 containerName, err := constructContainerName(containerType, app) 243 assert.NoError(err) 244 check, err := testcommon.ContainerCheck(containerName, "running") 245 assert.NoError(err) 246 assert.True(check, "Container check on %s failed", containerType) 247 } 248 249 err = app.Down(true, false) 250 assert.NoError(err) 251 runTime() 252 switchDir() 253 } 254 255 // Start up TestSites[0] again 256 site := TestSites[0] 257 err := os.Chdir(site.Dir) 258 assert.NoError(err) 259 err = app.Init(site.Dir) 260 assert.NoError(err) 261 err = app.Start() 262 assert.NoError(err) 263 264 // try to start a site of same name at different path 265 another := TestSites[0] 266 err = another.Prepare() 267 if err != nil { 268 assert.FailNow("TestDdevStart: Prepare() failed on another.Prepare(), err=%v", err) 269 return 270 } 271 272 badapp := &ddevapp.DdevApp{} 273 274 err = badapp.Init(another.Dir) 275 assert.Error(err) 276 if err != nil { 277 assert.Contains(err.Error(), fmt.Sprintf("a project (web container) in running state already exists for %s that was created at %s", TestSites[0].Name, TestSites[0].Dir)) 278 } 279 280 // Make sure that GetActiveApp() also fails when trying to start app of duplicate name in current directory. 281 switchDir := another.Chdir() 282 _, err = ddevapp.GetActiveApp("") 283 assert.Error(err) 284 if err != nil { 285 assert.Contains(err.Error(), fmt.Sprintf("a project (web container) in running state already exists for %s that was created at %s", TestSites[0].Name, TestSites[0].Dir)) 286 } 287 testcommon.CleanupDir(another.Dir) 288 switchDir() 289 290 // Clean up site 0 291 err = app.Down(true, false) 292 assert.NoError(err) 293 } 294 295 // TestDdevStartMultipleHostnames tests start with multiple hostnames 296 func TestDdevStartMultipleHostnames(t *testing.T) { 297 assert := asrt.New(t) 298 app := &ddevapp.DdevApp{} 299 300 for _, site := range TestSites { 301 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevStartMultipleHostnames", site.Name)) 302 testcommon.ClearDockerEnv() 303 304 err := app.Init(site.Dir) 305 assert.NoError(err) 306 307 // site.Name is explicitly added because if not removed in GetHostNames() it will cause ddev-router failure 308 // "a" is repeated for the same reason; a user error of this type should not cause a failure; GetHostNames() 309 // should uniqueify them. 310 app.AdditionalHostnames = []string{"sub1." + site.Name, "sub2." + site.Name, "subname.sub3." + site.Name, site.Name, site.Name, site.Name} 311 312 // sub1.<sitename>.ddev.local and sitename.ddev.local are deliberately included to prove they don't 313 // cause ddev-router failures" 314 app.AdditionalFQDNs = []string{"one.example.com", "two.example.com", "a.one.example.com", site.Name + "." + version.DDevTLD, "sub1." + site.Name + version.DDevTLD} 315 316 err = app.WriteConfig() 317 assert.NoError(err) 318 319 err = app.Start() 320 assert.NoError(err) 321 if err != nil && strings.Contains(err.Error(), "db container failed") { 322 stdout := util.CaptureUserOut() 323 err = app.Logs("db", false, false, "") 324 assert.NoError(err) 325 out := stdout() 326 t.Logf("DB Logs after app.Start: \n%s\n=== END DB LOGS ===", out) 327 } 328 329 // ensure docker-compose.yaml exists inside .ddev site folder 330 composeFile := fileutil.FileExists(app.DockerComposeYAMLPath()) 331 assert.True(composeFile) 332 333 for _, containerType := range [3]string{"web", "db", "dba"} { 334 //nolint: vetshadow 335 containerName, err := constructContainerName(containerType, app) 336 assert.NoError(err) 337 check, err := testcommon.ContainerCheck(containerName, "running") 338 assert.NoError(err) 339 assert.True(check, "Container check on %s failed", containerType) 340 } 341 342 for _, hostname := range app.GetHostnames() { 343 _, _ = testcommon.EnsureLocalHTTPContent(t, "http://"+hostname+site.Safe200URIWithExpectation.URI, site.Safe200URIWithExpectation.Expect) 344 _, _ = testcommon.EnsureLocalHTTPContent(t, "https://"+hostname+site.Safe200URIWithExpectation.URI, site.Safe200URIWithExpectation.Expect) 345 346 } 347 348 // Multiple projects can't run at the same time with the fqdns, so we need to clean 349 // up these for tests that run later. 350 app.AdditionalFQDNs = []string{} 351 app.AdditionalHostnames = []string{} 352 err = app.WriteConfig() 353 assert.NoError(err) 354 355 err = app.Down(true, false) 356 assert.NoError(err) 357 358 runTime() 359 } 360 } 361 362 // TestDdevXdebugEnabled tests running with xdebug_enabled = true, etc. 363 func TestDdevXdebugEnabled(t *testing.T) { 364 assert := asrt.New(t) 365 app := &ddevapp.DdevApp{} 366 367 site := TestSites[0] 368 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevXdebugEnabled", site.Name)) 369 370 err := app.Init(site.Dir) 371 assert.NoError(err) 372 373 // Run with xdebug_enabled: false 374 testcommon.ClearDockerEnv() 375 app.XdebugEnabled = false 376 err = app.WriteConfig() 377 assert.NoError(err) 378 err = app.Start() 379 assert.NoError(err) 380 381 opts := &ddevapp.ExecOpts{ 382 Service: "web", 383 Cmd: []string{"php", "--ri", "xdebug"}, 384 } 385 stdout, _, err := app.Exec(opts) 386 assert.Error(err) 387 assert.Contains(stdout, "Extension 'xdebug' not present") 388 389 // Run with xdebug_enabled: true 390 //err = app.Stop() 391 testcommon.ClearDockerEnv() 392 app.XdebugEnabled = true 393 err = app.WriteConfig() 394 assert.NoError(err) 395 err = app.Start() 396 assert.NoError(err) 397 stdout, _, err = app.Exec(opts) 398 assert.NoError(err) 399 assert.Contains(stdout, "xdebug support => enabled") 400 assert.Contains(stdout, "xdebug.remote_host => host.docker.internal => host.docker.internal") 401 402 err = app.Down(true, false) 403 assert.NoError(err) 404 405 runTime() 406 407 } 408 409 // TestDdevMysqlWorks tests that mysql client can be run in both containers. 410 func TestDdevMysqlWorks(t *testing.T) { 411 assert := asrt.New(t) 412 app := &ddevapp.DdevApp{} 413 414 site := TestSites[0] 415 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevMysqlWorks", site.Name)) 416 417 err := app.Init(site.Dir) 418 assert.NoError(err) 419 420 testcommon.ClearDockerEnv() 421 err = app.Start() 422 assert.NoError(err) 423 424 // Test that mysql + .my.cnf works on web container 425 _, _, err = app.Exec(&ddevapp.ExecOpts{ 426 Service: "web", 427 Cmd: []string{"bash", "-c", "mysql -e 'SELECT USER();' | grep 'db@'"}, 428 }) 429 assert.NoError(err) 430 _, _, err = app.Exec(&ddevapp.ExecOpts{ 431 Service: "web", 432 Cmd: []string{"bash", "-c", "mysql -e 'SELECT DATABASE();' | grep 'db'"}, 433 }) 434 assert.NoError(err) 435 436 // Test that mysql + .my.cnf works on db container 437 _, _, err = app.Exec(&ddevapp.ExecOpts{ 438 Service: "db", 439 Cmd: []string{"bash", "-c", "mysql -e 'SELECT USER();' | grep 'root@localhost'"}, 440 }) 441 assert.NoError(err) 442 _, _, err = app.Exec(&ddevapp.ExecOpts{ 443 Service: "db", 444 Cmd: []string{"bash", "-c", "mysql -e 'SELECT DATABASE();' | grep 'db'"}, 445 }) 446 assert.NoError(err) 447 448 err = app.Down(true, false) 449 assert.NoError(err) 450 451 runTime() 452 453 } 454 455 // TestStartWithoutDdev makes sure we don't have a regression where lack of .ddev 456 // causes a panic. 457 func TestStartWithoutDdevConfig(t *testing.T) { 458 // Set up tests and give ourselves a working directory. 459 assert := asrt.New(t) 460 testDir := testcommon.CreateTmpDir("TestStartWithoutDdevConfig") 461 462 // testcommon.Chdir()() and CleanupDir() check their own errors (and exit) 463 defer testcommon.CleanupDir(testDir) 464 defer testcommon.Chdir(testDir)() 465 466 err := os.MkdirAll(testDir+"/sites/default", 0777) 467 assert.NoError(err) 468 err = os.Chdir(testDir) 469 assert.NoError(err) 470 471 _, err = ddevapp.GetActiveApp("") 472 assert.Error(err) 473 if err != nil { 474 assert.Contains(err.Error(), "Could not find a project") 475 } 476 } 477 478 // TestGetApps tests the GetApps function to ensure it accurately returns a list of running applications. 479 func TestGetApps(t *testing.T) { 480 assert := asrt.New(t) 481 482 // Start the apps. 483 for _, site := range TestSites { 484 testcommon.ClearDockerEnv() 485 app := &ddevapp.DdevApp{} 486 487 err := app.Init(site.Dir) 488 assert.NoError(err) 489 490 err = app.Start() 491 assert.NoError(err) 492 } 493 494 apps := ddevapp.GetApps() 495 assert.Equal(len(TestSites), len(apps)) 496 497 for _, testSite := range TestSites { 498 var found bool 499 for _, app := range apps { 500 if testSite.Name == app.GetName() { 501 found = true 502 break 503 } 504 } 505 assert.True(found, "Found testSite %s in list", testSite.Name) 506 } 507 508 // Now shut down all sites as we expect them to be shut down. 509 for _, site := range TestSites { 510 testcommon.ClearDockerEnv() 511 app := &ddevapp.DdevApp{} 512 513 err := app.Init(site.Dir) 514 assert.NoError(err) 515 516 err = app.Down(true, false) 517 assert.NoError(err) 518 519 } 520 } 521 522 // TestDdevImportDB tests the functionality that is called when "ddev import-db" is executed 523 func TestDdevImportDB(t *testing.T) { 524 assert := asrt.New(t) 525 app := &ddevapp.DdevApp{} 526 testDir, _ := os.Getwd() 527 528 for _, site := range TestSites { 529 switchDir := site.Chdir() 530 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevImportDB", site.Name)) 531 532 testcommon.ClearDockerEnv() 533 err := app.Init(site.Dir) 534 assert.NoError(err) 535 err = app.Start() 536 assert.NoError(err) 537 538 // Test simple db loads. 539 for _, file := range []string{"users.sql", "users.mysql", "users.sql.gz", "users.mysql.gz", "users.sql.tar", "users.mysql.tar", "users.sql.tar.gz", "users.mysql.tar.gz", "users.sql.tgz", "users.mysql.tgz", "users.sql.zip", "users.mysql.zip"} { 540 path := filepath.Join(testDir, "testdata", file) 541 err = app.ImportDB(path, "") 542 assert.NoError(err, "Failed to app.ImportDB path: %s err: %v", path, err) 543 544 // Test that a settings file has correct hash_salt format 545 switch app.Type { 546 case ddevapp.AppTypeDrupal7: 547 // nolint: vetshadow 548 drupalHashSalt, err := fileutil.FgrepStringInFile(app.SiteLocalSettingsPath, "$drupal_hash_salt") 549 assert.NoError(err) 550 assert.True(drupalHashSalt) 551 case ddevapp.AppTypeDrupal8: 552 // nolint: vetshadow 553 settingsHashSalt, err := fileutil.FgrepStringInFile(app.SiteLocalSettingsPath, "settings['hash_salt']") 554 assert.NoError(err) 555 assert.True(settingsHashSalt) 556 case ddevapp.AppTypeWordPress: 557 // nolint: vetshadow 558 hasAuthSalt, err := fileutil.FgrepStringInFile(app.SiteSettingsPath, "SECURE_AUTH_SALT") 559 assert.NoError(err) 560 assert.True(hasAuthSalt) 561 } 562 } 563 564 if site.DBTarURL != "" { 565 // nolint: vetshadow 566 _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_siteTarArchive", "", site.DBTarURL) 567 assert.NoError(err) 568 err = app.ImportDB(cachedArchive, "") 569 assert.NoError(err) 570 571 out, _, err := app.Exec(&ddevapp.ExecOpts{ 572 Service: "db", 573 Cmd: []string{"mysql", "-e", "SHOW TABLES;"}, 574 }) 575 assert.NoError(err) 576 577 assert.Contains(out, "Tables_in_db") 578 assert.False(strings.Contains(out, "Empty set")) 579 580 assert.NoError(err) 581 } 582 583 if site.DBZipURL != "" { 584 // nolint: vetshadow 585 _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_siteZipArchive", "", site.DBZipURL) 586 587 assert.NoError(err) 588 err = app.ImportDB(cachedArchive, "") 589 assert.NoError(err) 590 591 out, _, err := app.Exec(&ddevapp.ExecOpts{ 592 Service: "db", 593 Cmd: []string{"mysql", "-e", "SHOW TABLES;"}, 594 }) 595 assert.NoError(err) 596 597 assert.Contains(out, "Tables_in_db") 598 assert.False(strings.Contains(out, "Empty set")) 599 } 600 601 if site.FullSiteTarballURL != "" { 602 // nolint: vetshadow 603 _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_FullSiteTarballURL", "", site.FullSiteTarballURL) 604 assert.NoError(err) 605 606 err = app.ImportDB(cachedArchive, "data.sql") 607 assert.NoError(err, "Failed to find data.sql at root of tarball %s", cachedArchive) 608 } 609 // We don't want all the projects running at once. 610 err = app.Down(true, false) 611 assert.NoError(err) 612 613 runTime() 614 switchDir() 615 } 616 } 617 618 // TestDdevExportDB tests the functionality that is called when "ddev export-db" is executed 619 func TestDdevExportDB(t *testing.T) { 620 assert := asrt.New(t) 621 app := &ddevapp.DdevApp{} 622 testDir, _ := os.Getwd() 623 624 site := TestSites[0] 625 switchDir := site.Chdir() 626 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevExportDB", site.Name)) 627 628 testcommon.ClearDockerEnv() 629 err := app.Init(site.Dir) 630 assert.NoError(err) 631 err = app.Start() 632 assert.NoError(err) 633 //nolint: errcheck 634 defer app.Down(true, false) 635 importPath := filepath.Join(testDir, "testdata", "users.sql") 636 err = app.ImportDB(importPath, "") 637 require.NoError(t, err) 638 639 err = os.Mkdir("tmp", 0777) 640 require.NoError(t, err) 641 642 err = fileutil.PurgeDirectory("tmp") 643 assert.NoError(err) 644 645 // Test that we can export-db to a gzipped file 646 err = app.ExportDB("tmp/users1.sql.gz", true) 647 assert.NoError(err) 648 649 // Validate contents 650 err = archive.Ungzip("tmp/users1.sql.gz", "tmp") 651 assert.NoError(err) 652 stringFound, err := fileutil.FgrepStringInFile("tmp/users1.sql", "Table structure for table `users`") 653 assert.NoError(err) 654 assert.True(stringFound) 655 656 err = fileutil.PurgeDirectory("tmp") 657 assert.NoError(err) 658 659 // Export to an ungzipped file and validate 660 err = app.ExportDB("tmp/users2.sql", false) 661 assert.NoError(err) 662 663 // Validate contents 664 stringFound, err = fileutil.FgrepStringInFile("tmp/users2.sql", "Table structure for table `users`") 665 assert.NoError(err) 666 assert.True(stringFound) 667 668 err = fileutil.PurgeDirectory("tmp") 669 assert.NoError(err) 670 671 // Capture to stdout without gzip compression 672 stdout := util.CaptureStdOut() 673 err = app.ExportDB("", false) 674 assert.NoError(err) 675 output := stdout() 676 assert.Contains(output, "Table structure for table `users`") 677 678 // Try it with capture to stdout, validate contents. 679 runTime() 680 switchDir() 681 } 682 683 // TestDdevFullSiteSetup tests a full import-db and import-files and then looks to see if 684 // we have a spot-test success hit on a URL 685 func TestDdevFullSiteSetup(t *testing.T) { 686 assert := asrt.New(t) 687 app := &ddevapp.DdevApp{} 688 689 for _, site := range TestSites { 690 switchDir := site.Chdir() 691 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevFullSiteSetup", site.Name)) 692 693 testcommon.ClearDockerEnv() 694 err := app.Init(site.Dir) 695 assert.NoError(err) 696 697 err = app.Start() 698 assert.NoError(err) 699 700 if site.DBTarURL != "" { 701 // nolint: vetshadow 702 _, cachedArchive, err := testcommon.GetCachedArchive(site.Name, site.Name+"_siteTarArchive", "", site.DBTarURL) 703 assert.NoError(err) 704 err = app.ImportDB(cachedArchive, "") 705 assert.NoError(err) 706 } 707 if site.FilesTarballURL != "" { 708 // nolint: vetshadow 709 _, tarballPath, err := testcommon.GetCachedArchive(site.Name, "local-tarballs-files", "", site.FilesTarballURL) 710 assert.NoError(err) 711 err = app.ImportFiles(tarballPath, "") 712 assert.NoError(err) 713 } 714 715 // Test static content. 716 _, _ = testcommon.EnsureLocalHTTPContent(t, app.GetHTTPURL()+site.Safe200URIWithExpectation.URI, site.Safe200URIWithExpectation.Expect) 717 // Test dynamic php + database content. 718 rawurl := app.GetHTTPURL() + site.DynamicURI.URI 719 body, resp, err := testcommon.GetLocalHTTPResponse(t, rawurl, 60) 720 assert.NoError(err, "GetLocalHTTPResponse returned err on rawurl %s, resp=%v: %v", rawurl, resp, err) 721 if err != nil { 722 stdout := util.CaptureUserOut() 723 err = app.Logs("web", false, false, "") 724 assert.NoError(err) 725 out := stdout() 726 t.Logf("Logs after GetLocalHTTPResponse: %s", out) 727 } 728 assert.Contains(body, site.DynamicURI.Expect) 729 730 // Load an image from the files section 731 if site.FilesImageURI != "" { 732 // nolint: vetshadow 733 _, resp, err := testcommon.GetLocalHTTPResponse(t, app.GetHTTPURL()+site.FilesImageURI) 734 assert.NoError(err) 735 assert.Equal("image/jpeg", resp.Header["Content-Type"][0]) 736 } 737 738 // We don't want all the projects running at once. 739 err = app.Down(true, false) 740 assert.NoError(err) 741 742 runTime() 743 switchDir() 744 } 745 } 746 747 // TestDdevRestoreSnapshot tests creating a snapshot and reverting to it 748 func TestDdevRestoreSnapshot(t *testing.T) { 749 assert := asrt.New(t) 750 testDir, _ := os.Getwd() 751 app := &ddevapp.DdevApp{} 752 753 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("TestDdevRestoreSnapshot")) 754 755 d7testerTest1Dump, err := filepath.Abs(filepath.Join("testdata", "restore_snapshot", "d7tester_test_1.sql.gz")) 756 assert.NoError(err) 757 d7testerTest2Dump, err := filepath.Abs(filepath.Join("testdata", "restore_snapshot", "d7tester_test_2.sql.gz")) 758 assert.NoError(err) 759 760 // Use d7 only for this test, the key thing is the database interaction 761 site := FullTestSites[2] 762 // If running this with GOTEST_SHORT we have to create the directory, tarball etc. 763 if site.Dir == "" || !fileutil.FileExists(site.Dir) { 764 err = site.Prepare() 765 if err != nil { 766 t.Fatalf("Prepare() failed on TestSite.Prepare() site=%s, err=%v", site.Name, err) 767 } 768 } 769 770 switchDir := site.Chdir() 771 testcommon.ClearDockerEnv() 772 773 err = app.Init(site.Dir) 774 if err != nil { 775 if app.SiteStatus() != ddevapp.SiteRunning { 776 t.Fatalf("app.Init() failed on site %s in dir %s, err=%v", site.Name, site.Dir, err) 777 } 778 } 779 780 // Try using php72 to avoid SIGBUS failures after restore. 781 app.PHPVersion = ddevapp.PHP72 782 783 err = app.Start() 784 if err != nil { 785 t.Fatalf("TestMain startup: app.Start() failed on site %s, err=%v", site.Name, err) 786 } 787 788 err = app.ImportDB(d7testerTest1Dump, "") 789 assert.NoError(err, "Failed to app.ImportDB path: %s err: %v", d7testerTest1Dump, err) 790 _, err = testcommon.EnsureLocalHTTPContent(t, app.GetHTTPURL(), "d7 tester test 1 has 1 node", 45) 791 if err != nil { 792 logs, err := app.CaptureLogs("web", false, "20") 793 assert.NoError(err) 794 t.Logf("Failed http content assertion; web container logs:\n=======%s==========\n", logs) 795 } 796 797 // Make a snapshot of d7 tester test 1 798 backupsDir := filepath.Join(app.GetConfigPath(""), "db_snapshots") 799 snapshotName, err := app.SnapshotDatabase("d7testerTest1") 800 assert.NoError(err) 801 assert.EqualValues(snapshotName, "d7testerTest1") 802 assert.True(fileutil.FileExists(filepath.Join(backupsDir, snapshotName, "xtrabackup_info"))) 803 804 err = app.ImportDB(d7testerTest2Dump, "") 805 assert.NoError(err, "Failed to app.ImportDB path: %s err: %v", d7testerTest2Dump, err) 806 _, _ = testcommon.EnsureLocalHTTPContent(t, app.GetHTTPURL(), "d7 tester test 2 has 2 nodes", 45) 807 808 snapshotName, err = app.SnapshotDatabase("d7testerTest2") 809 assert.NoError(err) 810 assert.EqualValues(snapshotName, "d7testerTest2") 811 assert.True(fileutil.FileExists(filepath.Join(backupsDir, snapshotName, "xtrabackup_info"))) 812 813 err = app.RestoreSnapshot("d7testerTest1") 814 assert.NoError(err) 815 _, _ = testcommon.EnsureLocalHTTPContent(t, app.GetHTTPURL(), "d7 tester test 1 has 1 node", 45) 816 err = app.RestoreSnapshot("d7testerTest2") 817 assert.NoError(err) 818 819 body, resp, err := testcommon.GetLocalHTTPResponse(t, app.GetHTTPURL(), 45) 820 assert.NoError(err, "GetLocalHTTPResponse returned err on rawurl %s: %v", app.GetHTTPURL(), err) 821 assert.Contains(body, "d7 tester test 2 has 2 nodes") 822 if err != nil { 823 t.Logf("resp after timeout: %v", resp) 824 stdout := util.CaptureUserOut() 825 err = app.Logs("web", false, false, "") 826 assert.NoError(err) 827 out := stdout() 828 t.Logf("web container logs after timeout: %s", out) 829 } 830 831 // Attempt a restore with a pre-mariadb_10.2 snapshot. It should fail and give a link. 832 oldSnapshotTarball, err := filepath.Abs(filepath.Join(testDir, "testdata", "restore_snapshot", "d7tester_test_1.snapshot_mariadb_10_1.tgz")) 833 assert.NoError(err) 834 835 err = archive.Untar(oldSnapshotTarball, filepath.Join(site.Dir, ".ddev", "db_snapshots", "oldsnapshot"), "") 836 assert.NoError(err) 837 err = app.RestoreSnapshot("oldsnapshot") 838 assert.Error(err) 839 assert.Contains(err.Error(), "is not compatible with this version of ddev and mariadb") 840 841 err = app.Down(true, false) 842 assert.NoError(err) 843 844 // TODO: Check behavior of ddev rm with snapshot, see if it has right stuff in it. 845 846 runTime() 847 switchDir() 848 } 849 850 // TestWriteableFilesDirectory tests to make sure that files created on host are writable on container 851 // and files ceated in container are correct user on host. 852 func TestWriteableFilesDirectory(t *testing.T) { 853 assert := asrt.New(t) 854 app := &ddevapp.DdevApp{} 855 856 for _, site := range TestSites { 857 switchDir := site.Chdir() 858 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevImportDB", site.Name)) 859 860 testcommon.ClearDockerEnv() 861 err := app.Init(site.Dir) 862 assert.NoError(err) 863 864 err = app.Start() 865 assert.NoError(err) 866 867 uploadDir := app.GetUploadDir() 868 if uploadDir != "" { 869 870 // Use exec to touch a file in the container and see what the result is. Make sure it comes out with ownership 871 // making it writeable on the host. 872 filename := fileutil.RandomFilenameBase() 873 dirname := fileutil.RandomFilenameBase() 874 // Use path.Join for items on th container (linux) and filepath.Join for items on the host. 875 inContainerDir := path.Join(uploadDir, dirname) 876 onHostDir := filepath.Join(app.Docroot, inContainerDir) 877 878 // The container execution directory is dependent on the app type 879 switch app.Type { 880 case ddevapp.AppTypeWordPress, ddevapp.AppTypeTYPO3, ddevapp.AppTypePHP: 881 inContainerDir = path.Join(app.Docroot, inContainerDir) 882 } 883 884 inContainerRelativePath := path.Join(inContainerDir, filename) 885 onHostRelativePath := path.Join(onHostDir, filename) 886 887 err = os.MkdirAll(onHostDir, 0775) 888 assert.NoError(err) 889 _, _, err = app.Exec(&ddevapp.ExecOpts{ 890 Service: "web", 891 Cmd: []string{"sh", "-c", "echo 'content created inside container\n' >" + inContainerRelativePath}, 892 }) 893 assert.NoError(err) 894 895 // Now try to append to the file on the host. 896 // os.OpenFile() for append here fails if the file does not already exist. 897 //nolint: vetshadow 898 f, err := os.OpenFile(onHostRelativePath, os.O_APPEND|os.O_WRONLY, 0660) 899 assert.NoError(err) 900 _, err = f.WriteString("this addition to the file was added on the host side") 901 assert.NoError(err) 902 _ = f.Close() 903 904 // Create a file on the host and see what the result is. Make sure we can not append/write to it in the container. 905 filename = fileutil.RandomFilenameBase() 906 dirname = fileutil.RandomFilenameBase() 907 inContainerDir = path.Join(uploadDir, dirname) 908 onHostDir = filepath.Join(app.Docroot, inContainerDir) 909 // The container execution directory is dependent on the app type 910 switch app.Type { 911 case ddevapp.AppTypeWordPress, ddevapp.AppTypeTYPO3, ddevapp.AppTypePHP: 912 inContainerDir = path.Join(app.Docroot, inContainerDir) 913 } 914 915 inContainerRelativePath = path.Join(inContainerDir, filename) 916 onHostRelativePath = filepath.Join(onHostDir, filename) 917 918 err = os.MkdirAll(onHostDir, 0775) 919 assert.NoError(err) 920 921 f, err = os.OpenFile(onHostRelativePath, os.O_CREATE|os.O_RDWR, 0660) 922 assert.NoError(err) 923 _, err = f.WriteString("this base content was inserted on the host side\n") 924 assert.NoError(err) 925 _ = f.Close() 926 // if the file exists, add to it. We don't want to add if it's not already there. 927 _, _, err = app.Exec(&ddevapp.ExecOpts{ 928 Service: "web", 929 Cmd: []string{"sh", "-c", "if [ -f " + inContainerRelativePath + " ]; then echo 'content added inside container\n' >>" + inContainerRelativePath + "; fi"}, 930 }) 931 assert.NoError(err) 932 // grep the file for both the content added on host and that added in container. 933 _, _, err = app.Exec(&ddevapp.ExecOpts{ 934 Service: "web", 935 Cmd: []string{"sh", "-c", "grep 'base content was inserted on the host' " + inContainerRelativePath + "&& grep 'content added inside container' " + inContainerRelativePath}, 936 }) 937 assert.NoError(err) 938 } 939 940 err = app.Down(true, false) 941 assert.NoError(err) 942 943 runTime() 944 switchDir() 945 } 946 } 947 948 // TestDdevImportFilesDir tests that "ddev import-files" can successfully import non-archive directories 949 func TestDdevImportFilesDir(t *testing.T) { 950 assert := asrt.New(t) 951 app := &ddevapp.DdevApp{} 952 953 // Create a dummy directory to test non-archive imports 954 importDir, err := ioutil.TempDir("", t.Name()) 955 assert.NoError(err) 956 fileNames := make([]string, 0) 957 for i := 0; i < 5; i++ { 958 fileName := uuid.New().String() 959 fileNames = append(fileNames, fileName) 960 961 fullPath := filepath.Join(importDir, fileName) 962 err = ioutil.WriteFile(fullPath, []byte(fileName), 0644) 963 assert.NoError(err) 964 } 965 966 for _, site := range TestSites { 967 switchDir := site.Chdir() 968 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s %s", site.Name, t.Name())) 969 970 testcommon.ClearDockerEnv() 971 err = app.Init(site.Dir) 972 assert.NoError(err) 973 974 // Function under test 975 err = app.ImportFiles(importDir, "") 976 assert.NoError(err, "Importing a directory returned an error:", err) 977 978 // Confirm contents of destination dir after import 979 absUploadDir := filepath.Join(app.AppRoot, app.Docroot, app.GetUploadDir()) 980 uploadedFiles, err := ioutil.ReadDir(absUploadDir) 981 assert.NoError(err) 982 983 uploadedFilesMap := map[string]bool{} 984 for _, uploadedFile := range uploadedFiles { 985 uploadedFilesMap[filepath.Base(uploadedFile.Name())] = true 986 } 987 988 for _, expectedFile := range fileNames { 989 assert.True(uploadedFilesMap[expectedFile], "Expected file %s not found for site: %s", expectedFile, site.Name) 990 } 991 992 runTime() 993 switchDir() 994 } 995 } 996 997 // TestDdevImportFiles tests the functionality that is called when "ddev import-files" is executed 998 func TestDdevImportFiles(t *testing.T) { 999 assert := asrt.New(t) 1000 app := &ddevapp.DdevApp{} 1001 1002 for _, site := range TestSites { 1003 switchDir := site.Chdir() 1004 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s %s", site.Name, t.Name())) 1005 1006 testcommon.ClearDockerEnv() 1007 err := app.Init(site.Dir) 1008 assert.NoError(err) 1009 1010 if site.FilesTarballURL != "" { 1011 _, tarballPath, err := testcommon.GetCachedArchive(site.Name, "local-tarballs-files", "", site.FilesTarballURL) 1012 assert.NoError(err) 1013 err = app.ImportFiles(tarballPath, "") 1014 assert.NoError(err) 1015 } 1016 1017 if site.FilesZipballURL != "" { 1018 _, zipballPath, err := testcommon.GetCachedArchive(site.Name, "local-zipballs-files", "", site.FilesZipballURL) 1019 assert.NoError(err) 1020 err = app.ImportFiles(zipballPath, "") 1021 assert.NoError(err) 1022 } 1023 1024 if site.FullSiteTarballURL != "" && site.FullSiteArchiveExtPath != "" { 1025 _, siteTarPath, err := testcommon.GetCachedArchive(site.Name, "local-site-tar", "", site.FullSiteTarballURL) 1026 assert.NoError(err) 1027 err = app.ImportFiles(siteTarPath, site.FullSiteArchiveExtPath) 1028 assert.NoError(err) 1029 } 1030 1031 runTime() 1032 switchDir() 1033 } 1034 } 1035 1036 // TestDdevImportFilesCustomUploadDir ensures that files are imported to a custom upload directory when requested 1037 func TestDdevImportFilesCustomUploadDir(t *testing.T) { 1038 assert := asrt.New(t) 1039 app := &ddevapp.DdevApp{} 1040 1041 for _, site := range TestSites { 1042 switchDir := site.Chdir() 1043 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s %s", site.Name, t.Name())) 1044 1045 testcommon.ClearDockerEnv() 1046 err := app.Init(site.Dir) 1047 assert.NoError(err) 1048 1049 // Set custom upload dir 1050 app.UploadDir = "my/upload/dir" 1051 absUploadDir := filepath.Join(app.AppRoot, app.Docroot, app.UploadDir) 1052 err = os.MkdirAll(absUploadDir, 0755) 1053 assert.NoError(err) 1054 1055 if site.FilesTarballURL != "" { 1056 _, tarballPath, err := testcommon.GetCachedArchive(site.Name, "local-tarballs-files", "", site.FilesTarballURL) 1057 assert.NoError(err) 1058 err = app.ImportFiles(tarballPath, "") 1059 assert.NoError(err) 1060 1061 // Ensure upload dir isn't empty 1062 fileInfoSlice, err := ioutil.ReadDir(absUploadDir) 1063 assert.NoError(err) 1064 assert.NotEmpty(fileInfoSlice) 1065 } 1066 1067 if site.FilesZipballURL != "" { 1068 _, zipballPath, err := testcommon.GetCachedArchive(site.Name, "local-zipballs-files", "", site.FilesZipballURL) 1069 assert.NoError(err) 1070 err = app.ImportFiles(zipballPath, "") 1071 assert.NoError(err) 1072 1073 // Ensure upload dir isn't empty 1074 fileInfoSlice, err := ioutil.ReadDir(absUploadDir) 1075 assert.NoError(err) 1076 assert.NotEmpty(fileInfoSlice) 1077 } 1078 1079 if site.FullSiteTarballURL != "" && site.FullSiteArchiveExtPath != "" { 1080 _, siteTarPath, err := testcommon.GetCachedArchive(site.Name, "local-site-tar", "", site.FullSiteTarballURL) 1081 assert.NoError(err) 1082 err = app.ImportFiles(siteTarPath, site.FullSiteArchiveExtPath) 1083 assert.NoError(err) 1084 1085 // Ensure upload dir isn't empty 1086 fileInfoSlice, err := ioutil.ReadDir(absUploadDir) 1087 assert.NoError(err) 1088 assert.NotEmpty(fileInfoSlice) 1089 } 1090 1091 runTime() 1092 switchDir() 1093 } 1094 } 1095 1096 // TestDdevExec tests the execution of commands inside a docker container of a site. 1097 func TestDdevExec(t *testing.T) { 1098 assert := asrt.New(t) 1099 app := &ddevapp.DdevApp{} 1100 1101 for _, site := range TestSites { 1102 switchDir := site.Chdir() 1103 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevExec", site.Name)) 1104 1105 err := app.Init(site.Dir) 1106 assert.NoError(err) 1107 err = app.Start() 1108 assert.NoError(err) 1109 1110 out, _, err := app.Exec(&ddevapp.ExecOpts{ 1111 Service: "web", 1112 Cmd: []string{"pwd"}, 1113 }) 1114 assert.NoError(err) 1115 assert.Contains(out, "/var/www/html") 1116 1117 out, _, err = app.Exec(&ddevapp.ExecOpts{ 1118 Service: "web", 1119 Dir: "/usr/local", 1120 Cmd: []string{"pwd"}, 1121 }) 1122 assert.NoError(err) 1123 assert.Contains(out, "/usr/local") 1124 1125 _, _, err = app.Exec(&ddevapp.ExecOpts{ 1126 Service: "db", 1127 Cmd: []string{"mysql", "-e", "DROP DATABASE db;"}, 1128 }) 1129 assert.NoError(err) 1130 _, _, err = app.Exec(&ddevapp.ExecOpts{ 1131 Service: "db", 1132 Cmd: []string{"mysql", "information_schema", "-e", "CREATE DATABASE db;"}, 1133 }) 1134 assert.NoError(err) 1135 1136 switch app.GetType() { 1137 case ddevapp.AppTypeDrupal6: 1138 fallthrough 1139 case ddevapp.AppTypeDrupal7: 1140 fallthrough 1141 case ddevapp.AppTypeDrupal8: 1142 out, _, err = app.Exec(&ddevapp.ExecOpts{ 1143 Service: "web", 1144 Cmd: []string{"drush", "status"}, 1145 }) 1146 assert.NoError(err) 1147 assert.Regexp("PHP configuration[ :]*/etc/php/[0-9].[0-9]/fpm/php.ini", out) 1148 case ddevapp.AppTypeWordPress: 1149 out, _, err = app.Exec(&ddevapp.ExecOpts{ 1150 Service: "web", 1151 Cmd: []string{"wp", "--info"}, 1152 }) 1153 assert.NoError(err) 1154 assert.Regexp("/etc/php.*/php.ini", out) 1155 } 1156 1157 err = app.Down(true, false) 1158 assert.NoError(err) 1159 1160 runTime() 1161 switchDir() 1162 1163 } 1164 } 1165 1166 // TestDdevLogs tests the container log output functionality. 1167 func TestDdevLogs(t *testing.T) { 1168 assert := asrt.New(t) 1169 1170 // Skip test because on Windows because the CaptureUserOut() hangs, at least 1171 // sometimes. 1172 if runtime.GOOS == "windows" { 1173 t.Skip("Skipping test TestDdevLogs on Windows") 1174 } 1175 1176 app := &ddevapp.DdevApp{} 1177 1178 for _, site := range TestSites { 1179 switchDir := site.Chdir() 1180 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevLogs", site.Name)) 1181 1182 err := app.Init(site.Dir) 1183 assert.NoError(err) 1184 1185 err = app.Start() 1186 assert.NoError(err) 1187 1188 stdout := util.CaptureUserOut() 1189 err = app.Logs("web", false, false, "") 1190 assert.NoError(err) 1191 out := stdout() 1192 assert.Contains(out, "Server started") 1193 1194 stdout = util.CaptureUserOut() 1195 err = app.Logs("db", false, false, "") 1196 assert.NoError(err) 1197 out = stdout() 1198 assert.Contains(out, "MySQL init process done. Ready for start up.") 1199 1200 // Test that we can get logs when project is stopped also 1201 err = app.Stop() 1202 assert.NoError(err) 1203 1204 stdout = util.CaptureUserOut() 1205 err = app.Logs("web", false, false, "") 1206 assert.NoError(err) 1207 out = stdout() 1208 assert.Contains(out, "Server started") 1209 1210 stdout = util.CaptureUserOut() 1211 err = app.Logs("db", false, false, "") 1212 assert.NoError(err) 1213 out = stdout() 1214 assert.Contains(out, "MySQL init process done. Ready for start up.") 1215 1216 err = app.Down(true, false) 1217 assert.NoError(err) 1218 1219 runTime() 1220 switchDir() 1221 } 1222 } 1223 1224 // TestProcessHooks tests execution of commands defined in config.yaml 1225 func TestProcessHooks(t *testing.T) { 1226 assert := asrt.New(t) 1227 1228 for _, site := range TestSites { 1229 cleanup := site.Chdir() 1230 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s ProcessHooks", site.Name)) 1231 1232 testcommon.ClearDockerEnv() 1233 app, err := ddevapp.NewApp(site.Dir, ddevapp.ProviderDefault) 1234 assert.NoError(err) 1235 err = app.Start() 1236 assert.NoError(err) 1237 1238 // Note that any ExecHost commands must be able to run on Windows. 1239 // echo and pwd are things that work pretty much the same in both places. 1240 app.Commands = map[string][]ddevapp.Command{ 1241 "hook-test": { 1242 { 1243 Exec: "ls /usr/local/bin/composer", 1244 }, 1245 { 1246 ExecHost: "echo something", 1247 }, 1248 }, 1249 } 1250 1251 stdout := util.CaptureUserOut() 1252 err = app.ProcessHooks("hook-test") 1253 assert.NoError(err) 1254 1255 // Ignore color in putput, can be different in different OS's 1256 out := vtclean.Clean(stdout(), false) 1257 1258 assert.Contains(out, "hook-test exec command succeeded, output below ---\n/usr/local/bin/composer") 1259 assert.Contains(out, "--- Running host command: echo something ---\nRunning Command Command=echo something\nsomething") 1260 1261 err = app.Down(true, false) 1262 assert.NoError(err) 1263 1264 runTime() 1265 cleanup() 1266 1267 } 1268 } 1269 1270 // TestDdevStop tests the functionality that is called when "ddev stop" is executed 1271 func TestDdevStop(t *testing.T) { 1272 assert := asrt.New(t) 1273 1274 app := &ddevapp.DdevApp{} 1275 1276 for _, site := range TestSites { 1277 switchDir := site.Chdir() 1278 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s DdevStop", site.Name)) 1279 1280 testcommon.ClearDockerEnv() 1281 err := app.Init(site.Dir) 1282 assert.NoError(err) 1283 err = app.Start() 1284 assert.NoError(err) 1285 err = app.Stop() 1286 assert.NoError(err) 1287 1288 for _, containerType := range [3]string{"web", "db", "dba"} { 1289 //nolint: vetshadow 1290 containerName, err := constructContainerName(containerType, app) 1291 assert.NoError(err) 1292 check, err := testcommon.ContainerCheck(containerName, "exited") 1293 assert.NoError(err) 1294 assert.True(check, containerType, "container has exited") 1295 } 1296 err = app.Down(true, false) 1297 assert.NoError(err) 1298 1299 runTime() 1300 switchDir() 1301 } 1302 } 1303 1304 // TestDdevStopMissingDirectory tests that the 'ddev stop' command works properly on sites with missing directories or ddev configs. 1305 func TestDdevStopMissingDirectory(t *testing.T) { 1306 assert := asrt.New(t) 1307 1308 site := TestSites[0] 1309 testcommon.ClearDockerEnv() 1310 app := &ddevapp.DdevApp{} 1311 err := app.Init(site.Dir) 1312 assert.NoError(err) 1313 1314 // Restart the site since it was stopped in the previous test. 1315 if app.SiteStatus() != ddevapp.SiteRunning { 1316 err = app.Start() 1317 assert.NoError(err) 1318 } 1319 1320 tempPath := testcommon.CreateTmpDir("site-copy") 1321 siteCopyDest := filepath.Join(tempPath, "site") 1322 defer removeAllErrCheck(tempPath, assert) 1323 1324 // Move the site directory to a temp location to mimic a missing directory. 1325 err = os.Rename(site.Dir, siteCopyDest) 1326 assert.NoError(err) 1327 1328 err = app.Stop() 1329 assert.Error(err) 1330 assert.Contains(err.Error(), "If you would like to continue using ddev to manage this project please restore your files to that directory.") 1331 // Move the site directory back to its original location. 1332 err = os.Rename(siteCopyDest, site.Dir) 1333 assert.NoError(err) 1334 err = app.Down(true, false) 1335 assert.NoError(err) 1336 } 1337 1338 // TestDescribe tests that the describe command works properly on a running 1339 // and also a stopped project. 1340 func TestDescribe(t *testing.T) { 1341 assert := asrt.New(t) 1342 app := &ddevapp.DdevApp{} 1343 1344 for _, site := range TestSites { 1345 switchDir := site.Chdir() 1346 1347 testcommon.ClearDockerEnv() 1348 err := app.Init(site.Dir) 1349 assert.NoError(err) 1350 1351 // It may already be running, but start does no harm. 1352 err = app.Start() 1353 1354 // If we have a problem starting, get the container logs and output. 1355 if err != nil { 1356 stdout := util.CaptureUserOut() 1357 logsErr := app.Logs("web", false, false, "") 1358 assert.NoError(logsErr) 1359 out := stdout() 1360 1361 healthcheck, inspectErr := exec.RunCommandPipe("bash", []string{"-c", fmt.Sprintf("docker inspect ddev-%s-web|jq -r '.[0].State.Health.Log[-1]'", app.Name)}) 1362 assert.NoError(inspectErr) 1363 1364 assert.NoError(err, "app.Start(%s) failed: %v, \nweb container healthcheck='%s', \n=== web container logs=\n%s\n=== END web container logs ===", site.Name, err, healthcheck, out) 1365 } 1366 1367 desc, err := app.Describe() 1368 assert.NoError(err) 1369 assert.EqualValues(ddevapp.SiteRunning, desc["status"], "") 1370 assert.EqualValues(app.GetName(), desc["name"]) 1371 assert.EqualValues(ddevapp.RenderHomeRootedDir(app.GetAppRoot()), desc["shortroot"]) 1372 assert.EqualValues(app.GetAppRoot(), desc["approot"]) 1373 assert.EqualValues(app.GetPhpVersion(), desc["php_version"]) 1374 1375 // Now stop it and test behavior. 1376 err = app.Stop() 1377 assert.NoError(err) 1378 1379 desc, err = app.Describe() 1380 assert.NoError(err) 1381 assert.EqualValues(ddevapp.SiteStopped, desc["status"]) 1382 err = app.Down(true, false) 1383 assert.NoError(err) 1384 switchDir() 1385 } 1386 } 1387 1388 // TestDescribeMissingDirectory tests that the describe command works properly on sites with missing directories or ddev configs. 1389 func TestDescribeMissingDirectory(t *testing.T) { 1390 assert := asrt.New(t) 1391 site := TestSites[0] 1392 tempPath := testcommon.CreateTmpDir("site-copy") 1393 siteCopyDest := filepath.Join(tempPath, "site") 1394 defer removeAllErrCheck(tempPath, assert) 1395 1396 app := &ddevapp.DdevApp{} 1397 err := app.Init(site.Dir) 1398 assert.NoError(err) 1399 err = app.Start() 1400 assert.NoError(err) 1401 1402 // Move the site directory to a temp location to mimick a missing directory. 1403 err = os.Rename(site.Dir, siteCopyDest) 1404 assert.NoError(err) 1405 1406 desc, err := app.Describe() 1407 assert.NoError(err) 1408 assert.Contains(desc["status"], ddevapp.SiteDirMissing, "Status did not include the phrase '%s' when describing a site with missing directories.", ddevapp.SiteDirMissing) 1409 // Move the site directory back to its original location. 1410 err = os.Rename(siteCopyDest, site.Dir) 1411 assert.NoError(err) 1412 err = app.Down(true, false) 1413 assert.NoError(err) 1414 } 1415 1416 // TestRouterPortsCheck makes sure that we can detect if the ports are available before starting the router. 1417 func TestRouterPortsCheck(t *testing.T) { 1418 assert := asrt.New(t) 1419 1420 // First, stop any sites that might be running 1421 app := &ddevapp.DdevApp{} 1422 1423 // Stop all sites, which should get the router out of there. 1424 for _, site := range TestSites { 1425 switchDir := site.Chdir() 1426 1427 testcommon.ClearDockerEnv() 1428 err := app.Init(site.Dir) 1429 assert.NoError(err) 1430 1431 if app.SiteStatus() == ddevapp.SiteRunning || app.SiteStatus() == ddevapp.SiteStopped { 1432 err = app.Down(true, false) 1433 assert.NoError(err) 1434 } 1435 1436 switchDir() 1437 } 1438 1439 // Now start one site, it's hard to get router to behave without one site. 1440 site := TestSites[0] 1441 testcommon.ClearDockerEnv() 1442 1443 err := app.Init(site.Dir) 1444 assert.NoError(err) 1445 err = app.Start() 1446 assert.NoError(err) 1447 1448 app, err = ddevapp.GetActiveApp(site.Name) 1449 if err != nil { 1450 t.Fatalf("Failed to GetActiveApp(%s), err:%v", site.Name, err) 1451 } 1452 err = app.Start() 1453 assert.NoError(err, "app.Start(%s) failed, err: %v", app.GetName(), err) 1454 1455 // Stop the router using code from StopRouterIfNoContainers(). 1456 // StopRouterIfNoContainers can't be used here because it checks to see if containers are running 1457 // and doesn't do its job as a result. 1458 dest := ddevapp.RouterComposeYAMLPath() 1459 _, _, err = dockerutil.ComposeCmd([]string{dest}, "-p", ddevapp.RouterProjectName, "down", "-v") 1460 assert.NoError(err, "Failed to stop router using docker-compose, err=%v", err) 1461 1462 // Occupy port 80 using docker busybox trick, then see if we can start router. 1463 // This is done with docker so that we don't have to use explicit sudo 1464 containerID, err := exec.RunCommand("sh", []string{"-c", "docker run -d -p80:80 --rm busybox:latest sleep 100 2>/dev/null"}) 1465 if err != nil { 1466 t.Fatalf("Failed to run docker command to occupy port 80, err=%v output=%v", err, containerID) 1467 } 1468 containerID = strings.TrimSpace(containerID) 1469 1470 // Now try to start the router. It should fail because the port is occupied. 1471 err = ddevapp.StartDdevRouter() 1472 assert.Error(err, "Failure: router started even though port 80 was occupied") 1473 1474 // Remove our dummy busybox docker container. 1475 out, err := exec.RunCommand("docker", []string{"rm", "-f", containerID}) 1476 assert.NoError(err, "Failed to docker rm the port-occupier container, err=%v output=%v", err, out) 1477 err = app.Down(true, false) 1478 assert.NoError(err) 1479 } 1480 1481 // TestCleanupWithoutCompose ensures app containers can be properly cleaned up without a docker-compose config file present. 1482 func TestCleanupWithoutCompose(t *testing.T) { 1483 assert := asrt.New(t) 1484 1485 // Skip test because we can't rename folders while they're in use if running on Windows. 1486 if runtime.GOOS == "windows" { 1487 t.Skip("Skipping test TestCleanupWithoutCompose on Windows") 1488 } 1489 1490 site := TestSites[0] 1491 1492 revertDir := site.Chdir() 1493 app := &ddevapp.DdevApp{} 1494 1495 testcommon.ClearDockerEnv() 1496 err := app.Init(site.Dir) 1497 assert.NoError(err) 1498 1499 // Ensure we have a site started so we have something to cleanup 1500 err = app.Start() 1501 assert.NoError(err) 1502 // Setup by creating temp directory and nesting a folder for our site. 1503 tempPath := testcommon.CreateTmpDir("site-copy") 1504 siteCopyDest := filepath.Join(tempPath, "site") 1505 defer removeAllErrCheck(tempPath, assert) 1506 1507 // Move site directory to a temp directory to mimick a missing directory. 1508 err = os.Rename(site.Dir, siteCopyDest) 1509 assert.NoError(err) 1510 1511 // Call the Down command() 1512 // Notice that we set the removeData parameter to true. 1513 // This gives us added test coverage over sites with missing directories 1514 // by ensuring any associated database files get cleaned up as well. 1515 err = app.Down(true, false) 1516 assert.NoError(err) 1517 1518 for _, containerType := range [3]string{"web", "db", "dba"} { 1519 // nolint: vetshadow 1520 _, err := constructContainerName(containerType, app) 1521 assert.Error(err) 1522 } 1523 1524 // Ensure there are no volumes associated with this project 1525 client := dockerutil.GetDockerClient() 1526 volumes, err := client.ListVolumes(docker.ListVolumesOptions{}) 1527 assert.NoError(err) 1528 for _, volume := range volumes { 1529 assert.False(volume.Labels["com.docker.compose.project"] == "ddev"+strings.ToLower(app.GetName())) 1530 } 1531 1532 revertDir() 1533 // Move the site directory back to its original location. 1534 err = os.Rename(siteCopyDest, site.Dir) 1535 assert.NoError(err) 1536 } 1537 1538 // TestGetappsEmpty ensures that GetApps returns an empty list when no applications are running. 1539 func TestGetAppsEmpty(t *testing.T) { 1540 assert := asrt.New(t) 1541 1542 // Ensure test sites are removed 1543 for _, site := range TestSites { 1544 app := &ddevapp.DdevApp{} 1545 1546 switchDir := site.Chdir() 1547 1548 testcommon.ClearDockerEnv() 1549 err := app.Init(site.Dir) 1550 assert.NoError(err) 1551 1552 if app.SiteStatus() != ddevapp.SiteNotFound { 1553 err = app.Down(true, false) 1554 assert.NoError(err) 1555 } 1556 switchDir() 1557 } 1558 1559 apps := ddevapp.GetApps() 1560 assert.Equal(0, len(apps), "Expected to find no apps but found %d apps=%v", len(apps), apps) 1561 } 1562 1563 // TestRouterNotRunning ensures the router is shut down after all sites are stopped. 1564 // This depends on TestGetAppsEmpty() having shut everything down. 1565 func TestRouterNotRunning(t *testing.T) { 1566 assert := asrt.New(t) 1567 containers, err := dockerutil.GetDockerContainers(false) 1568 assert.NoError(err) 1569 1570 for _, container := range containers { 1571 assert.NotEqual("ddev-router", dockerutil.ContainerName(container), "ddev-router was not supposed to be running but it was") 1572 } 1573 } 1574 1575 // TestListWithoutDir prevents regression where ddev list panics if one of the 1576 // sites found is missing a directory 1577 func TestListWithoutDir(t *testing.T) { 1578 // Set up tests and give ourselves a working directory. 1579 assert := asrt.New(t) 1580 testcommon.ClearDockerEnv() 1581 packageDir, _ := os.Getwd() 1582 1583 // startCount is the count of apps at the start of this adventure 1584 apps := ddevapp.GetApps() 1585 startCount := len(apps) 1586 1587 testDir := testcommon.CreateTmpDir("TestStartWithoutDdevConfig") 1588 defer testcommon.CleanupDir(testDir) 1589 1590 err := os.MkdirAll(testDir+"/sites/default", 0777) 1591 assert.NoError(err) 1592 err = os.Chdir(testDir) 1593 assert.NoError(err) 1594 1595 app, err := ddevapp.NewApp(testDir, ddevapp.ProviderDefault) 1596 assert.NoError(err) 1597 app.Name = "junk" 1598 app.Type = ddevapp.AppTypeDrupal7 1599 err = app.WriteConfig() 1600 assert.NoError(err) 1601 1602 // Do a start on the configured site. 1603 app, err = ddevapp.GetActiveApp("") 1604 assert.NoError(err) 1605 err = app.Start() 1606 assert.NoError(err) 1607 1608 // Make sure we move out of the directory for Windows' sake 1609 garbageDir := testcommon.CreateTmpDir("RestingHere") 1610 defer testcommon.CleanupDir(garbageDir) 1611 1612 err = os.Chdir(garbageDir) 1613 assert.NoError(err) 1614 1615 testcommon.CleanupDir(testDir) 1616 1617 apps = ddevapp.GetApps() 1618 1619 assert.EqualValues(len(apps), startCount+1) 1620 1621 // Make a whole table and make sure our app directory missing shows up. 1622 // This could be done otherwise, but we'd have to go find the site in the 1623 // array first. 1624 table := ddevapp.CreateAppTable() 1625 for _, site := range apps { 1626 // nolint: vetshadow 1627 desc, err := site.Describe() 1628 if err != nil { 1629 t.Fatalf("Failed to describe site %s: %v", site.GetName(), err) 1630 } 1631 1632 ddevapp.RenderAppRow(table, desc) 1633 } 1634 1635 // testDir on Windows has backslashes in it, resulting in invalid regexp 1636 // Remove them and use ., which is good enough. 1637 testDirSafe := strings.Replace(testDir, "\\", ".", -1) 1638 assert.Regexp(regexp.MustCompile("(?s)"+ddevapp.SiteDirMissing+".*"+testDirSafe), table.String()) 1639 1640 err = app.Down(true, false) 1641 assert.NoError(err) 1642 1643 // Change back to package dir. Lots of things will have to be cleaned up 1644 // in defers, and for windows we have to not be sitting in them. 1645 err = os.Chdir(packageDir) 1646 assert.NoError(err) 1647 } 1648 1649 type URLRedirectExpectations struct { 1650 scheme string 1651 uri string 1652 expectedRedirectURI string 1653 } 1654 1655 // TestHttpsRedirection tests to make sure that webserver and php redirect to correct 1656 // scheme (http or https). 1657 func TestHttpsRedirection(t *testing.T) { 1658 // Set up tests and give ourselves a working directory. 1659 assert := asrt.New(t) 1660 testcommon.ClearDockerEnv() 1661 packageDir, _ := os.Getwd() 1662 1663 testDir := testcommon.CreateTmpDir("TestHttpsRedirection") 1664 defer testcommon.CleanupDir(testDir) 1665 appDir := filepath.Join(testDir, "proj") 1666 err := fileutil.CopyDir(filepath.Join(packageDir, "testdata", "TestHttpsRedirection"), appDir) 1667 assert.NoError(err) 1668 err = os.Chdir(appDir) 1669 assert.NoError(err) 1670 1671 app, err := ddevapp.NewApp(appDir, ddevapp.ProviderDefault) 1672 assert.NoError(err) 1673 app.Name = "proj" 1674 app.Type = ddevapp.AppTypePHP 1675 1676 expectations := []URLRedirectExpectations{ 1677 {"https", "/subdir", "/subdir/"}, 1678 {"https", "/redir_abs.php", "/landed.php"}, 1679 {"https", "/redir_relative.php", "/landed.php"}, 1680 {"http", "/subdir", "/subdir/"}, 1681 {"http", "/redir_abs.php", "/landed.php"}, 1682 {"http", "/redir_relative.php", "/landed.php"}, 1683 } 1684 1685 for _, webserverType := range []string{ddevapp.WebserverNginxFPM, ddevapp.WebserverApacheFPM, ddevapp.WebserverApacheCGI} { 1686 app.WebserverType = webserverType 1687 err = app.WriteConfig() 1688 assert.NoError(err) 1689 1690 // Do a start on the configured site. 1691 app, err = ddevapp.GetActiveApp("") 1692 assert.NoError(err) 1693 err = app.Start() 1694 assert.NoError(err) 1695 1696 // Test for directory redirects under https and http 1697 for _, parts := range expectations { 1698 1699 reqURL := parts.scheme + "://" + app.GetHostname() + parts.uri 1700 // nolint: vetshadow 1701 _, resp, err := testcommon.GetLocalHTTPResponse(t, reqURL) 1702 assert.Error(err) 1703 assert.NotNil(resp, "resp was nil for webserver_type=%s url=%s", webserverType, reqURL) 1704 if resp != nil { 1705 locHeader := resp.Header.Get("Location") 1706 1707 expectedRedirect := parts.expectedRedirectURI 1708 // However, if we're hitting redir_abs.php (or apache hitting directory), the redirect will be the whole url. 1709 if strings.Contains(parts.uri, "redir_abs.php") || webserverType != ddevapp.WebserverNginxFPM { 1710 expectedRedirect = parts.scheme + "://" + app.GetHostname() + parts.expectedRedirectURI 1711 } 1712 // Except the php relative redirect is always relative. 1713 if strings.Contains(parts.uri, "redir_relative.php") { 1714 expectedRedirect = parts.expectedRedirectURI 1715 } 1716 1717 assert.EqualValues(locHeader, expectedRedirect, "For webserver_type %s url %s expected redirect %s != actual %s", webserverType, reqURL, expectedRedirect, locHeader) 1718 } 1719 } 1720 } 1721 1722 err = app.Down(true, false) 1723 assert.NoError(err) 1724 1725 // Change back to package dir. Lots of things will have to be cleaned up 1726 // in defers, and for windows we have to not be sitting in them. 1727 err = os.Chdir(packageDir) 1728 assert.NoError(err) 1729 } 1730 1731 // TestMultipleComposeFiles checks to see if a set of docker-compose files gets 1732 // properly loaded in the right order, with docker-compose.yaml first and 1733 // with docker-compose.override.yaml last. 1734 func TestMultipleComposeFiles(t *testing.T) { 1735 // Set up tests and give ourselves a working directory. 1736 assert := asrt.New(t) 1737 1738 // Make sure that valid yaml files get properly loaded in the proper order 1739 app, err := ddevapp.NewApp("./testdata/testMultipleComposeFiles", "") 1740 assert.NoError(err) 1741 1742 files, err := app.ComposeFiles() 1743 assert.NoError(err) 1744 assert.True(files[0] == filepath.Join(app.AppConfDir(), "docker-compose.yaml")) 1745 assert.True(files[len(files)-1] == filepath.Join(app.AppConfDir(), "docker-compose.override.yaml")) 1746 1747 // Make sure that some docker-compose.yml and docker-compose.yaml conflict gets noted properly 1748 app, err = ddevapp.NewApp("./testdata/testConflictingYamlYml", "") 1749 assert.NoError(err) 1750 1751 _, err = app.ComposeFiles() 1752 assert.Error(err) 1753 if err != nil { 1754 assert.Contains(err.Error(), "there are more than one docker-compose.y*l") 1755 } 1756 1757 // Make sure that some docker-compose.override.yml and docker-compose.override.yaml conflict gets noted properly 1758 app, err = ddevapp.NewApp("./testdata/testConflictingOverrideYaml", "") 1759 assert.NoError(err) 1760 1761 _, err = app.ComposeFiles() 1762 assert.Error(err) 1763 if err != nil { 1764 assert.Contains(err.Error(), "there are more than one docker-compose.override.y*l") 1765 } 1766 1767 // Make sure the error gets pointed out of there's no main docker-compose.yaml 1768 app, err = ddevapp.NewApp("./testdata/testNoDockerCompose", "") 1769 assert.NoError(err) 1770 1771 _, err = app.ComposeFiles() 1772 assert.Error(err) 1773 if err != nil { 1774 assert.Contains(err.Error(), "failed to find a docker-compose.yml or docker-compose.yaml") 1775 } 1776 1777 // Catch if we have no docker files at all. 1778 // This should also fail if the docker-compose.yaml.bak gets loaded. 1779 app, err = ddevapp.NewApp("./testdata/testNoDockerFilesAtAll", "") 1780 assert.NoError(err) 1781 1782 _, err = app.ComposeFiles() 1783 assert.Error(err) 1784 if err != nil { 1785 assert.Contains(err.Error(), "failed to load any docker-compose.*y*l files") 1786 } 1787 } 1788 1789 // TestGetAllURLs ensures the GetAllURLs function returns the expected number of URLs, 1790 // and that one of them is the direct web container address. 1791 func TestGetAllURLs(t *testing.T) { 1792 assert := asrt.New(t) 1793 1794 for _, site := range TestSites { 1795 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s GetAllURLs", site.Name)) 1796 1797 testcommon.ClearDockerEnv() 1798 app := new(ddevapp.DdevApp) 1799 1800 err := app.Init(site.Dir) 1801 assert.NoError(err) 1802 1803 // Add some additional hostnames 1804 app.AdditionalHostnames = []string{ 1805 fmt.Sprintf("sub1.%s", site.Name), 1806 fmt.Sprintf("sub2.%s", site.Name), 1807 fmt.Sprintf("sub3.%s", site.Name), 1808 } 1809 1810 err = app.WriteConfig() 1811 assert.NoError(err) 1812 1813 err = app.Start() 1814 assert.NoError(err) 1815 1816 urls := app.GetAllURLs() 1817 1818 // Convert URLs to map[string]bool 1819 urlMap := make(map[string]bool) 1820 for _, u := range urls { 1821 urlMap[u] = true 1822 } 1823 1824 // We expect two URLs for each hostname (http/https) and one direct web container address. 1825 expectedNumUrls := (2 * len(app.GetHostnames())) + 1 1826 assert.Equal(len(urlMap), expectedNumUrls, "Unexpected number of URLs returned: %d", len(urlMap)) 1827 1828 // Ensure urlMap contains direct address of the web container 1829 webContainer, err := app.FindContainerByType("web") 1830 assert.NoError(err) 1831 1832 dockerIP, err := dockerutil.GetDockerIP() 1833 assert.NoError(err) 1834 1835 // Find HTTP port of web container 1836 var port docker.APIPort 1837 for _, p := range webContainer.Ports { 1838 if p.PrivatePort == 80 { 1839 port = p 1840 break 1841 } 1842 } 1843 1844 expectedDirectAddress := fmt.Sprintf("http://%s:%d", dockerIP, port.PublicPort) 1845 exists := urlMap[expectedDirectAddress] 1846 1847 assert.True(exists, "URL list for app: %s does not contain direct web container address: %s", app.Name, expectedDirectAddress) 1848 1849 // Multiple projects can't run at the same time with the fqdns, so we need to clean 1850 // up these for tests that run later. 1851 app.AdditionalFQDNs = []string{} 1852 app.AdditionalHostnames = []string{} 1853 err = app.WriteConfig() 1854 assert.NoError(err) 1855 1856 err = app.Down(true, false) 1857 assert.NoError(err) 1858 1859 runTime() 1860 } 1861 } 1862 1863 // TestWebserverType checks that webserver_type:apache-cgi or apache-fpm does the right thing 1864 func TestWebserverType(t *testing.T) { 1865 assert := asrt.New(t) 1866 1867 for _, site := range TestSites { 1868 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s GetAllURLs", site.Name)) 1869 1870 app := new(ddevapp.DdevApp) 1871 1872 err := app.Init(site.Dir) 1873 assert.NoError(err) 1874 1875 // Copy our phpinfo into the docroot of testsite. 1876 pwd, err := os.Getwd() 1877 assert.NoError(err) 1878 err = fileutil.CopyFile(filepath.Join(pwd, "testdata", "servertype.php"), filepath.Join(app.AppRoot, app.Docroot, "servertype.php")) 1879 1880 assert.NoError(err) 1881 for _, app.WebserverType = range []string{ddevapp.WebserverApacheFPM, ddevapp.WebserverApacheCGI, ddevapp.WebserverNginxFPM} { 1882 1883 err = app.WriteConfig() 1884 assert.NoError(err) 1885 1886 testcommon.ClearDockerEnv() 1887 1888 err = app.Start() 1889 assert.NoError(err) 1890 1891 // nolint: vetshadow 1892 out, resp, err := testcommon.GetLocalHTTPResponse(t, app.GetWebContainerDirectURL()+"/servertype.php") 1893 assert.NoError(err) 1894 1895 expectedServerType := "Apache/2" 1896 if app.WebserverType == ddevapp.WebserverNginxFPM { 1897 expectedServerType = "nginx" 1898 } 1899 assert.Contains(resp.Header["Server"][0], expectedServerType, "Server header for project=%s, app.WebserverType=%s should be %s", app.Name, app.WebserverType, expectedServerType) 1900 assert.Contains(out, expectedServerType, "For app.WebserverType=%s phpinfo expected servertype.php to show %s", app.WebserverType, expectedServerType) 1901 } 1902 1903 // Set the apptype back to whatever the default was so we don't break any following tests. 1904 testVar := os.Getenv("DDEV_TEST_WEBSERVER_TYPE") 1905 if testVar != "" { 1906 app.WebserverType = testVar 1907 err = app.WriteConfig() 1908 assert.NoError(err) 1909 } 1910 err = app.Down(true, false) 1911 assert.NoError(err) 1912 1913 runTime() 1914 } 1915 } 1916 1917 // TestDbMigration tests migration from bind-mounted db to volume-mounted db 1918 // This should be important around the time of its release, 2018-08-02 or so, but should be increasingly 1919 // irrelevant after that and can eventually be removed. 1920 func TestDbMigration(t *testing.T) { 1921 assert := asrt.New(t) 1922 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("TestDbMigration")) 1923 1924 app := &ddevapp.DdevApp{} 1925 dbMigrationTarball, err := filepath.Abs(filepath.Join("testdata", "db_migration", "d7_to_migrate.tgz")) 1926 assert.NoError(err) 1927 1928 // Use d7 only for this test 1929 site := FullTestSites[2] 1930 1931 // If running this with GOTEST_SHORT we have to create the directory, tarball etc. 1932 if site.Dir == "" || !fileutil.FileExists(site.Dir) { 1933 err = site.Prepare() 1934 if err != nil { 1935 t.Fatalf("Prepare() failed on TestSite.Prepare() site=%s, err=%v", site.Name, err) 1936 } 1937 } 1938 1939 switchDir := site.Chdir() 1940 testcommon.ClearDockerEnv() 1941 1942 err = app.Init(site.Dir) 1943 assert.NoError(err) 1944 1945 dataDir := filepath.Join(util.GetGlobalDdevDir(), app.Name, "mysql") 1946 1947 // Remove any existing dataDir or migration backups 1948 if fileutil.FileExists(dataDir) { 1949 err = os.RemoveAll(dataDir) 1950 assert.NoError(err) 1951 } 1952 if fileutil.FileExists(dataDir + "_migrated.bak") { 1953 err = os.RemoveAll(dataDir + "_migrated.bak") 1954 assert.NoError(err) 1955 } 1956 1957 // Untar the to-migrate db into old-style dataDir (~/.ddev/projectname/mysql) 1958 err = os.MkdirAll(dataDir, 0755) 1959 require.NoError(t, err) 1960 err = archive.Untar(dbMigrationTarball, dataDir, "") 1961 require.NoError(t, err) 1962 defer os.RemoveAll(dataDir) 1963 1964 _, err = app.CreateSettingsFile() 1965 assert.NoError(err) 1966 1967 // app.Start() will discover the mysql directory and migrate it to a snapshot. 1968 err = app.Start() 1969 assert.Error(err) 1970 assert.Contains(err.Error(), "it is not possible to migrate bind-mounted") 1971 1972 runTime() 1973 switchDir() 1974 } 1975 1976 // TestInternalAndExternalAccessToURL checks we can access content from host and from inside container by URL (with port) 1977 func TestInternalAndExternalAccessToURL(t *testing.T) { 1978 assert := asrt.New(t) 1979 1980 for _, site := range TestSites { 1981 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s TestInternalAndExternalAccessToURL", site.Name)) 1982 1983 app := new(ddevapp.DdevApp) 1984 1985 err := app.Init(site.Dir) 1986 assert.NoError(err) 1987 1988 //nolint: vet 1989 for _, pair := range []testcommon.PortPair{{"80", "443"}, {"8080", "8443"}} { 1990 testcommon.ClearDockerEnv() 1991 app.RouterHTTPPort = pair.HTTPPort 1992 app.RouterHTTPSPort = pair.HTTPSPort 1993 err = app.WriteConfig() 1994 assert.NoError(err) 1995 1996 if app.SiteStatus() == ddevapp.SiteStopped || app.SiteStatus() == ddevapp.SiteRunning { 1997 err = app.Down(true, false) 1998 assert.NoError(err) 1999 } 2000 err = app.Start() 2001 assert.NoError(err) 2002 2003 // Ensure that we can access from the host even with extra port specifications. 2004 _, _ = testcommon.EnsureLocalHTTPContent(t, app.GetHTTPURL()+site.Safe200URIWithExpectation.URI, site.Safe200URIWithExpectation.Expect) 2005 _, _ = testcommon.EnsureLocalHTTPContent(t, app.GetHTTPSURL()+site.Safe200URIWithExpectation.URI, site.Safe200URIWithExpectation.Expect) 2006 2007 // Ensure that we can access the same URL from within the web container (via router) 2008 var out string 2009 out, _, err = app.Exec(&ddevapp.ExecOpts{ 2010 Service: "web", 2011 Cmd: []string{"curl", "-sk", app.GetHTTPURL() + site.Safe200URIWithExpectation.URI}, 2012 }) 2013 assert.NoError(err) 2014 assert.Contains(out, site.Safe200URIWithExpectation.Expect) 2015 2016 out, _, err = app.Exec(&ddevapp.ExecOpts{ 2017 Service: "web", 2018 Cmd: []string{"curl", "-sk", app.GetHTTPSURL() + site.Safe200URIWithExpectation.URI}, 2019 }) 2020 assert.NoError(err) 2021 assert.Contains(out, site.Safe200URIWithExpectation.Expect) 2022 } 2023 2024 // Set the ports back to the default was so we don't break any following tests. 2025 app.RouterHTTPSPort = "443" 2026 app.RouterHTTPPort = "80" 2027 err = app.WriteConfig() 2028 assert.NoError(err) 2029 err = app.Down(true, false) 2030 assert.NoError(err) 2031 2032 runTime() 2033 } 2034 } 2035 2036 // TestCaptureLogs checks that app.CaptureLogs() works 2037 func TestCaptureLogs(t *testing.T) { 2038 assert := asrt.New(t) 2039 2040 site := TestSites[0] 2041 runTime := testcommon.TimeTrack(time.Now(), fmt.Sprintf("%s CaptureLogs", site.Name)) 2042 2043 app := new(ddevapp.DdevApp) 2044 2045 err := app.Init(site.Dir) 2046 assert.NoError(err) 2047 err = app.Start() 2048 assert.NoError(err) 2049 2050 logs, err := app.CaptureLogs("web", false, "100") 2051 assert.NoError(err) 2052 2053 assert.Contains(logs, "INFO spawned") 2054 2055 err = app.Down(true, false) 2056 assert.NoError(err) 2057 2058 runTime() 2059 } 2060 2061 // constructContainerName builds a container name given the type (web/db/dba) and the app 2062 func constructContainerName(containerType string, app *ddevapp.DdevApp) (string, error) { 2063 container, err := app.FindContainerByType(containerType) 2064 if err != nil { 2065 return "", err 2066 } 2067 if container == nil { 2068 return "", fmt.Errorf("No container exists for containerType=%s app=%v", containerType, app) 2069 } 2070 name := dockerutil.ContainerName(*container) 2071 return name, nil 2072 } 2073 2074 func removeAllErrCheck(path string, assert *asrt.Assertions) { 2075 err := os.RemoveAll(path) 2076 assert.NoError(err) 2077 }