diff options
author | Nick Thomas <nick@gitlab.com> | 2019-01-11 17:05:20 +0300 |
---|---|---|
committer | Nick Thomas <nick@gitlab.com> | 2019-01-11 17:05:20 +0300 |
commit | 89519349a4a3efbc7a2512f08a1fa3ce85ed0feb (patch) | |
tree | f1df712c69b0f8c4660b9442fe0e13153c369710 /internal | |
parent | 5dc404ef5660903a14f98bb46f6628afe3b272af (diff) | |
parent | f7fd1fa5f93c36f2473ee1b53ba4ee2b21eeac12 (diff) |
Merge branch 'ac-subgroups' into 'master'
pages for subgroups
Closes gitlab-ce#30548
See merge request gitlab-org/gitlab-pages!123
Diffstat (limited to 'internal')
-rw-r--r-- | internal/domain/domain.go | 33 | ||||
-rw-r--r-- | internal/domain/domain_config_test.go | 3 | ||||
-rw-r--r-- | internal/domain/domain_test.go | 148 | ||||
-rw-r--r-- | internal/domain/group.go | 38 | ||||
-rw-r--r-- | internal/domain/group_test.go | 97 | ||||
-rw-r--r-- | internal/domain/map.go | 65 | ||||
-rw-r--r-- | internal/domain/map_test.go | 119 |
7 files changed, 392 insertions, 111 deletions
diff --git a/internal/domain/domain.go b/internal/domain/domain.go index efbb8316..31b30de0 100644 --- a/internal/domain/domain.go +++ b/internal/domain/domain.go @@ -21,6 +21,14 @@ import ( "gitlab.com/gitlab-org/gitlab-pages/internal/httputil" ) +const ( + subgroupScanLimit int = 21 + // maxProjectDepth is set to the maximum nested project depth in gitlab (21) plus 3. + // One for the project, one for the first empty element of the split (URL.Path starts with /), + // and one for the real file path + maxProjectDepth int = subgroupScanLimit + 3 +) + type locationDirectoryError struct { FullPath string RelativePath string @@ -33,11 +41,9 @@ type project struct { ID uint64 } -type projects map[string]*project - // D is a domain that gitlab-pages can serve. type D struct { - group string + group // custom domains: projectName string @@ -46,19 +52,16 @@ type D struct { certificate *tls.Certificate certificateError error certificateOnce sync.Once - - // group domains: - projects projects } // String implements Stringer. func (d *D) String() string { - if d.group != "" && d.projectName != "" { - return d.group + "/" + d.projectName + if d.group.name != "" && d.projectName != "" { + return d.group.name + "/" + d.projectName } - if d.group != "" { - return d.group + if d.group.name != "" { + return d.group.name } return d.projectName @@ -110,11 +113,11 @@ func getHost(r *http.Request) string { func (d *D) getProjectWithSubpath(r *http.Request) (*project, string, string) { // Check for a project specified in the URL: http://group.gitlab.io/projectA // If present, these projects shadow the group domain. - split := strings.SplitN(r.URL.Path, "/", 3) + split := strings.SplitN(r.URL.Path, "/", maxProjectDepth) if len(split) >= 2 { - projectName := strings.ToLower(split[1]) - if project := d.projects[projectName]; project != nil { - return project, split[1], strings.Join(split[2:], "/") + project, projectPath, urlPath := d.digProjectWithSubpath("", split[1:]) + if project != nil { + return project, projectPath, urlPath } } @@ -314,7 +317,7 @@ func (d *D) serveCustomFile(w http.ResponseWriter, r *http.Request, code int, or // Resolve the HTTP request to a path on disk, converting requests for // directories to requests for index.html inside the directory if appropriate. func (d *D) resolvePath(projectName string, subPath ...string) (string, error) { - publicPath := filepath.Join(d.group, projectName, "public") + publicPath := filepath.Join(d.group.name, projectName, "public") // Don't use filepath.Join as cleans the path, // where we want to traverse full path as supplied by user diff --git a/internal/domain/domain_config_test.go b/internal/domain/domain_config_test.go index 05db0aa3..a89ead35 100644 --- a/internal/domain/domain_config_test.go +++ b/internal/domain/domain_config_test.go @@ -38,7 +38,8 @@ func TestDomainConfigValidness(t *testing.T) { } func TestDomainConfigRead(t *testing.T) { - setUpTests() + cleanup := setUpTests(t) + defer cleanup() d := domainsConfig{} err := d.Read("test-group", "test-project") diff --git a/internal/domain/domain_test.go b/internal/domain/domain_test.go index 32bf2e3d..b65396a7 100644 --- a/internal/domain/domain_test.go +++ b/internal/domain/domain_test.go @@ -11,7 +11,6 @@ import ( "testing" "time" - log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,16 +26,19 @@ func serveFileOrNotFound(domain *D) http.HandlerFunc { } func TestGroupServeHTTP(t *testing.T) { - setUpTests() + cleanup := setUpTests(t) + defer cleanup() testGroup := &D{ - group: "group", projectName: "", - projects: map[string]*project{ - "group.test.io": &project{}, - "group.gitlab-example.com": &project{}, - "project": &project{}, - "project2": &project{}, + group: group{ + name: "group", + projects: map[string]*project{ + "group.test.io": &project{}, + "group.gitlab-example.com": &project{}, + "project": &project{}, + "project2": &project{}, + }, }, } @@ -64,10 +66,11 @@ func TestGroupServeHTTP(t *testing.T) { } func TestDomainServeHTTP(t *testing.T) { - setUpTests() + cleanup := setUpTests(t) + defer cleanup() testDomain := &D{ - group: "group", + group: group{name: "group"}, projectName: "project2", config: &domainConfig{ Domain: "test.domain.com", @@ -95,7 +98,7 @@ func TestIsHTTPSOnly(t *testing.T) { { name: "Custom domain with HTTPS-only enabled", domain: &D{ - group: "group", + group: group{name: "group"}, projectName: "project", config: &domainConfig{HTTPSOnly: true}, }, @@ -105,7 +108,7 @@ func TestIsHTTPSOnly(t *testing.T) { { name: "Custom domain with HTTPS-only disabled", domain: &D{ - group: "group", + group: group{name: "group"}, projectName: "project", config: &domainConfig{HTTPSOnly: false}, }, @@ -115,9 +118,11 @@ func TestIsHTTPSOnly(t *testing.T) { { name: "Default group domain with HTTPS-only enabled", domain: &D{ - group: "group", projectName: "project", - projects: projects{"test-domain": &project{HTTPSOnly: true}}, + group: group{ + name: "group", + projects: projects{"test-domain": &project{HTTPSOnly: true}}, + }, }, url: "http://test-domain", expected: true, @@ -125,9 +130,11 @@ func TestIsHTTPSOnly(t *testing.T) { { name: "Default group domain with HTTPS-only disabled", domain: &D{ - group: "group", projectName: "project", - projects: projects{"test-domain": &project{HTTPSOnly: false}}, + group: group{ + name: "group", + projects: projects{"test-domain": &project{HTTPSOnly: false}}, + }, }, url: "http://test-domain", expected: false, @@ -135,9 +142,11 @@ func TestIsHTTPSOnly(t *testing.T) { { name: "Case-insensitive default group domain with HTTPS-only enabled", domain: &D{ - group: "group", projectName: "project", - projects: projects{"test-domain": &project{HTTPSOnly: true}}, + group: group{ + name: "group", + projects: projects{"test-domain": &project{HTTPSOnly: true}}, + }, }, url: "http://Test-domain", expected: true, @@ -145,9 +154,11 @@ func TestIsHTTPSOnly(t *testing.T) { { name: "Other group domain with HTTPS-only enabled", domain: &D{ - group: "group", projectName: "project", - projects: projects{"project": &project{HTTPSOnly: true}}, + group: group{ + name: "group", + projects: projects{"project": &project{HTTPSOnly: true}}, + }, }, url: "http://test-domain/project", expected: true, @@ -155,9 +166,11 @@ func TestIsHTTPSOnly(t *testing.T) { { name: "Other group domain with HTTPS-only disabled", domain: &D{ - group: "group", projectName: "project", - projects: projects{"project": &project{HTTPSOnly: false}}, + group: group{ + name: "group", + projects: projects{"project": &project{HTTPSOnly: false}}, + }, }, url: "http://test-domain/project", expected: false, @@ -165,7 +178,7 @@ func TestIsHTTPSOnly(t *testing.T) { { name: "Unknown project", domain: &D{ - group: "group", + group: group{name: "group"}, projectName: "project", }, url: "http://test-domain/project", @@ -209,16 +222,19 @@ func testHTTPGzip(t *testing.T, handler http.HandlerFunc, mode, url string, valu } func TestGroupServeHTTPGzip(t *testing.T) { - setUpTests() + cleanup := setUpTests(t) + defer cleanup() testGroup := &D{ - group: "group", projectName: "", - projects: map[string]*project{ - "group.test.io": &project{}, - "group.gitlab-example.com": &project{}, - "project": &project{}, - "project2": &project{}, + group: group{ + name: "group", + projects: map[string]*project{ + "group.test.io": &project{}, + "group.gitlab-example.com": &project{}, + "project": &project{}, + "project2": &project{}, + }, }, } @@ -285,17 +301,20 @@ func testHTTP404(t *testing.T, handler http.HandlerFunc, mode, url string, value } func TestGroup404ServeHTTP(t *testing.T) { - setUpTests() + cleanup := setUpTests(t) + defer cleanup() testGroup := &D{ - group: "group.404", projectName: "", - projects: map[string]*project{ - "domain.404": &project{}, - "group.404.test.io": &project{}, - "project.404": &project{}, - "project.404.symlink": &project{}, - "project.no.404": &project{}, + group: group{ + name: "group.404", + projects: map[string]*project{ + "domain.404": &project{}, + "group.404.test.io": &project{}, + "project.404": &project{}, + "project.404.symlink": &project{}, + "project.no.404": &project{}, + }, }, } @@ -311,10 +330,11 @@ func TestGroup404ServeHTTP(t *testing.T) { } func TestDomain404ServeHTTP(t *testing.T) { - setUpTests() + cleanup := setUpTests(t) + defer cleanup() testDomain := &D{ - group: "group.404", + group: group{name: "group.404"}, projectName: "domain.404", config: &domainConfig{ Domain: "domain.404.com", @@ -326,10 +346,11 @@ func TestDomain404ServeHTTP(t *testing.T) { } func TestPredefined404ServeHTTP(t *testing.T) { - setUpTests() + cleanup := setUpTests(t) + defer cleanup() testDomain := &D{ - group: "group", + group: group{name: "group"}, } testHTTP404(t, serveFileOrNotFound(testDomain), "GET", "http://group.test.io/not-existing-file", nil, "The page you're looking for could not be found") @@ -337,7 +358,7 @@ func TestPredefined404ServeHTTP(t *testing.T) { func TestGroupCertificate(t *testing.T) { testGroup := &D{ - group: "group", + group: group{name: "group"}, projectName: "", } @@ -348,7 +369,7 @@ func TestGroupCertificate(t *testing.T) { func TestDomainNoCertificate(t *testing.T) { testDomain := &D{ - group: "group", + group: group{name: "group"}, projectName: "project2", config: &domainConfig{ Domain: "test.domain.com", @@ -366,7 +387,7 @@ func TestDomainNoCertificate(t *testing.T) { func TestDomainCertificate(t *testing.T) { testDomain := &D{ - group: "group", + group: group{name: "group"}, projectName: "project2", config: &domainConfig{ Domain: "test.domain.com", @@ -381,10 +402,15 @@ func TestDomainCertificate(t *testing.T) { } func TestCacheControlHeaders(t *testing.T) { + cleanup := setUpTests(t) + defer cleanup() + testGroup := &D{ - group: "group", - projects: map[string]*project{ - "group.test.io": &project{}, + group: group{ + name: "group", + projects: map[string]*project{ + "group.test.io": &project{}, + }, }, } w := httptest.NewRecorder() @@ -431,15 +457,27 @@ func TestOpenNoFollow(t *testing.T) { var chdirSet = false -func setUpTests() { +func setUpTests(t require.TestingT) func() { + return chdirInPath(t, "../../shared/pages") +} + +func chdirInPath(t require.TestingT, path string) func() { + noOp := func() {} if chdirSet { - return + return noOp } - err := os.Chdir("../../shared/pages") - if err != nil { - log.WithError(err).Print("chdir") - } else { - chdirSet = true + cwd, err := os.Getwd() + require.NoError(t, err, "Cannot Getwd") + + err = os.Chdir(path) + require.NoError(t, err, "Cannot Chdir") + + chdirSet = true + return func() { + err := os.Chdir(cwd) + require.NoError(t, err, "Cannot Chdir in cleanup") + + chdirSet = false } } diff --git a/internal/domain/group.go b/internal/domain/group.go new file mode 100644 index 00000000..83b8d255 --- /dev/null +++ b/internal/domain/group.go @@ -0,0 +1,38 @@ +package domain + +import ( + "path" + "strings" +) + +type projects map[string]*project +type subgroups map[string]*group + +type group struct { + name string + + // nested groups + subgroups subgroups + + // group domains: + projects projects +} + +func (g *group) digProjectWithSubpath(parentPath string, keys []string) (*project, string, string) { + if len(keys) >= 1 { + head := keys[0] + tail := keys[1:] + currentPath := path.Join(parentPath, head) + search := strings.ToLower(head) + + if project := g.projects[search]; project != nil { + return project, currentPath, path.Join(tail...) + } + + if subgroup := g.subgroups[search]; subgroup != nil { + return subgroup.digProjectWithSubpath(currentPath, tail) + } + } + + return nil, "", "" +} diff --git a/internal/domain/group_test.go b/internal/domain/group_test.go new file mode 100644 index 00000000..2e41ef53 --- /dev/null +++ b/internal/domain/group_test.go @@ -0,0 +1,97 @@ +package domain + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGroupDig(t *testing.T) { + matchingProject := &project{ID: 1} + + tests := []struct { + name string + g group + path string + expectedProject *project + expectedProjectPath string + expectedPath string + }{ + { + name: "empty group", + path: "projectb/demo/features.html", + g: group{}, + }, + { + name: "group with project", + path: "projectb/demo/features.html", + g: group{ + projects: projects{"projectb": matchingProject}, + }, + expectedProject: matchingProject, + expectedProjectPath: "projectb", + expectedPath: "demo/features.html", + }, + { + name: "group with project and no path in URL", + path: "projectb", + g: group{ + projects: projects{"projectb": matchingProject}, + }, + expectedProject: matchingProject, + expectedProjectPath: "projectb", + }, + { + name: "group with subgroup and project", + path: "projectb/demo/features.html", + g: group{ + projects: projects{"projectb": matchingProject}, + subgroups: subgroups{ + "sub1": &group{ + projects: projects{"another": &project{}}, + }, + }, + }, + expectedProject: matchingProject, + expectedProjectPath: "projectb", + expectedPath: "demo/features.html", + }, + { + name: "group with project inside a subgroup", + path: "sub1/projectb/demo/features.html", + g: group{ + subgroups: subgroups{ + "sub1": &group{ + projects: projects{"projectb": matchingProject}, + }, + }, + projects: projects{"another": &project{}}, + }, + expectedProject: matchingProject, + expectedProjectPath: "sub1/projectb", + expectedPath: "demo/features.html", + }, + { + name: "group with matching subgroup but no project", + path: "sub1/projectb/demo/features.html", + g: group{ + subgroups: subgroups{ + "sub1": &group{ + projects: projects{"another": &project{}}, + }, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + project, projectPath, urlPath := test.g.digProjectWithSubpath("", strings.Split(test.path, "/")) + + assert.Equal(t, test.expectedProject, project) + assert.Equal(t, test.expectedProjectPath, projectPath) + assert.Equal(t, test.expectedPath, urlPath) + }) + } +} diff --git a/internal/domain/map.go b/internal/domain/map.go index d2e7c74f..2891a272 100644 --- a/internal/domain/map.go +++ b/internal/domain/map.go @@ -34,9 +34,9 @@ func (dm Map) updateDomainMap(domainName string, domain *D) { dm[domainName] = domain } -func (dm Map) addDomain(rootDomain, group, projectName string, config *domainConfig) { +func (dm Map) addDomain(rootDomain, groupName, projectName string, config *domainConfig) { newDomain := &D{ - group: group, + group: group{name: groupName}, projectName: projectName, config: config, } @@ -46,19 +46,41 @@ func (dm Map) addDomain(rootDomain, group, projectName string, config *domainCon dm.updateDomainMap(domainName, newDomain) } -func (dm Map) updateGroupDomain(rootDomain, group, projectName string, httpsOnly bool, accessControl bool, id uint64) { - domainName := strings.ToLower(group + "." + rootDomain) +func (dm Map) updateGroupDomain(rootDomain, groupName, projectPath string, httpsOnly bool, accessControl bool, id uint64) { + domainName := strings.ToLower(groupName + "." + rootDomain) groupDomain := dm[domainName] if groupDomain == nil { groupDomain = &D{ - group: group, - projects: make(projects), + group: group{ + name: groupName, + projects: make(projects), + subgroups: make(subgroups), + }, } } - groupDomain.projects[strings.ToLower(projectName)] = &project{ - NamespaceProject: domainName == strings.ToLower(projectName), + split := strings.SplitN(strings.ToLower(projectPath), "/", maxProjectDepth) + projectName := split[len(split)-1] + g := &groupDomain.group + + for i := 0; i < len(split)-1; i++ { + subgroupName := split[i] + subgroup := g.subgroups[subgroupName] + if subgroup == nil { + subgroup = &group{ + name: subgroupName, + projects: make(projects), + subgroups: make(subgroups), + } + g.subgroups[subgroupName] = subgroup + } + + g = subgroup + } + + g.projects[projectName] = &project{ + NamespaceProject: domainName == projectName, HTTPSOnly: httpsOnly, AccessControl: accessControl, ID: id, @@ -86,7 +108,7 @@ func (dm Map) readProjectConfig(rootDomain string, group, projectName string, co } } -func readProject(group, projectName string, fanIn chan<- jobResult) { +func readProject(group, parent, projectName string, level int, fanIn chan<- jobResult) { if strings.HasPrefix(projectName, ".") { return } @@ -96,25 +118,34 @@ func readProject(group, projectName string, fanIn chan<- jobResult) { return } - if _, err := os.Lstat(filepath.Join(group, projectName, "public")); err != nil { + projectPath := filepath.Join(parent, projectName) + if _, err := os.Lstat(filepath.Join(group, projectPath, "public")); err != nil { + // maybe it's a subgroup + if level <= subgroupScanLimit { + buf := make([]byte, 2*os.Getpagesize()) + readProjects(group, projectPath, level+1, buf, fanIn) + } + return } // We read the config.json file _before_ fanning in, because it does disk // IO and it does not need access to the domains map. config := &domainsConfig{} - if err := config.Read(group, projectName); err != nil { + if err := config.Read(group, projectPath); err != nil { config = nil } - fanIn <- jobResult{group: group, project: projectName, config: config} + fanIn <- jobResult{group: group, project: projectPath, config: config} } -func readProjects(group string, buf []byte, fanIn chan<- jobResult) { - fis, err := godirwalk.ReadDirents(group, buf) +func readProjects(group, parent string, level int, buf []byte, fanIn chan<- jobResult) { + subgroup := filepath.Join(group, parent) + fis, err := godirwalk.ReadDirents(subgroup, buf) if err != nil { log.WithError(err).WithFields(log.Fields{ - "group": group, + "group": group, + "parent": parent, }).Print("readdir failed") return } @@ -125,7 +156,7 @@ func readProjects(group string, buf []byte, fanIn chan<- jobResult) { continue } - readProject(group, project.Name(), fanIn) + readProject(group, parent, project.Name(), level, fanIn) } } @@ -149,7 +180,7 @@ func (dm Map) ReadGroups(rootDomain string, fis godirwalk.Dirents) { for group := range fanOutGroups { started := time.Now() - readProjects(group, buf, fanIn) + readProjects(group, "", 0, buf, fanIn) log.WithFields(log.Fields{ "group": group, diff --git a/internal/domain/map_test.go b/internal/domain/map_test.go index a2b2b575..dc5e8648 100644 --- a/internal/domain/map_test.go +++ b/internal/domain/map_test.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "os" + "strings" "testing" "time" @@ -30,7 +31,8 @@ func getEntriesForBenchmark(t *testing.B) godirwalk.Dirents { } func TestReadProjects(t *testing.T) { - setUpTests() + cleanup := setUpTests(t) + defer cleanup() dm := make(Map) dm.ReadGroups("test.io", getEntries(t)) @@ -70,6 +72,63 @@ func TestReadProjects(t *testing.T) { exp2 := &domainConfig{Domain: "other.domain.com", Certificate: "test", Key: "key"} assert.Equal(t, exp2, dm["other.domain.com"].config) + + // check subgroups + domain, ok := dm["group.test.io"] + require.True(t, ok, "missing group.test.io domain") + subgroup, ok := domain.subgroups["subgroup"] + require.True(t, ok, "missing group.test.io subgroup") + _, ok = subgroup.projects["project"] + require.True(t, ok, "missing project for subgroup in group.test.io domain") +} + +func TestReadProjectsMaxDepth(t *testing.T) { + nGroups := 3 + levels := subgroupScanLimit + 5 + cleanup := buildFakeDomainsDirectory(t, nGroups, levels) + defer cleanup() + + defaultDomain := "test.io" + dm := make(Map) + dm.ReadGroups(defaultDomain, getEntries(t)) + + var domains []string + for d := range dm { + domains = append(domains, d) + } + + var expectedDomains []string + for i := 0; i < nGroups; i++ { + expectedDomains = append(expectedDomains, fmt.Sprintf("group-%d.%s", i, defaultDomain)) + } + + for _, expected := range domains { + assert.Contains(t, domains, expected) + } + + for _, actual := range domains { + // we are not checking config.json domains here + if !strings.HasSuffix(actual, defaultDomain) { + continue + } + assert.Contains(t, expectedDomains, actual) + } + + // check subgroups + domain, ok := dm["group-0.test.io"] + require.True(t, ok, "missing group-0.test.io domain") + subgroup := &domain.group + for i := 0; i < levels; i++ { + subgroup, ok = subgroup.subgroups["sub"] + if i <= subgroupScanLimit { + require.True(t, ok, "missing group-0.test.io subgroup at level %d", i) + _, ok = subgroup.projects["project-0"] + require.True(t, ok, "missing project for subgroup in group-0.test.io domain at level %d", i) + } else { + require.False(t, ok, "subgroup level %d. Maximum allowed nesting level is %d", i, subgroupScanLimit) + break + } + } } // This write must be atomic, otherwise we cannot predict the state of the @@ -91,7 +150,8 @@ func writeRandomTimestamp(t *testing.T) { } func TestWatch(t *testing.T) { - setUpTests() + cleanup := setUpTests(t) + defer cleanup() require.NoError(t, os.RemoveAll(updateFile)) @@ -126,36 +186,49 @@ func recvTimeout(t *testing.T, ch <-chan Map) Map { } } -func BenchmarkReadGroups(b *testing.B) { +func buildFakeDomainsDirectory(t require.TestingT, nGroups, levels int) func() { testRoot, err := ioutil.TempDir("", "gitlab-pages-test") - require.NoError(b, err) - - cwd, err := os.Getwd() - require.NoError(b, err) - - defer func(oldWd, testWd string) { - os.Chdir(oldWd) - fmt.Printf("cleaning up test directory %s\n", testWd) - os.RemoveAll(testWd) - }(cwd, testRoot) - - require.NoError(b, os.Chdir(testRoot)) + require.NoError(t, err) - nGroups := 10000 - b.Logf("creating fake domains directory with %d groups", nGroups) for i := 0; i < nGroups; i++ { - for j := 0; j < 5; j++ { - dir := fmt.Sprintf("%s/group-%d/project-%d", testRoot, i, j) - require.NoError(b, os.MkdirAll(dir+"/public", 0755)) - - fakeConfig := fmt.Sprintf(`{"Domains":[{"Domain":"foo.%d.%d.example.io","Certificate":"bar","Key":"baz"}]}`, i, j) - require.NoError(b, ioutil.WriteFile(dir+"/config.json", []byte(fakeConfig), 0644)) + parent := fmt.Sprintf("%s/group-%d", testRoot, i) + domain := fmt.Sprintf("%d.example.io", i) + buildFakeProjectsDirectory(t, parent, domain) + for j := 0; j < levels; j++ { + parent = fmt.Sprintf("%s/sub", parent) + domain = fmt.Sprintf("%d.%s", j, domain) + buildFakeProjectsDirectory(t, parent, domain) } if i%100 == 0 { fmt.Print(".") } } + cleanup := chdirInPath(t, testRoot) + + return func() { + defer cleanup() + fmt.Printf("cleaning up test directory %s\n", testRoot) + os.RemoveAll(testRoot) + } +} + +func buildFakeProjectsDirectory(t require.TestingT, groupPath, domain string) { + for j := 0; j < 5; j++ { + dir := fmt.Sprintf("%s/project-%d", groupPath, j) + require.NoError(t, os.MkdirAll(dir+"/public", 0755)) + + fakeConfig := fmt.Sprintf(`{"Domains":[{"Domain":"foo.%d.%s","Certificate":"bar","Key":"baz"}]}`, j, domain) + require.NoError(t, ioutil.WriteFile(dir+"/config.json", []byte(fakeConfig), 0644)) + } +} + +func BenchmarkReadGroups(b *testing.B) { + nGroups := 10000 + b.Logf("creating fake domains directory with %d groups", nGroups) + cleanup := buildFakeDomainsDirectory(b, nGroups, 0) + defer cleanup() + b.Run("ReadGroups", func(b *testing.B) { var dm Map for i := 0; i < 2; i++ { |