// +build ignore // Build a directory structure with the required number of files in // // Run with go run make_test_files.go [flag] package main import ( cryptrand "crypto/rand" "flag" "io" "log" "math/rand" "os" "path/filepath" ) var ( // Flags numberOfFiles = flag.Int("n", 1000, "Number of files to create") averageFilesPerDirectory = flag.Int("files-per-directory", 10, "Average number of files per directory") maxDepth = flag.Int("max-depth", 10, "Maximum depth of directory hierarchy") minFileSize = flag.Int64("min-size", 0, "Minimum size of file to create") maxFileSize = flag.Int64("max-size", 100, "Maximum size of files to create") minFileNameLength = flag.Int("min-name-length", 4, "Minimum size of file to create") maxFileNameLength = flag.Int("max-name-length", 12, "Maximum size of files to create") directoriesToCreate int totalDirectories int fileNames = map[string]struct{}{} // keep a note of which file name we've used already ) // randomString create a random string for test purposes func randomString(n int) string { const ( vowel = "aeiou" consonant = "bcdfghjklmnpqrstvwxyz" digit = "0123456789" ) pattern := []string{consonant, vowel, consonant, vowel, consonant, vowel, consonant, digit} out := make([]byte, n) p := 0 for i := range out { source := pattern[p] p = (p + 1) % len(pattern) out[i] = source[rand.Intn(len(source))] } return string(out) } // fileName creates a unique random file or directory name func fileName() (name string) { for { length := rand.Intn(*maxFileNameLength-*minFileNameLength) + *minFileNameLength name = randomString(length) if _, found := fileNames[name]; !found { break } } fileNames[name] = struct{}{} return name } // dir is a directory in the directory hierarchy being built up type dir struct { name string depth int children []*dir parent *dir } // Create a random directory hierarchy under d func (d *dir) createDirectories() { for totalDirectories < directoriesToCreate { newDir := &dir{ name: fileName(), depth: d.depth + 1, parent: d, } d.children = append(d.children, newDir) totalDirectories++ switch rand.Intn(4) { case 0: if d.depth < *maxDepth { newDir.createDirectories() } case 1: return } } return } // list the directory hierarchy func (d *dir) list(path string, output []string) []string { dirPath := filepath.Join(path, d.name) output = append(output, dirPath) for _, subDir := range d.children { output = subDir.list(dirPath, output) } return output } // writeFile writes a random file at dir/name func writeFile(dir, name string) { err := os.MkdirAll(dir, 0777) if err != nil { log.Fatalf("Failed to make directory %q: %v", dir, err) } path := filepath.Join(dir, name) fd, err := os.Create(path) if err != nil { log.Fatalf("Failed to open file %q: %v", path, err) } size := rand.Int63n(*maxFileSize-*minFileSize) + *minFileSize _, err = io.CopyN(fd, cryptrand.Reader, size) if err != nil { log.Fatalf("Failed to write %v bytes to file %q: %v", size, path, err) } err = fd.Close() if err != nil { log.Fatalf("Failed to close file %q: %v", path, err) } } func main() { flag.Parse() args := flag.Args() if len(args) != 1 { log.Fatalf("Require 1 directory argument") } outputDirectory := args[0] log.Printf("Output dir %q", outputDirectory) directoriesToCreate = *numberOfFiles / *averageFilesPerDirectory log.Printf("directoriesToCreate %v", directoriesToCreate) root := &dir{name: outputDirectory, depth: 1} for totalDirectories < directoriesToCreate { root.createDirectories() } dirs := root.list("", []string{}) for i := 0; i < *numberOfFiles; i++ { dir := dirs[rand.Intn(len(dirs))] writeFile(dir, fileName()) } }