1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-16 05:16:08 +02:00

Enhance piperutils.Files and mock.FilesMock (#1664)

* Flesh out piperutils.Files and mock.FilesMock functionality
* Avoid a lot of code-duplication via embedding
This commit is contained in:
Stephan Aßmus 2020-06-15 09:47:33 +02:00 committed by GitHub
parent 4e73a699c2
commit f855658e06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 622 additions and 261 deletions

View File

@ -25,7 +25,7 @@ func TestMavenBuild(t *testing.T) {
execMockRunner := mock.ExecMockRunner{}
mockedUtils := mock.FilesMock{}
mockedUtils.Files = []string{"integration-tests/pom.xml"}
mockedUtils.AddFile("integration-tests/pom.xml", []byte{})
config := mavenBuildOptions{}

View File

@ -2,7 +2,6 @@ package cmd
import (
"fmt"
"github.com/bmatcuk/doublestar"
"github.com/pkg/errors"
"os"
"path/filepath"
@ -23,82 +22,44 @@ import (
// nexusUploadUtils defines an interface for utility functionality used from external packages,
// so it can be easily mocked for testing.
type nexusUploadUtils interface {
fileExists(path string) (bool, error)
fileRead(path string) ([]byte, error)
fileWrite(path string, content []byte, perm os.FileMode) error
fileRemove(path string)
dirExists(path string) (bool, error)
usesMta() bool
usesMaven() bool
usesNpm() bool
FileExists(path string) (bool, error)
FileRead(path string) ([]byte, error)
FileWrite(path string, content []byte, perm os.FileMode) error
FileRemove(path string) error
DirExists(path string) (bool, error)
Glob(pattern string) (matches []string, err error)
UsesMta() bool
UsesMaven() bool
UsesNpm() bool
getEnvParameter(path, name string) string
getExecRunner() execRunner
evaluate(options *maven.EvaluateOptions, expression string) (string, error)
glob(pattern string) (matches []string, err error)
}
type utilsBundle struct {
projectStructure piperutils.ProjectStructure
fileUtils piperutils.Files
execRunner *command.Command
*piperutils.ProjectStructure
*piperutils.Files
execRunner *command.Command
}
func newUtilsBundle() *utilsBundle {
return &utilsBundle{
projectStructure: piperutils.ProjectStructure{},
fileUtils: piperutils.Files{},
ProjectStructure: &piperutils.ProjectStructure{},
Files: &piperutils.Files{},
}
}
func (u *utilsBundle) fileExists(path string) (bool, error) {
return u.fileUtils.FileExists(path)
}
func (u *utilsBundle) fileRead(path string) ([]byte, error) {
return u.fileUtils.FileRead(path)
}
func (u *utilsBundle) fileWrite(filePath string, content []byte, perm os.FileMode) error {
func (u *utilsBundle) FileWrite(filePath string, content []byte, perm os.FileMode) error {
parent := filepath.Dir(filePath)
if parent != "" {
err := u.fileUtils.MkdirAll(parent, 0775)
err := u.Files.MkdirAll(parent, 0775)
if err != nil {
return err
}
}
return u.fileUtils.FileWrite(filePath, content, perm)
}
func (u *utilsBundle) fileRemove(path string) {
err := os.Remove(path)
if err != nil {
log.Entry().WithError(err).Warnf("Failed to remove file '%s'.", path)
}
}
func (u *utilsBundle) dirExists(path string) (bool, error) {
info, err := os.Stat(path)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, err
}
return info.IsDir(), nil
}
func (u *utilsBundle) usesMta() bool {
return u.projectStructure.UsesMta()
}
func (u *utilsBundle) usesMaven() bool {
return u.projectStructure.UsesMaven()
}
func (u *utilsBundle) usesNpm() bool {
return u.projectStructure.UsesNpm()
return u.Files.FileWrite(filePath, content, perm)
}
func (u *utilsBundle) getEnvParameter(path, name string) string {
@ -118,10 +79,6 @@ func (u *utilsBundle) evaluate(options *maven.EvaluateOptions, expression string
return maven.Evaluate(options, expression, u.getExecRunner())
}
func (u *utilsBundle) glob(pattern string) (matches []string, err error) {
return doublestar.Glob(pattern)
}
func nexusUpload(options nexusUploadOptions, _ *telemetry.CustomData) {
utils := newUtilsBundle()
uploader := nexus.Upload{}
@ -140,7 +97,7 @@ func runNexusUpload(utils nexusUploadUtils, uploader nexus.Uploader, options *ne
return err
}
if utils.usesNpm() && performNpmUpload {
if utils.UsesNpm() && performNpmUpload {
log.Entry().Info("NPM project structure detected")
err = uploadNpmArtifacts(utils, uploader, options)
} else {
@ -151,10 +108,10 @@ func runNexusUpload(utils nexusUploadUtils, uploader nexus.Uploader, options *ne
}
if performMavenUpload {
if utils.usesMta() {
if utils.UsesMta() {
log.Entry().Info("MTA project structure detected")
return uploadMTA(utils, uploader, options)
} else if utils.usesMaven() {
} else if utils.UsesMaven() {
log.Entry().Info("Maven project structure detected")
return uploadMaven(utils, uploader, options)
}
@ -184,7 +141,7 @@ func uploadMTA(utils nexusUploadUtils, uploader nexus.Uploader, options *nexusUp
return fmt.Errorf("the 'groupId' parameter needs to be provided for MTA projects")
}
var mtaPath string
exists, _ := utils.fileExists("mta.yaml")
exists, _ := utils.FileExists("mta.yaml")
if exists {
mtaPath = "mta.yaml"
// Give this file precedence, but it would be even better if
@ -223,7 +180,7 @@ type mtaYaml struct {
}
func getInfoFromMtaFile(utils nexusUploadUtils, filePath string) (*mtaYaml, error) {
mtaYamlContent, err := utils.fileRead(filePath)
mtaYamlContent, err := utils.FileRead(filePath)
if err != nil {
return nil, fmt.Errorf("could not read from required project descriptor file '%s'",
filePath)
@ -273,7 +230,7 @@ func setupNexusCredentialsSettingsFile(utils nexusUploadUtils, options *nexusUpl
return "", nil
}
err := utils.fileWrite(settingsPath, []byte(nexusMavenSettings), os.ModePerm)
err := utils.FileWrite(settingsPath, []byte(nexusMavenSettings), os.ModePerm)
if err != nil {
return "", fmt.Errorf("failed to write maven settings file to '%s': %w", settingsPath, err)
}
@ -322,7 +279,7 @@ func uploadArtifacts(utils nexusUploadUtils, uploader nexus.Uploader, options *n
return fmt.Errorf("writing credential settings for maven failed: %w", err)
}
if settingsFile != "" {
defer utils.fileRemove(settingsFile)
defer func() { _ = utils.FileRemove(settingsFile) }()
}
// iterate over the artifact descriptions, the first one is the main artifact, the following ones are
@ -385,7 +342,7 @@ func uploadArtifactsBundle(d artifactDefines, generatePOM bool, mavenOptions mav
}
func addArtifact(utils nexusUploadUtils, uploader nexus.Uploader, filePath, classifier, fileType string) error {
exists, _ := utils.fileExists(filePath)
exists, _ := utils.FileExists(filePath)
if !exists {
return fmt.Errorf("artifact file not found '%s'", filePath)
}
@ -400,7 +357,7 @@ func addArtifact(utils nexusUploadUtils, uploader nexus.Uploader, filePath, clas
var errPomNotFound = errors.New("pom.xml not found")
func uploadMaven(utils nexusUploadUtils, uploader nexus.Uploader, options *nexusUploadOptions) error {
pomFiles, _ := utils.glob("**/pom.xml")
pomFiles, _ := utils.Glob("**/pom.xml")
if len(pomFiles) == 0 {
return errPomNotFound
}
@ -434,7 +391,7 @@ func uploadMavenArtifacts(utils nexusUploadUtils, uploader nexus.Uploader, optio
}
if packaging != "pom" {
// Ignore this module if there is no 'target' folder
hasTarget, _ := utils.dirExists(targetFolder)
hasTarget, _ := utils.DirExists(targetFolder)
if !hasTarget {
log.Entry().Warnf("Ignoring module '%s' as it has no 'target' folder", pomPath)
return nil
@ -481,7 +438,7 @@ func addMavenTargetArtifacts(utils nexusUploadUtils, uploader nexus.Uploader, po
for _, fileType := range fileTypes {
pattern := targetFolder + "/*." + fileType
matches, _ := utils.glob(pattern)
matches, _ := utils.Glob(pattern)
if len(matches) == 0 && fileType == packaging {
return fmt.Errorf("target artifact not found for packaging '%s'", packaging)
}

View File

@ -5,85 +5,39 @@ import (
"github.com/SAP/jenkins-library/pkg/maven"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/SAP/jenkins-library/pkg/nexus"
"github.com/bmatcuk/doublestar"
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"sort"
"testing"
)
type mockUtilsBundle struct {
mta bool
maven bool
npm bool
files map[string][]byte
removedFiles map[string][]byte
properties map[string]map[string]string
cpe map[string]string
execRunner mock.ExecMockRunner
*mock.FilesMock
mta bool
maven bool
npm bool
properties map[string]map[string]string
cpe map[string]string
execRunner mock.ExecMockRunner
}
func newMockUtilsBundle(usesMta, usesMaven, usesNpm bool) mockUtilsBundle {
utils := mockUtilsBundle{mta: usesMta, maven: usesMaven, npm: usesNpm}
utils.files = map[string][]byte{}
utils.removedFiles = map[string][]byte{}
utils := mockUtilsBundle{FilesMock: &mock.FilesMock{}, mta: usesMta, maven: usesMaven, npm: usesNpm}
utils.properties = map[string]map[string]string{}
utils.cpe = map[string]string{}
return utils
}
func (m *mockUtilsBundle) usesMta() bool {
func (m *mockUtilsBundle) UsesMta() bool {
return m.mta
}
func (m *mockUtilsBundle) usesMaven() bool {
func (m *mockUtilsBundle) UsesMaven() bool {
return m.maven
}
func (m *mockUtilsBundle) usesNpm() bool {
func (m *mockUtilsBundle) UsesNpm() bool {
return m.npm
}
func (m *mockUtilsBundle) fileExists(path string) (bool, error) {
content := m.files[path]
if content == nil {
return false, fmt.Errorf("'%s': %w", path, os.ErrNotExist)
}
return true, nil
}
func (m *mockUtilsBundle) fileRead(path string) ([]byte, error) {
content := m.files[path]
if content == nil {
return nil, fmt.Errorf("could not read '%s'", path)
}
return content, nil
}
func (m *mockUtilsBundle) fileWrite(path string, content []byte, _ os.FileMode) error {
m.files[path] = content
return nil
}
func (m *mockUtilsBundle) fileRemove(path string) {
contents := m.files[path]
m.files[path] = nil
if contents != nil {
m.removedFiles[path] = contents
}
}
func (m *mockUtilsBundle) dirExists(path string) (bool, error) {
for file := range m.files {
dir := filepath.Dir(file)
if dir == path {
return true, nil
}
}
return false, nil
}
func (m *mockUtilsBundle) getEnvParameter(path, name string) string {
path = path + "/" + name
return m.cpe[path]
@ -117,33 +71,6 @@ func (m *mockUtilsBundle) evaluate(options *maven.EvaluateOptions, expression st
return value, nil
}
type byLen []string
func (a byLen) Len() int {
return len(a)
}
func (a byLen) Less(i, j int) bool {
return len(a[i]) < len(a[j])
}
func (a byLen) Swap(i, j int) {
a[i], a[j] = a[j], a[i]
}
func (m *mockUtilsBundle) glob(pattern string) ([]string, error) {
var matches []string
for path := range m.files {
matched, _ := doublestar.Match(pattern, path)
if matched {
matches = append(matches, path)
}
}
// The order in m.files is not deterministic, this would result in flaky tests.
sort.Sort(byLen(matches))
return matches, nil
}
type mockUploader struct {
nexus.Upload
uploadedArtifacts []nexus.ArtifactDescription
@ -205,9 +132,10 @@ var testPackageJson = []byte(`{
`)
func TestUploadMTAProjects(t *testing.T) {
t.Parallel()
t.Run("Uploading MTA project without groupId parameter fails", func(t *testing.T) {
utils := newMockUtilsBundle(true, false, false)
utils.files["mta.yaml"] = testMtaYml
utils.AddFile("mta.yaml", testMtaYml)
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
uploader := mockUploader{}
options := createOptions()
@ -220,8 +148,8 @@ func TestUploadMTAProjects(t *testing.T) {
})
t.Run("Uploading MTA project without artifactId parameter works", func(t *testing.T) {
utils := newMockUtilsBundle(true, false, false)
utils.files["mta.yaml"] = testMtaYml
utils.files["test.mtar"] = []byte("contentsOfMtar")
utils.AddFile("mta.yaml", testMtaYml)
utils.AddFile("test.mtar", []byte("contentsOfMtar"))
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
uploader := mockUploader{}
options := createOptions()
@ -246,7 +174,7 @@ func TestUploadMTAProjects(t *testing.T) {
})
t.Run("Uploading MTA project fails due to garbage YAML content", func(t *testing.T) {
utils := newMockUtilsBundle(true, false, false)
utils.files["mta.yaml"] = []byte("garbage")
utils.AddFile("mta.yaml", []byte("garbage"))
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
uploader := mockUploader{}
options := createOptions()
@ -259,7 +187,7 @@ func TestUploadMTAProjects(t *testing.T) {
})
t.Run("Uploading MTA project fails due invalid version in YAML content", func(t *testing.T) {
utils := newMockUtilsBundle(true, false, false)
utils.files["mta.yaml"] = []byte(testMtaYmlNoVersion)
utils.AddFile("mta.yaml", []byte(testMtaYmlNoVersion))
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
uploader := mockUploader{}
options := createOptions()
@ -272,7 +200,7 @@ func TestUploadMTAProjects(t *testing.T) {
})
t.Run("Test uploading mta.yaml project fails due to missing mtar file", func(t *testing.T) {
utils := newMockUtilsBundle(true, false, false)
utils.files["mta.yaml"] = testMtaYml
utils.AddFile("mta.yaml", testMtaYml)
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
uploader := mockUploader{}
options := createOptions()
@ -293,8 +221,8 @@ func TestUploadMTAProjects(t *testing.T) {
})
t.Run("Test uploading mta.yaml project works", func(t *testing.T) {
utils := newMockUtilsBundle(true, false, false)
utils.files["mta.yaml"] = testMtaYml
utils.files["test.mtar"] = []byte("contentsOfMtar")
utils.AddFile("mta.yaml", testMtaYml)
utils.AddFile("test.mtar", []byte("contentsOfMtar"))
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
uploader := mockUploader{}
options := createOptions()
@ -316,8 +244,8 @@ func TestUploadMTAProjects(t *testing.T) {
})
t.Run("Test uploading mta.yml project works", func(t *testing.T) {
utils := newMockUtilsBundle(true, false, false)
utils.files["mta.yml"] = testMtaYml
utils.files["test.mtar"] = []byte("contentsOfMtar")
utils.AddFile("mta.yml", testMtaYml)
utils.AddFile("test.mtar", []byte("contentsOfMtar"))
utils.cpe[".pipeline/commonPipelineEnvironment/mtarFilePath"] = "test.mtar"
uploader := mockUploader{}
options := createOptions()
@ -340,6 +268,7 @@ func TestUploadMTAProjects(t *testing.T) {
}
func TestUploadArtifacts(t *testing.T) {
t.Parallel()
t.Run("Uploading MTA project fails without info", func(t *testing.T) {
utils := newMockUtilsBundle(false, true, false)
uploader := mockUploader{}
@ -422,7 +351,7 @@ func TestUploadArtifacts(t *testing.T) {
func TestUploadNpmProjects(t *testing.T) {
t.Run("Test uploading simple npm project", func(t *testing.T) {
utils := newMockUtilsBundle(false, false, true)
utils.files["package.json"] = testPackageJson
utils.AddFile("package.json", testPackageJson)
uploader := mockUploader{}
options := createOptions()
options.User = "admin"
@ -439,6 +368,7 @@ func TestUploadNpmProjects(t *testing.T) {
}
func TestUploadMavenProjects(t *testing.T) {
t.Parallel()
t.Run("Uploading Maven project fails due to missing pom.xml", func(t *testing.T) {
utils := newMockUtilsBundle(false, true, false)
uploader := mockUploader{}
@ -455,7 +385,7 @@ func TestUploadMavenProjects(t *testing.T) {
utils.setProperty("pom.xml", "project.artifactId", "my-app")
utils.setProperty("pom.xml", "project.packaging", "pom")
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
utils.files["pom.xml"] = testPomXml
utils.AddFile("pom.xml", testPomXml)
uploader := mockUploader{}
options := createOptions()
@ -477,8 +407,8 @@ func TestUploadMavenProjects(t *testing.T) {
utils.setProperty("pom.xml", "project.artifactId", "my-app")
utils.setProperty("pom.xml", "project.packaging", "jar")
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
utils.files["pom.xml"] = testPomXml
utils.files["target/dummy"] = []byte("contentsOfJar") // causes "target" folder to exist
utils.AddFile("pom.xml", testPomXml)
utils.AddDir("target")
uploader := mockUploader{}
options := createOptions()
@ -493,8 +423,8 @@ func TestUploadMavenProjects(t *testing.T) {
utils.setProperty("pom.xml", "project.artifactId", "my-app")
utils.setProperty("pom.xml", "project.packaging", "jar")
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
utils.files["pom.xml"] = testPomXml
utils.files["target/my-app-1.0.jar"] = []byte("contentsOfJar")
utils.AddFile("pom.xml", testPomXml)
utils.AddFile("target/my-app-1.0.jar", []byte("contentsOfJar"))
uploader := mockUploader{}
options := createOptions()
@ -520,8 +450,8 @@ func TestUploadMavenProjects(t *testing.T) {
utils.setProperty("pom.xml", "project.artifactId", "my-app")
utils.setProperty("pom.xml", "project.packaging", "<empty>")
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
utils.files["pom.xml"] = testPomXml
utils.files["target/my-app-1.0.jar"] = []byte("contentsOfJar")
utils.AddFile("pom.xml", testPomXml)
utils.AddFile("target/my-app-1.0.jar", []byte("contentsOfJar"))
uploader := mockUploader{}
options := createOptions()
@ -545,7 +475,7 @@ func TestUploadMavenProjects(t *testing.T) {
utils.setProperty("pom.xml", "project.artifactId", "my-app")
utils.setProperty("pom.xml", "project.packaging", "pom")
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
utils.files["pom.xml"] = testPomXml
utils.AddFile("pom.xml", testPomXml)
uploader := mockUploader{}
options := createOptions()
options.GroupID = "awesome.group"
@ -570,8 +500,8 @@ func TestUploadMavenProjects(t *testing.T) {
utils.setProperty("pom.xml", "project.groupId", "awesome.group")
utils.setProperty("pom.xml", "project.artifactId", "my-app")
utils.setProperty("pom.xml", "project.packaging", "jar")
utils.files["pom.xml"] = testPomXml
utils.files["target/my-app-1.0.jar"] = []byte("contentsOfJar")
utils.AddFile("pom.xml", testPomXml)
utils.AddFile("target/my-app-1.0.jar", []byte("contentsOfJar"))
uploader := mockUploader{}
options := createOptions()
@ -617,45 +547,49 @@ func TestUploadMavenProjects(t *testing.T) {
utils.setProperty("performance-tests/pom.xml", "project.groupId", "com.mycompany.app")
utils.setProperty("performance-tests/pom.xml", "project.artifactId", "my-app-app")
utils.setProperty("performance-tests/pom.xml", "project.packaging", "")
utils.files["pom.xml"] = testPomXml
utils.files["application/pom.xml"] = testPomXml
utils.files["application/target/final-artifact.war"] = []byte("contentsOfJar")
utils.files["application/target/final-artifact-classes.jar"] = []byte("contentsOfClassesJar")
utils.files["integration-tests/pom.xml"] = testPomXml
utils.files["integration-tests/target/final-artifact-integration-tests.jar"] = []byte("contentsOfJar")
utils.files["unit-tests/pom.xml"] = testPomXml
utils.files["unit-tests/target/final-artifact-unit-tests.jar"] = []byte("contentsOfJar")
utils.files["performance-tests/pom.xml"] = testPomXml
utils.AddFile("pom.xml", testPomXml)
utils.AddFile("application/pom.xml", testPomXml)
utils.AddFile("application/target/final-artifact.war", []byte("contentsOfJar"))
utils.AddFile("application/target/final-artifact-classes.jar", []byte("contentsOfClassesJar"))
utils.AddFile("integration-tests/pom.xml", testPomXml)
utils.AddFile("integration-tests/target/final-artifact-integration-tests.jar", []byte("contentsOfJar"))
utils.AddFile("unit-tests/pom.xml", testPomXml)
utils.AddFile("unit-tests/target/final-artifact-unit-tests.jar", []byte("contentsOfJar"))
utils.AddFile("performance-tests/pom.xml", testPomXml)
uploader := mockUploader{}
options := createOptions()
err := runNexusUpload(&utils, &uploader, &options)
assert.NoError(t, err, "expected upload of maven project with application module to succeed")
assert.Equal(t, "1.0", uploader.GetArtifactsVersion())
assert.Equal(t, "my-app-app", uploader.GetArtifactsID())
assert.Equal(t, "my-app", uploader.GetArtifactsID())
artifacts := uploader.uploadedArtifacts
if assert.Equal(t, 4, len(artifacts)) {
assert.Equal(t, "pom.xml", artifacts[0].File)
assert.Equal(t, "application/pom.xml", artifacts[0].File)
assert.Equal(t, "pom", artifacts[0].Type)
assert.Equal(t, "application/pom.xml", artifacts[1].File)
assert.Equal(t, "pom", artifacts[1].Type)
assert.Equal(t, "application/target/final-artifact.war", artifacts[1].File)
assert.Equal(t, "war", artifacts[1].Type)
assert.Equal(t, "application/target/final-artifact.war", artifacts[2].File)
assert.Equal(t, "war", artifacts[2].Type)
assert.Equal(t, "application/target/final-artifact-classes.jar", artifacts[2].File)
assert.Equal(t, "jar", artifacts[2].Type)
assert.Equal(t, "pom.xml", artifacts[3].File)
assert.Equal(t, "pom", artifacts[3].Type)
assert.Equal(t, "application/target/final-artifact-classes.jar", artifacts[3].File)
assert.Equal(t, "jar", artifacts[3].Type)
}
if assert.Equal(t, 2, len(utils.execRunner.Calls)) {
expectedParameters1 := []string{
"-Durl=http://localhost:8081/repository/maven-releases/",
"-DgroupId=com.mycompany.app",
"-Dversion=1.0",
"-DartifactId=my-app",
"-Dfile=pom.xml",
"-DartifactId=my-app-app",
"-Dfile=application/pom.xml",
"-Dpackaging=pom",
"-Dfiles=application/target/final-artifact.war,application/target/final-artifact-classes.jar",
"-Dclassifiers=,classes",
"-Dtypes=war,jar",
"-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn",
"--batch-mode",
deployGoal}
@ -666,12 +600,9 @@ func TestUploadMavenProjects(t *testing.T) {
"-Durl=http://localhost:8081/repository/maven-releases/",
"-DgroupId=com.mycompany.app",
"-Dversion=1.0",
"-DartifactId=my-app-app",
"-Dfile=application/pom.xml",
"-DartifactId=my-app",
"-Dfile=pom.xml",
"-Dpackaging=pom",
"-Dfiles=application/target/final-artifact.war,application/target/final-artifact-classes.jar",
"-Dclassifiers=,classes",
"-Dtypes=war,jar",
"-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn",
"--batch-mode",
deployGoal}
@ -686,7 +617,7 @@ func TestUploadMavenProjects(t *testing.T) {
utils.setProperty("pom.xml", "project.artifactId", "my-app")
utils.setProperty("pom.xml", "project.packaging", "pom")
utils.setProperty("pom.xml", "project.build.finalName", "my-app-1.0")
utils.files["pom.xml"] = testPomXml
utils.AddFile("pom.xml", testPomXml)
uploader := mockUploader{}
options := createOptions()
options.User = "admin"
@ -716,8 +647,8 @@ func TestUploadMavenProjects(t *testing.T) {
assert.Equal(t, 2, len(utils.execRunner.Env))
assert.Equal(t, expectedEnv, utils.execRunner.Env)
assert.Nil(t, utils.files[settingsPath])
assert.NotNil(t, utils.removedFiles[settingsPath])
assert.False(t, utils.HasFile(settingsPath))
assert.True(t, utils.HasRemovedFile(settingsPath))
})
}
@ -734,5 +665,5 @@ func TestSetupNexusCredentialsSettingsFile(t *testing.T) {
assert.Equal(t, expectedEnv, utils.execRunner.Env)
assert.True(t, settingsPath != "")
assert.NotNil(t, utils.files[settingsPath])
assert.True(t, utils.HasFile(settingsPath))
}

View File

@ -28,7 +28,7 @@ func (u *npmExecuteScriptsMockUtilsBundle) glob(pattern string) ([]string, error
}
}
// The order in m.files is not deterministic, this would result in flaky tests.
sort.Sort(byLen(matches))
sort.Strings(matches)
return matches, nil
}

View File

@ -3,44 +3,203 @@
package mock
import (
"errors"
"fmt"
"github.com/bmatcuk/doublestar"
"os"
"path/filepath"
"sort"
"strings"
)
// FilesMock ...
var dirContent []byte
// FilesMock implements the functions from piperutils.Files with an in-memory file system.
type FilesMock struct {
Files []string
files map[string]*[]byte
removedFiles map[string]*[]byte
currentDir string
}
// FileExists ...
func (f FilesMock) FileExists(filename string) (bool, error) {
for _, file := range f.Files {
if file == filename {
return true, nil
}
func (f *FilesMock) init() {
if f.files == nil {
f.files = map[string]*[]byte{}
}
if f.removedFiles == nil {
f.removedFiles = map[string]*[]byte{}
}
}
func (f *FilesMock) toAbsPath(path string) string {
if !strings.HasPrefix(path, string(os.PathSeparator)) {
path = string(os.PathSeparator) + filepath.Join(f.currentDir, path)
}
return path
}
// AddFile establishes the existence of a virtual file.
func (f *FilesMock) AddFile(path string, contents []byte) {
f.init()
f.files[f.toAbsPath(path)] = &contents
}
// AddDir establishes the existence of a virtual directory.
func (f *FilesMock) AddDir(path string) {
f.init()
f.files[f.toAbsPath(path)] = &dirContent
}
// HasFile returns true if the virtual file system contains an entry for the given path.
func (f *FilesMock) HasFile(path string) bool {
_, exists := f.files[f.toAbsPath(path)]
return exists
}
// HasRemovedFile returns true if the virtual file system at one point contained an entry for the given path,
// and it was removed via FileRemove().
func (f *FilesMock) HasRemovedFile(path string) bool {
_, exists := f.removedFiles[f.toAbsPath(path)]
return exists
}
// FileExists returns true if file content has been associated with the given path, false otherwise.
// Only relative paths are supported.
func (f *FilesMock) FileExists(path string) (bool, error) {
if f.files == nil {
return false, nil
}
content, exists := f.files[f.toAbsPath(path)]
if !exists {
return false, fmt.Errorf("'%s': %w", path, os.ErrNotExist)
}
return content != &dirContent, nil
}
// DirExists returns true, if the given path is a previously added directory, or a parent directory for any of the
// previously added files.
func (f *FilesMock) DirExists(path string) (bool, error) {
path = f.toAbsPath(path)
for entry, content := range f.files {
var dirComponents []string
if content == &dirContent {
dirComponents = strings.Split(entry, string(os.PathSeparator))
} else {
dirComponents = strings.Split(filepath.Dir(entry), string(os.PathSeparator))
}
if len(dirComponents) > 0 {
dir := ""
for i, component := range dirComponents {
if i == 0 {
dir = component
} else {
dir = dir + string(os.PathSeparator) + component
}
if dir == path {
return true, nil
}
}
}
}
return false, nil
}
// Copy ...
func (f FilesMock) Copy(src, dst string) (int64, error) {
return 0, errors.New("Not implemented")
// Copy checks if content has been associated with the given src path, and if so copies it under the given path dst.
func (f *FilesMock) Copy(src, dst string) (int64, error) {
f.init()
content, exists := f.files[f.toAbsPath(src)]
if !exists || content == &dirContent {
return 0, fmt.Errorf("cannot copy '%s': %w", src, os.ErrNotExist)
}
f.AddFile(dst, *content)
return int64(len(*content)), nil
}
// FileRead ...
func (f FilesMock) FileRead(path string) ([]byte, error) {
return nil, errors.New("Not implemented")
// FileRead returns the content previously associated with the given path via AddFile(), or an error if no
// content has been associated.
func (f *FilesMock) FileRead(path string) ([]byte, error) {
f.init()
content, exists := f.files[f.toAbsPath(path)]
if !exists {
return nil, fmt.Errorf("could not read '%s'", path)
}
// check if trying to open a directory for reading
if content == &dirContent {
return nil, fmt.Errorf("could not read '%s': %w", path, os.ErrInvalid)
}
return *content, nil
}
// FileWrite ...
func (f FilesMock) FileWrite(path string, content []byte, perm os.FileMode) error {
return errors.New("Not implemented")
// FileWrite just forwards to AddFile(), i.e. the content is associated with the given path.
func (f *FilesMock) FileWrite(path string, content []byte, _ os.FileMode) error {
// NOTE: FilesMock could be extended to have a set of paths for which FileWrite should fail.
// This is why AddFile() exists separately, to differentiate the notion of setting up the mocking
// versus implementing the methods from Files.
f.AddFile(path, content)
return nil
}
// MkdirAll ...
func (f FilesMock) MkdirAll(path string, perm os.FileMode) error {
return errors.New("Not implemented")
// FileRemove deletes the association of the given path with any content and records the removal of the file.
// If the path has not been registered before, it returns an error.
func (f *FilesMock) FileRemove(path string) error {
if f.files == nil {
return fmt.Errorf("the file '%s' does not exist: %w", path, os.ErrNotExist)
}
absPath := f.toAbsPath(path)
content, exists := f.files[absPath]
if !exists {
return fmt.Errorf("the file '%s' does not exist: %w", path, os.ErrNotExist)
}
delete(f.files, absPath)
f.removedFiles[absPath] = content
return nil
}
// MkdirAll creates a directory in the in-memory file system, so that this path is established to exist.
func (f *FilesMock) MkdirAll(path string, _ os.FileMode) error {
// NOTE: FilesMock could be extended to have a set of paths for which MkdirAll should fail.
// This is why AddDir() exists separately, to differentiate the notion of setting up the mocking
// versus implementing the methods from Files.
f.AddDir(path)
return nil
}
// Glob returns an array of path strings which match the given glob-pattern. Double star matching is supported.
func (f *FilesMock) Glob(pattern string) ([]string, error) {
var matches []string
if f.files == nil {
return matches, nil
}
for path := range f.files {
path = strings.TrimLeft(path, string(os.PathSeparator))
matched, _ := doublestar.Match(pattern, path)
if matched {
matches = append(matches, path)
}
}
// The order in f.files is not deterministic, this would result in flaky tests.
sort.Strings(matches)
return matches, nil
}
// Getwd returns the rooted current virtual working directory
func (f *FilesMock) Getwd() (string, error) {
return f.toAbsPath(""), nil
}
// Chdir changes virtually in to the given directory.
// The directory needs to exist according to the files and directories via AddFile() and AddDirectory().
// The implementation does not support relative path components such as "..".
func (f *FilesMock) Chdir(path string) error {
if path == "." || path == "."+string(os.PathSeparator) {
return nil
}
path = f.toAbsPath(path)
exists, _ := f.DirExists(path)
if !exists {
return fmt.Errorf("failed to change current directory into '%s': %w", path, os.ErrNotExist)
}
f.currentDir = strings.TrimLeft(path, string(os.PathSeparator))
return nil
}

244
pkg/mock/fileUtils_test.go Normal file
View File

@ -0,0 +1,244 @@
package mock
import (
"github.com/stretchr/testify/assert"
"os"
"path/filepath"
"testing"
)
func TestFilesMockFileExists(t *testing.T) {
t.Parallel()
t.Run("no init", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("some", "path")
exists, err := files.FileExists(path)
assert.NoError(t, err)
assert.False(t, exists)
})
t.Run("file exists after AddFile()", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("some", "path")
files.AddFile(path, []byte("dummy content"))
exists, err := files.FileExists(path)
assert.NoError(t, err)
assert.True(t, exists)
})
t.Run("path is a directory after AddDir()", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("some", "path")
files.AddDir(path)
exists, err := files.FileExists(path)
assert.NoError(t, err)
assert.False(t, exists)
})
t.Run("file exists after changing current dir", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("some", "path")
files.AddFile(path, []byte("dummy content"))
err := files.Chdir("some")
assert.NoError(t, err)
exists, err := files.FileExists("path")
assert.NoError(t, err)
assert.True(t, exists)
})
t.Run("file does not exist after changing current dir", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("some", "path")
files.AddFile(path, []byte("dummy content"))
err := files.Chdir("some")
assert.NoError(t, err)
exists, err := files.FileExists(path)
assert.EqualError(t, err, "'"+path+"': file does not exist")
assert.False(t, exists)
})
}
func TestFilesMockDirExists(t *testing.T) {
t.Parallel()
t.Run("no init", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("some", "path")
exists, err := files.DirExists(path)
assert.NoError(t, err)
assert.False(t, exists)
})
t.Run("dir exists after AddDir()", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("some", "path")
files.AddDir(path)
exists, err := files.DirExists(path)
assert.NoError(t, err)
assert.True(t, exists)
})
t.Run("absolute dir exists after AddDir()", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("some", "path")
files.AddDir(path)
err := files.Chdir("some")
assert.NoError(t, err)
exists, err := files.DirExists(string(os.PathSeparator) + path)
assert.NoError(t, err)
assert.True(t, exists)
})
t.Run("parent dirs exists after AddFile()", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("path", "to", "some", "file")
files.AddFile(path, []byte("dummy content"))
testDirs := []string{
"path",
filepath.Join("path", "to"),
filepath.Join("path", "to", "some"),
}
for _, dir := range testDirs {
exists, err := files.DirExists(dir)
assert.NoError(t, err)
assert.True(t, exists, "Should exist: '%s'", dir)
}
})
t.Run("invalid sub-folders do not exist after AddFile()", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("path", "to", "some", "file")
files.AddFile(path, []byte("dummy content"))
testDirs := []string{
"to",
filepath.Join("to", "some"),
"some",
filepath.Join("path", "to", "so"),
}
for _, dir := range testDirs {
exists, err := files.DirExists(dir)
assert.NoError(t, err)
assert.False(t, exists, "Should not exist: '%s'", dir)
}
})
}
func TestFilesMockCopy(t *testing.T) {
t.Parallel()
t.Run("copy a previously added file successfully", func(t *testing.T) {
files := FilesMock{}
src := filepath.Join("some", "file")
content := []byte("dummy content")
files.AddFile(src, content)
dst := filepath.Join("another", "file")
length, err := files.Copy(src, dst)
assert.NoError(t, err)
assert.Equal(t, length, int64(len(content)))
})
t.Run("fail to copy non-existing file", func(t *testing.T) {
files := FilesMock{}
src := filepath.Join("some", "file")
dst := filepath.Join("another", "file")
length, err := files.Copy(src, dst)
assert.EqualError(t, err, "cannot copy '"+src+"': file does not exist")
assert.Equal(t, length, int64(0))
})
t.Run("fail to copy folder", func(t *testing.T) {
files := FilesMock{}
src := filepath.Join("some", "file")
files.AddDir(src)
dst := filepath.Join("another", "file")
length, err := files.Copy(src, dst)
assert.EqualError(t, err, "cannot copy '"+src+"': file does not exist")
assert.Equal(t, length, int64(0))
})
}
func TestFilesMockFileRemove(t *testing.T) {
t.Parallel()
t.Run("fail to remove non-existing file", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("some", "file")
err := files.FileRemove(path)
assert.EqualError(t, err, "the file '"+path+"' does not exist: file does not exist")
assert.False(t, files.HasRemovedFile(path))
})
t.Run("track removing a file", func(t *testing.T) {
files := FilesMock{}
path := filepath.Join("some", "file")
files.AddFile(path, []byte("dummy content"))
assert.True(t, files.HasFile(path))
err := files.FileRemove(path)
assert.NoError(t, err)
assert.False(t, files.HasFile(path))
assert.True(t, files.HasRemovedFile(path))
})
}
func TestFilesMockGetwd(t *testing.T) {
t.Parallel()
t.Run("test root", func(t *testing.T) {
files := FilesMock{}
dir, err := files.Getwd()
assert.NoError(t, err)
assert.Equal(t, string(os.PathSeparator), dir)
})
t.Run("test sub folder", func(t *testing.T) {
files := FilesMock{}
dirChain := []string{"some", "deep", "folder"}
files.AddDir(filepath.Join(dirChain...))
for _, element := range dirChain {
err := files.Chdir(element)
assert.NoError(t, err)
}
dir, err := files.Getwd()
assert.NoError(t, err)
assert.Equal(t, string(os.PathSeparator)+filepath.Join(dirChain...), dir)
})
}
func TestFilesMockGlob(t *testing.T) {
t.Parallel()
files := FilesMock{}
content := []byte("dummy content")
files.AddFile(filepath.Join("dir", "foo.xml"), content)
files.AddFile(filepath.Join("dir", "another", "foo.xml"), content)
files.AddFile(filepath.Join("dir", "baz"), content)
files.AddFile(filepath.Join("baz.xml"), content)
t.Run("one match in root-dir", func(t *testing.T) {
matches, err := files.Glob("*.xml")
assert.NoError(t, err)
if assert.Len(t, matches, 1) {
assert.Equal(t, matches[0], "baz.xml")
}
})
t.Run("three matches in all levels", func(t *testing.T) {
matches, err := files.Glob("**/*.xml")
assert.NoError(t, err)
if assert.Len(t, matches, 3) {
assert.Equal(t, matches[0], "baz.xml")
assert.Equal(t, matches[1], filepath.Join("dir", "another", "foo.xml"))
assert.Equal(t, matches[2], filepath.Join("dir", "foo.xml"))
}
})
t.Run("match only in sub-dir", func(t *testing.T) {
matches, err := files.Glob("*/*.xml")
assert.NoError(t, err)
if assert.Len(t, matches, 1) {
assert.Equal(t, matches[0], filepath.Join("dir", "foo.xml"))
}
})
t.Run("match for two levels", func(t *testing.T) {
matches, err := files.Glob("*/*/*.xml")
assert.NoError(t, err)
if assert.Len(t, matches, 1) {
assert.Equal(t, matches[0], filepath.Join("dir", "another", "foo.xml"))
}
})
t.Run("match prefix", func(t *testing.T) {
matches, err := files.Glob("**/baz*")
assert.NoError(t, err)
if assert.Len(t, matches, 2) {
assert.Equal(t, matches[0], filepath.Join("baz.xml"))
assert.Equal(t, matches[1], filepath.Join("dir", "baz"))
}
})
t.Run("no matches", func(t *testing.T) {
matches, err := files.Glob("**/*bar*")
assert.NoError(t, err)
assert.Len(t, matches, 0)
})
}

View File

@ -4,6 +4,7 @@ import (
"archive/zip"
"errors"
"fmt"
"github.com/bmatcuk/doublestar"
"io"
"io/ioutil"
"os"
@ -43,6 +44,20 @@ func FileExists(filename string) (bool, error) {
return Files{}.FileExists(filename)
}
// DirExists returns true if the file system entry for the given path exists and is a directory.
func (f Files) DirExists(path string) (bool, error) {
info, err := os.Stat(path)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, err
}
return info.IsDir(), nil
}
// Copy ...
func (f Files) Copy(src, dst string) (int64, error) {
@ -156,17 +171,37 @@ func Copy(src, dst string) (int64, error) {
return Files{}.Copy(src, dst)
}
//FileRead ...
// FileRead is a wrapper for ioutil.ReadFile().
func (f Files) FileRead(path string) ([]byte, error) {
return ioutil.ReadFile(path)
}
// FileWrite ...
// FileWrite is a wrapper for ioutil.WriteFile().
func (f Files) FileWrite(path string, content []byte, perm os.FileMode) error {
return ioutil.WriteFile(path, content, perm)
}
// MkdirAll ...
// FileRemove is a wrapper for os.FileRemove().
func (f Files) FileRemove(path string) error {
return os.Remove(path)
}
// MkdirAll is a wrapper for os.MkdirAll().
func (f Files) MkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}
// Glob is a wrapper for doublestar.Glob().
func (f Files) Glob(pattern string) (matches []string, err error) {
return doublestar.Glob(pattern)
}
// Getwd is a wrapper for os.Getwd().
func (f Files) Getwd() (string, error) {
return os.Getwd()
}
// Chdir is a wrapper for os.Chdir().
func (f Files) Chdir(path string) error {
return os.Chdir(path)
}

View File

@ -9,35 +9,70 @@ import (
)
func TestFileExists(t *testing.T) {
dir, err := ioutil.TempDir("", "dir")
if err != nil {
t.Fatal("Failed to create temporary workspace directory")
}
// clean up tmp dir
defer os.RemoveAll(dir)
result, err := FileExists(dir)
assert.NoError(t, err, "Didn't expert error but got one")
assert.Equal(t, false, result, "Expected false but got true")
file, err := ioutil.TempFile(dir, "testFile")
assert.NoError(t, err, "Didn't expert error but got one")
result, err = FileExists(file.Name())
assert.NoError(t, err, "Didn't expert error but got one")
assert.Equal(t, true, result, "Expected true but got false")
t.Parallel()
runInTempDir(t, "testing dir returns false", "dir", func(t *testing.T) {
err := os.Mkdir("test", 0777)
if err != nil {
t.Fatal("failed to create test dir in temporary dir")
}
result, err := FileExists("test")
assert.NoError(t, err)
assert.False(t, result)
})
runInTempDir(t, "testing file returns true", "dir", func(t *testing.T) {
file, err := ioutil.TempFile("", "testFile")
assert.NoError(t, err)
result, err := FileExists(file.Name())
assert.NoError(t, err)
assert.True(t, result)
})
}
func TestCopy(t *testing.T) {
dir, err := ioutil.TempDir("", "dir2")
file := filepath.Join(dir, "testFile")
err = ioutil.WriteFile(file, []byte{byte(1), byte(2), byte(3)}, 0700)
if err != nil {
t.Fatal("Failed to create temporary workspace directory")
}
// clean up tmp dir
defer os.RemoveAll(dir)
t.Parallel()
runInTempDir(t, "copying file succeeds", "dir2", func(t *testing.T) {
file := "testFile"
err := ioutil.WriteFile(file, []byte{byte(1), byte(2), byte(3)}, 0700)
if err != nil {
t.Fatal("Failed to create temporary workspace directory")
}
result, err := Copy(file, filepath.Join(dir, "testFile2"))
assert.NoError(t, err, "Didn't expert error but got one")
assert.Equal(t, int64(3), result, "Expected true but got false")
result, err := Copy(file, "testFile2")
assert.NoError(t, err, "Didn't expert error but got one")
assert.Equal(t, int64(3), result, "Expected true but got false")
})
runInTempDir(t, "copying directory fails", "dir3", func(t *testing.T) {
src := filepath.Join("some", "file")
dst := filepath.Join("another", "file")
err := os.MkdirAll(src, 0777)
if err != nil {
t.Fatal("Failed to create test directory")
}
files := Files{}
exists, err := files.DirExists(src)
assert.NoError(t, err)
assert.True(t, exists)
length, err := files.Copy(src, dst)
assert.EqualError(t, err, "Source file '"+src+"' does not exist")
assert.Equal(t, length, int64(0))
})
}
func runInTempDir(t *testing.T, nameOfRun, tempDirPattern string, run func(t *testing.T)) {
dir, err := ioutil.TempDir("", tempDirPattern)
if err != nil {
t.Fatal("Failed to create temporary directory")
}
oldCWD, _ := os.Getwd()
_ = os.Chdir(dir)
// clean up tmp dir
defer func() {
_ = os.Chdir(oldCWD)
_ = os.RemoveAll(dir)
}()
t.Run(nameOfRun, run)
}