From 048283093968c3f25070468d58ae68ab78315b9c Mon Sep 17 00:00:00 2001 From: Jason Rudolph Date: Sun, 18 Jul 2021 13:45:21 -0400 Subject: [PATCH] Support glob patterns to specify location of XML report files --- cmd/test-reporter/main.go | 4 +- cmd/test-reporter/main_test.go | 4 +- internal/cmd/submit/submit.go | 241 +++++++++++----- internal/cmd/submit/submit_test.go | 262 ++++++++++++++---- .../browserstack/example-1.xml | 0 .../browserstack/example-2.xml} | 0 .../browsertest/example-3.txt | 0 .../browsertest/example-3.xml | 0 .../dir-without-xml-files}/example.go | 0 .../example-reports-dir/example-1.xml | 6 + .../example-reports-dir/example-2.XML | 6 + .../testdata/example-reports-dir/example.txt | 7 + .../example-test-results/buildpulse.log | 3 - .../example-test-results/buildpulse.yml | 9 - 14 files changed, 402 insertions(+), 140 deletions(-) rename internal/cmd/submit/testdata/{example-test-results/junit => example-reports-dir/dir-with-xml-files}/browserstack/example-1.xml (100%) rename internal/cmd/submit/testdata/{example-test-results/junit/browserstack/example-2.XML => example-reports-dir/dir-with-xml-files/browserstack/example-2.xml} (100%) rename internal/cmd/submit/testdata/{example-test-results/junit => example-reports-dir/dir-with-xml-files}/browsertest/example-3.txt (100%) rename internal/cmd/submit/testdata/{example-test-results/junit => example-reports-dir/dir-with-xml-files}/browsertest/example-3.xml (100%) rename internal/cmd/submit/testdata/{example-test-results/dir-without-relevant-files => example-reports-dir/dir-without-xml-files}/example.go (100%) create mode 100644 internal/cmd/submit/testdata/example-reports-dir/example-1.xml create mode 100644 internal/cmd/submit/testdata/example-reports-dir/example-2.XML create mode 100644 internal/cmd/submit/testdata/example-reports-dir/example.txt delete mode 100644 internal/cmd/submit/testdata/example-test-results/buildpulse.log delete mode 100644 internal/cmd/submit/testdata/example-test-results/buildpulse.yml diff --git a/cmd/test-reporter/main.go b/cmd/test-reporter/main.go index a61124df..acd55150 100644 --- a/cmd/test-reporter/main.go +++ b/cmd/test-reporter/main.go @@ -22,7 +22,7 @@ var usage = strings.ReplaceAll(` CLI to submit test results to BuildPulse USAGE - $ %s submit TEST_RESULTS_DIR --account-id=ACCOUNT_ID --repository-id=REPOSITORY_ID + $ %s submit TEST_RESULTS_PATH --account-id=ACCOUNT_ID --repository-id=REPOSITORY_ID FLAGS --account-id (required) BuildPulse account ID for the account that owns the repository @@ -38,7 +38,7 @@ ENVIRONMENT VARIABLES BUILDPULSE_SECRET_ACCESS_KEY BuildPulse secret access key for the account that owns the repository EXAMPLE - $ %s submit test/reports --account-id 42 --repository-id 8675309 + $ %s submit test/reports/*.xml --account-id 42 --repository-id 8675309 `, "\t", " ") func main() { diff --git a/cmd/test-reporter/main_test.go b/cmd/test-reporter/main_test.go index 95564acb..94894fa6 100644 --- a/cmd/test-reporter/main_test.go +++ b/cmd/test-reporter/main_test.go @@ -74,9 +74,9 @@ func TestCLI(t *testing.T) { }, { name: "submit subcommand with invalid args", - args: fmt.Sprintf("submit %s --account-id bogus --repository-id 8675309", dir), + args: "submit some-non-existent-path", errMsg: "exit status 1", - out: `invalid value "bogus" for flag -account-id`, + out: `no XML reports found at TEST_RESULTS_PATH`, }, { name: "unsupported subcommand", diff --git a/internal/cmd/submit/submit.go b/internal/cmd/submit/submit.go index ec99fc2c..8f8f22eb 100644 --- a/internal/cmd/submit/submit.go +++ b/internal/cmd/submit/submit.go @@ -68,7 +68,7 @@ type Submit struct { version *metadata.Version envs map[string]string - path string + paths []string bucket string accountID uint64 repositoryID uint64 @@ -111,20 +111,30 @@ func (s *Submit) Init(args []string, envs map[string]string, commitResolverFacto } s.logger.Printf("Using working directory: %v", dir) - s.path = args[0] - isFlag, err := regexp.MatchString("^-", s.path) + pathArgs, flagArgs := pathsAndFlagsFromArgs(args) + if len(pathArgs) == 0 { + return fmt.Errorf("missing TEST_RESULTS_PATH") + } + + s.paths, err = xmlPathsFromArgs(pathArgs) if err != nil { return err } - if isFlag { - return fmt.Errorf("missing TEST_RESULTS_DIR") - } - info, err := os.Stat(s.path) - if err != nil || !info.IsDir() { - return fmt.Errorf("path is not a directory: %s", s.path) + if len(s.paths) == 0 { + // To maintain backwards compatibility with releases prior to v0.19.0, if + // exactly one path was given, and it's a directory, and it contains no XML + // reports, continue without erroring. The resulting upload will contain + // *zero* XML reports. In all other scenarios, treat this as an error. + // + // TODO: Treat this scenario as an error for the next major version release. + info, err := os.Stat(pathArgs[0]) + isSingleDir := len(pathArgs) == 1 && err == nil && info.IsDir() + if !isSingleDir { + return fmt.Errorf("no XML reports found at TEST_RESULTS_PATH: %s", strings.Join(pathArgs, " ")) + } } - if err := s.fs.Parse(args[1:]); err != nil { + if err := s.fs.Parse(flagArgs); err != nil { return err } @@ -165,7 +175,7 @@ func (s *Submit) Init(args []string, envs map[string]string, commitResolverFacto return fmt.Errorf("invalid value \"%s\" for flag -tree: should be a 40-character SHA-1 hash", s.tree) } - info, err = os.Stat(s.repositoryPath) + info, err := os.Stat(s.repositoryPath) if err != nil || !info.IsDir() { return fmt.Errorf("invalid value for flag -repository-dir: %s is not a directory", s.repositoryPath) } @@ -195,72 +205,58 @@ func (s *Submit) Init(args []string, envs map[string]string, commitResolverFacto // Run packages up the test results and sends them to BuildPulse. It returns the // key that uniquely identifies the uploaded object. func (s *Submit) Run() (string, error) { - meta, err := metadata.NewMetadata(s.version, s.envs, s.commitResolver, time.Now, s.logger) + tarpath, err := s.bundle() if err != nil { return "", err } - yamlpath := filepath.Join(s.path, "buildpulse.yml") - s.logger.Printf("Writing metadata to %s", yamlpath) - yaml, err := meta.MarshalYAML() - if err != nil { - return "", err - } - err = ioutil.WriteFile(yamlpath, yaml, 0644) + s.logger.Printf("Gzipping tarball (%s)", tarpath) + zippath, err := toGz(tarpath) if err != nil { return "", err } - logpath := filepath.Join(s.path, "buildpulse.log") - s.logger.Printf("Flushing log to %s", logpath) - err = ioutil.WriteFile(logpath, []byte(s.logger.Text()), 0644) + s.logger.Printf("Sending %s to BuildPulse", zippath) + key, err := s.upload(zippath) if err != nil { return "", err } + s.logger.Printf("Delivered test results to BuildPulse (%s)", key) - s.logger.Printf("Preparing gzipped archive of test results and metadata at %s", s.path) - path, err := toTarGz(s.path) + return key, nil +} + +// bundle gathers the artifacts expected by BuildPulse, creates a tarball +// containing those artifacts, and returns the path of the resulting file. +func (s *Submit) bundle() (string, error) { + // Prepare the metadata file + ////////////////////////////////////////////////////////////////////////////// + + s.logger.Printf("Gathering metadata to describe the build") + meta, err := metadata.NewMetadata(s.version, s.envs, s.commitResolver, time.Now, s.logger) if err != nil { return "", err } - s.logger.Printf("Gzipped archive written to %s", path) - - s.logger.Printf("Sending %s to BuildPulse", path) - key, err := s.upload(path) + yaml, err := meta.MarshalYAML() if err != nil { return "", err } - s.logger.Printf("Delivered test results to BuildPulse (%s)", key) - return key, nil -} - -// upload transmits the file at the given path to S3 -func (s *Submit) upload(path string) (string, error) { - key := fmt.Sprintf("%d/%d/buildpulse-%s.gz", s.accountID, s.repositoryID, s.idgen()) - - err := putS3Object(s.client, s.credentials.AccessKeyID, s.credentials.SecretAccessKey, s.bucket, key, path) + yamlfile, err := ioutil.TempFile("", "buildpulse-*.yml") if err != nil { return "", err } + defer yamlfile.Close() - return key, nil -} - -// toTarGz creates a gzipped tarball containing the contents of the named -// directory (dir) and returns the path of the resulting file. -func toTarGz(dir string) (dest string, err error) { - tarPath, err := toTar(dir) + s.logger.Printf("Writing metadata to %s", yamlfile.Name()) + _, err = yamlfile.Write(yaml) if err != nil { return "", err } - return toGz(tarPath) -} + // Initialize the tarfile for writing + ////////////////////////////////////////////////////////////////////////////// -// toTar creates a tarball containing the submittable contents of the named -// directory (dir) and returns the path of the resulting file. -func toTar(dir string) (dest string, err error) { f, err := ioutil.TempFile("", "buildpulse-*.tar") if err != nil { return "", err @@ -270,42 +266,61 @@ func toTar(dir string) (dest string, err error) { t := tar.Create(f) defer t.Close() - err = t.Write(filepath.Join(dir, "buildpulse.yml"), "buildpulse.yml") + // Write the XML reports to the tarfile + ////////////////////////////////////////////////////////////////////////////// + + s.logger.Printf("Preparing tarball of test results:") + for _, p := range s.paths { + s.logger.Printf("- %s", p) + err = t.Write(p, p) + if err != nil { + return "", err + } + } + + // Write the metadata file to the tarfile + ////////////////////////////////////////////////////////////////////////////// + + s.logger.Printf("Adding buildpulse.yml to tarball") + err = t.Write(yamlfile.Name(), "buildpulse.yml") if err != nil { return "", err } - err = t.Write(filepath.Join(dir, "buildpulse.log"), "buildpulse.log") + // Write the log to the tarfile + ////////////////////////////////////////////////////////////////////////////// + + logfile, err := ioutil.TempFile("", "buildpulse-*.log") if err != nil { return "", err } + defer logfile.Close() - err = filepath.WalkDir(dir, func(srcpath string, _ os.DirEntry, err error) error { - if err != nil { - return err - } + s.logger.Printf("Flushing log to %s", logfile.Name()) + _, err = logfile.Write([]byte(s.logger.Text())) + if err != nil { + return "", err + } - if !isXML(srcpath) { - return nil - } + s.logger.Printf("Adding buildpulse.log to tarball") + err = t.Write(logfile.Name(), "buildpulse.log") + if err != nil { + return "", err + } - destpath, err := filepath.Rel(dir, srcpath) - if err != nil { - return err - } + return f.Name(), nil +} - err = t.Write(srcpath, destpath) - if err != nil { - return err - } +// upload transmits the file at the given path to S3 +func (s *Submit) upload(path string) (string, error) { + key := fmt.Sprintf("%d/%d/buildpulse-%s.gz", s.accountID, s.repositoryID, s.idgen()) - return nil - }) + err := putS3Object(s.client, s.credentials.AccessKeyID, s.credentials.SecretAccessKey, s.bucket, key, path) if err != nil { return "", err } - return f.Name(), nil + return key, nil } // toGz gzips the named file (src) and returns the path of the resulting file. @@ -315,7 +330,7 @@ func toGz(src string) (dest string, err error) { return "", err } - zipfile, err := ioutil.TempFile("", "buildpulse-*.tar.gz") + zipfile, err := ioutil.TempFile("", "buildpulse-*.gz") if err != nil { return "", err } @@ -367,6 +382,92 @@ func putS3Object(client *http.Client, id string, secret string, bucket string, o return nil } +// flagRegex matches args that are flags. +var flagRegex = regexp.MustCompile("^-") + +// pathsAndFlagsFromArgs returns a slice containing the subset of args that +// represent paths and a slice containing the subset of args that represent +// flags. +func pathsAndFlagsFromArgs(args []string) ([]string, []string) { + for i, v := range args { + isFlag := flagRegex.MatchString(v) + + if isFlag { + paths := args[0:i] + flags := args[i:] + return paths, flags + } + } + + return args, []string{} +} + +// xmlPathsFromArgs translates each path in args into a list of XML files present +// at that path. It returns the resulting list of XML file paths. +func xmlPathsFromArgs(args []string) ([]string, error) { + var paths []string + + for _, arg := range args { + info, err := os.Stat(arg) + if err == nil && info.IsDir() { + xmls, err := xmlPathsFromDir(arg) + if err != nil { + return nil, err + } + paths = append(paths, xmls...) + } else { + xmls, err := xmlPathsFromGlob(arg) + if err != nil { + return nil, fmt.Errorf("invalid value \"%s\" for path: %v", arg, err) + } + paths = append(paths, xmls...) + } + } + + return paths, nil +} + +// xmlPathsFromDir returns a list of all the XML files in the given directory +// and its subdirectories. +func xmlPathsFromDir(dir string) ([]string, error) { + var paths []string + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if isXML(info.Name()) { + paths = append(paths, path) + } + + return nil + }) + if err != nil { + return nil, err + } + + return paths, nil +} + +// xmlPathsFromGlob returns a list of all the XML files that match the given +// glob pattern. +func xmlPathsFromGlob(pattern string) ([]string, error) { + candidates, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + + var paths []string + for _, p := range candidates { + if isXML(p) { + paths = append(paths, p) + } + } + + return paths, nil +} + // isXML returns true if the given filename has an XML extension // (case-insensitive); false, otherwise. func isXML(filename string) bool { diff --git a/internal/cmd/submit/submit_test.go b/internal/cmd/submit/submit_test.go index c5c654c4..dfa504c9 100644 --- a/internal/cmd/submit/submit_test.go +++ b/internal/cmd/submit/submit_test.go @@ -31,14 +31,11 @@ const ( ) func TestSubmit_Init(t *testing.T) { - resultsDir, err := os.Getwd() - require.NoError(t, err) - t.Run("MinimumRequiredArgs", func(t *testing.T) { s := NewSubmit(&metadata.Version{}, logger.New()) - err = s.Init([]string{resultsDir, "--account-id", "42", "--repository-id", "8675309"}, exampleEnv, new(stubCommitResolverFactory)) + err := s.Init([]string{"testdata/example-reports-dir/example-*.xml", "--account-id", "42", "--repository-id", "8675309"}, exampleEnv, new(stubCommitResolverFactory)) require.NoError(t, err) - assert.Equal(t, resultsDir, s.path) + assert.ElementsMatch(t, []string{"testdata/example-reports-dir/example-1.xml"}, s.paths) assert.EqualValues(t, 42, s.accountID) assert.EqualValues(t, 8675309, s.repositoryID) assert.Equal(t, "buildpulse-uploads", s.bucket) @@ -49,30 +46,79 @@ func TestSubmit_Init(t *testing.T) { assert.Equal(t, "Repository", s.commitResolver.Source()) }) + t.Run("WithMultiplePathArgs", func(t *testing.T) { + s := NewSubmit(&metadata.Version{}, logger.New()) + err := s.Init( + []string{"testdata/example-reports-dir/example-1.xml", "testdata/example-reports-dir/example-2.XML", "--account-id", "42", "--repository-id", "8675309"}, + exampleEnv, + new(stubCommitResolverFactory), + ) + require.NoError(t, err) + assert.ElementsMatch(t, []string{"testdata/example-reports-dir/example-1.xml", "testdata/example-reports-dir/example-2.XML"}, s.paths) + assert.EqualValues(t, 42, s.accountID) + assert.EqualValues(t, 8675309, s.repositoryID) + }) + + t.Run("WithDirectoryWithReportsAsPathArg", func(t *testing.T) { + s := NewSubmit(&metadata.Version{}, logger.New()) + err := s.Init( + []string{"testdata/example-reports-dir/dir-with-xml-files/browserstack", "--account-id", "42", "--repository-id", "8675309"}, + exampleEnv, + new(stubCommitResolverFactory), + ) + require.NoError(t, err) + assert.ElementsMatch(t, + []string{ + "testdata/example-reports-dir/dir-with-xml-files/browserstack/example-1.xml", + "testdata/example-reports-dir/dir-with-xml-files/browserstack/example-2.xml", + }, + s.paths, + ) + assert.EqualValues(t, 42, s.accountID) + assert.EqualValues(t, 8675309, s.repositoryID) + }) + + // To maintain backwards compatibility with releases prior to v0.19.0, if + // exactly one path is given, and it's a directory, and it contains no XML + // reports, then continue without erroring. The resulting upload will contain + // *zero* XML reports. + // + // TODO: Treat this scenario as an error for the next major version release. + t.Run("WithDirectoryWithoutReportsAsPathArg", func(t *testing.T) { + s := NewSubmit(&metadata.Version{}, logger.New()) + err := s.Init( + []string{"testdata/example-reports-dir/dir-without-xml-files", "--account-id", "42", "--repository-id", "8675309"}, + exampleEnv, + new(stubCommitResolverFactory), + ) + require.NoError(t, err) + assert.Empty(t, s.paths) + assert.EqualValues(t, 42, s.accountID) + assert.EqualValues(t, 8675309, s.repositoryID) + }) + t.Run("WithRepositoryDirArg", func(t *testing.T) { repoDir := t.TempDir() s := NewSubmit(&metadata.Version{}, logger.New()) - err = s.Init( - []string{resultsDir, "--account-id", "42", "--repository-id", "8675309", "--repository-dir", repoDir}, + err := s.Init( + []string{"testdata/example-reports-dir/example-*.xml", "--account-id", "42", "--repository-id", "8675309", "--repository-dir", repoDir}, exampleEnv, new(stubCommitResolverFactory), ) require.NoError(t, err) - assert.Equal(t, resultsDir, s.path) assert.Equal(t, repoDir, s.repositoryPath) assert.Equal(t, "Repository", s.commitResolver.Source()) }) t.Run("WithTreeArg", func(t *testing.T) { s := NewSubmit(&metadata.Version{}, logger.New()) - err = s.Init( - []string{resultsDir, "--account-id", "42", "--repository-id", "8675309", "--tree", "0000000000000000000000000000000000000000"}, + err := s.Init( + []string{"testdata/example-reports-dir/example-*.xml", "--account-id", "42", "--repository-id", "8675309", "--tree", "0000000000000000000000000000000000000000"}, exampleEnv, new(stubCommitResolverFactory), ) require.NoError(t, err) - assert.Equal(t, resultsDir, s.path) assert.Equal(t, "Static", s.commitResolver.Source()) }) @@ -85,8 +131,8 @@ func TestSubmit_Init(t *testing.T) { "BUILDPULSE_BUCKET": "buildpulse-uploads-test", } s := NewSubmit(&metadata.Version{}, logger.New()) - err = s.Init( - []string{resultsDir, "--account-id", "42", "--repository-id", "8675309", "--repository-dir", repoDir}, + err := s.Init( + []string{"testdata/example-reports-dir/example-*.xml", "--account-id", "42", "--repository-id", "8675309", "--repository-dir", repoDir}, envs, new(stubCommitResolverFactory), ) @@ -112,7 +158,7 @@ func TestSubmit_Init_invalidArgs(t *testing.T) { { name: "FlagsWithNoPath", args: "--account-id 1 --repository-id 2", - errMsg: "missing TEST_RESULTS_DIR", + errMsg: "missing TEST_RESULTS_PATH", }, { name: "MissingAccountID", @@ -219,7 +265,7 @@ func TestSubmit_Init_invalidEnv(t *testing.T) { } } -func TestSubmit_Init_invalidPath(t *testing.T) { +func TestSubmit_Init_invalidPaths(t *testing.T) { t.Run("NonexistentPath", func(t *testing.T) { s := NewSubmit(&metadata.Version{}, logger.New()) err := s.Init([]string{ @@ -231,18 +277,38 @@ func TestSubmit_Init_invalidPath(t *testing.T) { &stubCommitResolverFactory{}, ) if assert.Error(t, err) { - assert.Equal(t, "path is not a directory: some-nonexistent-path", err.Error()) + assert.Equal(t, "no XML reports found at TEST_RESULTS_PATH: some-nonexistent-path", err.Error()) } }) - t.Run("NonDirectoryPath", func(t *testing.T) { - tmpfile, err := ioutil.TempFile("", "buildpulse-cli-test-fixture") - require.NoError(t, err) - defer os.Remove(tmpfile.Name()) + t.Run("MultiplePathsWithNoReportFiles", func(t *testing.T) { + s := NewSubmit(&metadata.Version{}, logger.New()) + err := s.Init([]string{ + "testdata/example-reports-dir/dir-without-xml-files", + "testdata/example-reports-dir/example.txt", + "--account-id", "42", + "--repository-id", "8675309", + }, + exampleEnv, + &stubCommitResolverFactory{}, + ) + if assert.Error(t, err) { + assert.Equal(t, "no XML reports found at TEST_RESULTS_PATH: testdata/example-reports-dir/dir-without-xml-files testdata/example-reports-dir/example.txt", err.Error()) + } + }) + t.Run("GlobPathMatchingNoReportFiles", func(t *testing.T) { s := NewSubmit(&metadata.Version{}, logger.New()) - err = s.Init([]string{ - tmpfile.Name(), + err := s.Init([]string{"testdata/example-reports-dir/bogus*", "--account-id", "42", "--repository-id", "8675309"}, exampleEnv, &stubCommitResolverFactory{}) + if assert.Error(t, err) { + assert.Equal(t, "no XML reports found at TEST_RESULTS_PATH: testdata/example-reports-dir/bogus*", err.Error()) + } + }) + + t.Run("BadGlobPattern", func(t *testing.T) { + s := NewSubmit(&metadata.Version{}, logger.New()) + err := s.Init([]string{ + "[", "--account-id", "42", "--repository-id", "8675309", }, @@ -250,7 +316,7 @@ func TestSubmit_Init_invalidPath(t *testing.T) { &stubCommitResolverFactory{}, ) if assert.Error(t, err) { - assert.Regexp(t, "path is not a directory: ", err.Error()) + assert.Equal(t, `invalid value "[" for path: syntax error in pattern`, err.Error()) } }) } @@ -311,8 +377,6 @@ func TestSubmit_Init_invalidRepoPath(t *testing.T) { } func TestSubmit_Run(t *testing.T) { - dir := t.TempDir() - r, err := recorder.New("testdata/s3-success") require.NoError(t, err) defer func() { @@ -332,7 +396,7 @@ func TestSubmit_Run(t *testing.T) { version: &metadata.Version{Number: "v1.2.3"}, commitResolver: metadata.NewStaticCommitResolver(&metadata.Commit{TreeSHA: "ccccccccccccccccccccdddddddddddddddddddd"}, log), envs: envs, - path: dir, + paths: []string{"testdata/example-reports-dir/example-1.xml"}, bucket: "buildpulse-uploads", accountID: 42, repositoryID: 8675309, @@ -344,15 +408,52 @@ func TestSubmit_Run(t *testing.T) { key, err := s.Run() require.NoError(t, err) + assert.Equal(t, "42/8675309/buildpulse-00000000-0000-0000-0000-000000000000.gz", key) +} + +func Test_bundle(t *testing.T) { + envs := map[string]string{ + "GITHUB_ACTIONS": "true", + "GITHUB_SHA": "aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbb", + } + + log := logger.New() + s := &Submit{ + logger: log, + version: &metadata.Version{Number: "v1.2.3"}, + commitResolver: metadata.NewStaticCommitResolver(&metadata.Commit{TreeSHA: "ccccccccccccccccccccdddddddddddddddddddd"}, log), + envs: envs, + paths: []string{"testdata/example-reports-dir/example-1.xml"}, + bucket: "buildpulse-uploads", + accountID: 42, + repositoryID: 8675309, + } + + path, err := s.bundle() + require.NoError(t, err) - yaml, err := ioutil.ReadFile(filepath.Join(dir, "buildpulse.yml")) + unzipDir := t.TempDir() + err = archiver.Unarchive(path, unzipDir) + require.NoError(t, err) + + // Verify buildpulse.yml is present and contains expected content + yaml, err := ioutil.ReadFile(filepath.Join(unzipDir, "buildpulse.yml")) require.NoError(t, err) assert.Contains(t, string(yaml), ":ci_provider: github-actions") assert.Contains(t, string(yaml), ":commit: aaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbb") assert.Contains(t, string(yaml), ":tree: ccccccccccccccccccccdddddddddddddddddddd") assert.Contains(t, string(yaml), ":reporter_version: v1.2.3") - assert.Equal(t, "42/8675309/buildpulse-00000000-0000-0000-0000-000000000000.gz", key) + // Verify test report XML file is present and contains expected content + assertEqualContent(t, + "testdata/example-reports-dir/example-1.xml", + filepath.Join(unzipDir, "testdata/example-reports-dir/example-1.xml"), + ) + + // Verify buildpulse.log is present and contains expected content + logdata, err := ioutil.ReadFile(filepath.Join(unzipDir, "buildpulse.log")) + require.NoError(t, err) + assert.Contains(t, string(logdata), "Gathering metadata to describe the build") } func Test_upload(t *testing.T) { @@ -445,39 +546,92 @@ func Test_upload(t *testing.T) { } } -func Test_toTarGz(t *testing.T) { - path, err := toTarGz("./testdata/example-test-results") +func Test_toGz(t *testing.T) { + path, err := toGz("testdata/example-reports-dir/example.txt") require.NoError(t, err) + assert.Regexp(t, `\.gz$`, path) dir := t.TempDir() - err = archiver.Unarchive(path, dir) + unzippedPath := filepath.Join(dir, "example.txt") + err = archiver.DecompressFile(path, unzippedPath) require.NoError(t, err) - // === Verify original directory content matches resulting directory content - assertEqualContent(t, - "testdata/example-test-results/buildpulse.yml", - filepath.Join(dir, "buildpulse.yml"), - ) - assertEqualContent(t, - "testdata/example-test-results/junit/browserstack/example-1.xml", - filepath.Join(dir, "junit/browserstack/example-1.xml"), - ) - assertEqualContent(t, - "testdata/example-test-results/junit/browserstack/example-2.XML", - filepath.Join(dir, "junit/browserstack/example-2.XML"), - ) - assertEqualContent(t, - "testdata/example-test-results/junit/browsertest/example-3.xml", - filepath.Join(dir, "junit/browsertest/example-3.xml"), - ) + // === Verify original content matches resulting content + assertEqualContent(t, "testdata/example-reports-dir/example.txt", unzippedPath) +} - // === Verify tarball excludes irrelevant files - assert.FileExists(t, "testdata/example-test-results/junit/browsertest/example-3.txt") - assert.NoFileExists(t, filepath.Join(dir, "junit/browsertest/example-3.txt")) +func Test_xmlPathsFromDir(t *testing.T) { + tests := []struct { + name string + path string + want []string + }{ + { + name: "DirectoryWithFilesAtRootAndInSubDirectories", + path: "testdata/example-reports-dir", + want: []string{ + "testdata/example-reports-dir/example-1.xml", + "testdata/example-reports-dir/example-2.XML", + "testdata/example-reports-dir/dir-with-xml-files/browserstack/example-1.xml", + "testdata/example-reports-dir/dir-with-xml-files/browserstack/example-2.xml", + "testdata/example-reports-dir/dir-with-xml-files/browsertest/example-3.xml", + }, + }, + { + name: "DirectoryWithoutXMLFiles", + path: "testdata/example-reports-dir/dir-without-xml-files", + want: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reportPaths, err := xmlPathsFromDir(tt.path) + require.NoError(t, err) + assert.ElementsMatch(t, tt.want, reportPaths) + }) + } +} + +func Test_xmlPathsFromGlob(t *testing.T) { + tests := []struct { + name string + path string + want []string + }{ + { + name: "ExactPathToSingleFile", + path: "testdata/example-reports-dir/example-1.xml", + want: []string{ + "testdata/example-reports-dir/example-1.xml", + }, + }, + { + name: "PathMatchingFilesByWildcard", + path: "testdata/example-reports-dir/example*", + want: []string{ + "testdata/example-reports-dir/example-1.xml", + "testdata/example-reports-dir/example-2.XML", + }, + }, + { + name: "PathMatchingDirectoriesAndFilesByWildcard", + path: "testdata/example-reports-dir/dir-with-xml-files/*/*.xml", + want: []string{ + "testdata/example-reports-dir/dir-with-xml-files/browserstack/example-1.xml", + "testdata/example-reports-dir/dir-with-xml-files/browserstack/example-2.xml", + "testdata/example-reports-dir/dir-with-xml-files/browsertest/example-3.xml", + }, + }, + } - // === Verify tarball excludes irrelevant directories (i.e., directories that contain no relevant files) - assert.DirExists(t, "testdata/example-test-results/dir-without-relevant-files") - assert.NoDirExists(t, filepath.Join(dir, "dir-without-relevant-files")) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + reportPaths, err := xmlPathsFromGlob(tt.path) + require.NoError(t, err) + assert.ElementsMatch(t, tt.want, reportPaths) + }) + } } // assertEqualContent asserts that two files have the same content. diff --git a/internal/cmd/submit/testdata/example-test-results/junit/browserstack/example-1.xml b/internal/cmd/submit/testdata/example-reports-dir/dir-with-xml-files/browserstack/example-1.xml similarity index 100% rename from internal/cmd/submit/testdata/example-test-results/junit/browserstack/example-1.xml rename to internal/cmd/submit/testdata/example-reports-dir/dir-with-xml-files/browserstack/example-1.xml diff --git a/internal/cmd/submit/testdata/example-test-results/junit/browserstack/example-2.XML b/internal/cmd/submit/testdata/example-reports-dir/dir-with-xml-files/browserstack/example-2.xml similarity index 100% rename from internal/cmd/submit/testdata/example-test-results/junit/browserstack/example-2.XML rename to internal/cmd/submit/testdata/example-reports-dir/dir-with-xml-files/browserstack/example-2.xml diff --git a/internal/cmd/submit/testdata/example-test-results/junit/browsertest/example-3.txt b/internal/cmd/submit/testdata/example-reports-dir/dir-with-xml-files/browsertest/example-3.txt similarity index 100% rename from internal/cmd/submit/testdata/example-test-results/junit/browsertest/example-3.txt rename to internal/cmd/submit/testdata/example-reports-dir/dir-with-xml-files/browsertest/example-3.txt diff --git a/internal/cmd/submit/testdata/example-test-results/junit/browsertest/example-3.xml b/internal/cmd/submit/testdata/example-reports-dir/dir-with-xml-files/browsertest/example-3.xml similarity index 100% rename from internal/cmd/submit/testdata/example-test-results/junit/browsertest/example-3.xml rename to internal/cmd/submit/testdata/example-reports-dir/dir-with-xml-files/browsertest/example-3.xml diff --git a/internal/cmd/submit/testdata/example-test-results/dir-without-relevant-files/example.go b/internal/cmd/submit/testdata/example-reports-dir/dir-without-xml-files/example.go similarity index 100% rename from internal/cmd/submit/testdata/example-test-results/dir-without-relevant-files/example.go rename to internal/cmd/submit/testdata/example-reports-dir/dir-without-xml-files/example.go diff --git a/internal/cmd/submit/testdata/example-reports-dir/example-1.xml b/internal/cmd/submit/testdata/example-reports-dir/example-1.xml new file mode 100644 index 00000000..8bcb2a42 --- /dev/null +++ b/internal/cmd/submit/testdata/example-reports-dir/example-1.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/internal/cmd/submit/testdata/example-reports-dir/example-2.XML b/internal/cmd/submit/testdata/example-reports-dir/example-2.XML new file mode 100644 index 00000000..36546aa1 --- /dev/null +++ b/internal/cmd/submit/testdata/example-reports-dir/example-2.XML @@ -0,0 +1,6 @@ + + + + + + diff --git a/internal/cmd/submit/testdata/example-reports-dir/example.txt b/internal/cmd/submit/testdata/example-reports-dir/example.txt new file mode 100644 index 00000000..bfb04df9 --- /dev/null +++ b/internal/cmd/submit/testdata/example-reports-dir/example.txt @@ -0,0 +1,7 @@ +Bacon ipsum dolor sit amet shoulder fatback venison, tongue spare ribs sausage +cow swine bacon boudin tri-tip biltong bresaola shank. Sirloin chicken pork +loin, spare ribs cow pancetta pastrami jowl beef beef ribs ball tip tongue pork +belly turkey chuck. T-bone tenderloin beef, swine short ribs pork chop ribeye. +Beef ribs salami pork belly, cow ground round flank venison ham sirloin jowl. +Bacon shoulder short loin, chuck fatback corned beef pork belly pig. Ribeye +swine strip steak jowl pork chop. Bacon tongue pork chop biltong. diff --git a/internal/cmd/submit/testdata/example-test-results/buildpulse.log b/internal/cmd/submit/testdata/example-test-results/buildpulse.log deleted file mode 100644 index 297df73c..00000000 --- a/internal/cmd/submit/testdata/example-test-results/buildpulse.log +++ /dev/null @@ -1,3 +0,0 @@ - Current version: BuildPulse Test Reporter 0.17.0 (linux 45a62ed go1.16.5) - Initiating `submit` - ... diff --git a/internal/cmd/submit/testdata/example-test-results/buildpulse.yml b/internal/cmd/submit/testdata/example-test-results/buildpulse.yml deleted file mode 100644 index 2f1dcdd6..00000000 --- a/internal/cmd/submit/testdata/example-test-results/buildpulse.yml +++ /dev/null @@ -1,9 +0,0 @@ -:branch: origin/some-branch -:build_url: https://example.com/job/some-project/8675309 -:check: some-check -:ci_provider: some-provider -:commit: 1f192ff735f887dd7a25229b2ece0422d17931f5 -:repo_name_with_owner: some-owner/some-repo -:reporter_os: linux -:reporter_version: v1.2.3 -:timestamp: 2020-07-11T01:02:03Z