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  }