diff --git a/.gitignore b/.gitignore index ca1142a..a2ef6c6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,6 @@ openapi2beans/build **/.vscode **/generated/* **/target -*.jar \ No newline at end of file +*.jar +temp/ +build/ \ No newline at end of file diff --git a/Makefile b/Makefile index f9c266d..c46a3f7 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,48 @@ -all: bin/galasabld-linux-amd64 \ +all: clean test \ + bin/galasabld-linux-amd64 \ bin/galasabld-windows-amd64 \ bin/galasabld-darwin-amd64 \ bin/galasabld-linux-s390x \ bin/galasabld-darwin-arm64 -bin/galasabld-linux-amd64 : ./Makefile ./cmd/galasabld/main.go ./pkg/cmd/*.go ./pkg/galasayaml/*.go ./pkg/githubjson/*.go +src : ./Makefile \ + ./cmd/galasabld/main.go \ + ./pkg/cmd/*.go \ + ./pkg/galasayaml/*.go \ + ./pkg/githubjson/*.go \ + ./pkg/utils/*.go \ + ./pkg/versioning/*.go + +test: src + +bin/galasabld-linux-amd64 : src CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/galasabld-linux-amd64 ./cmd/galasabld -bin/galasabld-windows-amd64 : ./Makefile ./cmd/galasabld/main.go ./pkg/cmd/*.go ./pkg/galasayaml/*.go ./pkg/githubjson/*.go +bin/galasabld-windows-amd64 : src CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o bin/galasabld-windows-amd64 ./cmd/galasabld -bin/galasabld-darwin-amd64 : ./Makefile ./cmd/galasabld/main.go ./pkg/cmd/*.go ./pkg/galasayaml/*.go ./pkg/githubjson/*.go +bin/galasabld-darwin-amd64 : src CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/galasabld-darwin-amd64 ./cmd/galasabld -bin/galasabld-darwin-arm64 : ./Makefile ./cmd/galasabld/main.go ./pkg/cmd/*.go ./pkg/galasayaml/*.go ./pkg/githubjson/*.go +bin/galasabld-darwin-arm64 : src CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o bin/galasabld-darwin-arm64 ./cmd/galasabld -bin/galasabld-linux-s390x : ./Makefile ./cmd/galasabld/main.go ./pkg/cmd/*.go ./pkg/galasayaml/*.go ./pkg/githubjson/*.go +bin/galasabld-linux-s390x : src CGO_ENABLED=0 GOOS=linux GOARCH=s390x go build -o bin/galasabld-linux-s390x ./cmd/galasabld +test: src build/coverage.txt build/coverage.html build/coverage.out + +build/coverage.out : src + mkdir -p build + go test -v -cover -coverprofile=build/coverage.out -coverpkg ./pkg/cmd,./pkg/galasayaml,./pkg/githubjson,./pkg/utils,./pkg/versioning ./pkg/... + +build/coverage.html : build/coverage.out + go tool cover -html=build/coverage.out -o build/coverage.html + +build/coverage.txt : build/coverage.out + go tool cover -func=build/coverage.out > build/coverage.txt + cat build/coverage.txt clean: rm -rf bin + rm -rf build/coverage.* diff --git a/README.md b/README.md index ab04a92..b54b950 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,37 @@ This repository is built into tools which themselves are used during a Galasa bu ## To build locally Use the `./build-locally.sh --help` to get a description of the required parameters and environment variables. + +## The `galasabld` utility + + +### To list the versions of all gradle modules + +``` +$galasabld versioning list --sourcefolderpath {my-source-folder} +a.b.c. 0.21.0 +a.b.d. 0.25.0-SNAPSHOT +``` + +To find a module version, the code must: +- have a build.gradle file +- have a settings.gradle file +- the build.gradle file must have a line like `version = "0.1.2"` or similar. +- the settings.gradle file must have a line like `rootProject.name = "dev.galasa.examples/module2"`. + +### To set a version suffix on all gradle modules +``` +$galasabld versioning suffix set --sourcefolderpath {my-source-folder} --suffix "-alpha" +``` +This will recursively look for module versions, stripping off any existing suffix, and adding the `-alpha` suffix to everything. + +Note: The value of the `--suffix` parameter must start with `-` or `_` + +So for example, `0.0.1` will be changed to `0.0.1-alpha` if `-alpha` is the suffix value. + +### To remove any suffix on all gradle modules +``` +$galasabld versioning suffix remove --sourcefolderpath {my-source-folder} +``` +This will recursively look for module versions, stripping off any existing suffix. +So for example, `0.0.1-SNAPSHOT` will be changed to `0.0.1` \ No newline at end of file diff --git a/build-locally.sh b/build-locally.sh index ac2ee80..d7d744b 100755 --- a/build-locally.sh +++ b/build-locally.sh @@ -20,7 +20,6 @@ export ORIGINAL_DIR=$(pwd) cd "${BASEDIR}/.." WORKSPACE_DIR=$(pwd) - #----------------------------------------------------------------------------------------- # # Set Colors @@ -40,26 +39,16 @@ blue=$(tput setaf 25) # Headers and Logging # #----------------------------------------------------------------------------------------- -underline() { printf "${underline}${bold}%s${reset}\n" "$@" -} -h1() { printf "\n${underline}${bold}${blue}%s${reset}\n" "$@" -} -h2() { printf "\n${underline}${bold}${white}%s${reset}\n" "$@" -} -debug() { printf "${white}[.] %s${reset}\n" "$@" -} -info() { printf "${white}[➜] %s${reset}\n" "$@" -} -success() { printf "${white}[${green}✔${white}] ${green}%s${reset}\n" "$@" -} -error() { printf "${white}[${red}✖${white}] ${red}%s${reset}\n" "$@" -} -warn() { printf "${white}[${tan}➜${white}] ${tan}%s${reset}\n" "$@" -} -bold() { printf "${bold}%s${reset}\n" "$@" -} -note() { printf "\n${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n" "$@" -} +underline() { printf "${underline}${bold}%s${reset}\n" "$@" ; } +h1() { printf "\n${underline}${bold}${blue}%s${reset}\n" "$@" ; } +h2() { printf "\n${underline}${bold}${white}%s${reset}\n" "$@" ; } +debug() { printf "${white}[.] %s${reset}\n" "$@" ; } +info() { printf "${white}[➜] %s${reset}\n" "$@" ; } +success() { printf "${white}[${green}✔${white}] ${green}%s${reset}\n" "$@" ; } +error() { printf "${white}[${red}✖${white}] ${red}%s${reset}\n" "$@" ; } +warn() { printf "${white}[${tan}➜${white}] ${tan}%s${reset}\n" "$@" ; } +bold() { printf "${bold}%s${reset}\n" "$@" ; } +note() { printf "\n${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n" "$@" ; } #----------------------------------------------------------------------------------------- # Functions @@ -69,13 +58,6 @@ function usage { cat << EOF Options are: -h | --help : Display this help text - -Environment Variables: - -LOGS_DIR : - Controls where logs are placed. - Optional. Defaults to creating a new temporary folder - EOF } @@ -136,40 +118,47 @@ done source_dir="." -project=$(basename ${BASEDIR}) -h1 "Building ${project}" +function clean_temp_folder() { + rm -fr $BASEDIR/temp + mkdir -p $BASEDIR/temp + LOGS_DIR=$BASEDIR/temp +} + +function build_tools() { -# Create a temporary dir. -# Note: This bash 'spell' works in OSX and Linux. -if [[ -z ${LOGS_DIR} ]]; then - export LOGS_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t "galasa-logs") - info "Logs are stored in the ${LOGS_DIR} folder." - info "Over-ride this setting using the LOGS_DIR environment variable." -else - mkdir -p ${LOGS_DIR} 2>&1 > /dev/null # Don't show output. We don't care if it already existed. - info "Logs are stored in the ${LOGS_DIR} folder." - info "Over-ridden by caller using the LOGS_DIR variable." -fi + project=$(basename ${BASEDIR}) + h1 "Building ${project}" -info "Using source code at ${source_dir}" -cd ${BASEDIR}/${source_dir} + info "Using source code at ${source_dir}" + cd ${BASEDIR}/${source_dir} -log_file=${LOGS_DIR}/${project}.txt -info "Log will be placed at ${log_file}" -date > ${log_file} + log_file=${LOGS_DIR}/${project}.txt + info "Log will be placed at ${log_file}" + date > ${log_file} + cmd="make all" + info "Command is '$cmd'" -cmd="make all" -info "Command is '$cmd'" + cd ${BASEDIR} + $cmd 2>&1 >> ${log_file} -cd ${BASEDIR} -$cmd 2>&1 >> ${log_file} + rc=$? + check_exit_code $rc "Failed to build the ${project}" + success "${project} built ok - log is at ${log_file}" +} + + + +function build_openapi2beans() { + h2 "Building openapi2beans." + ./openapi2beans/build-locally.sh +} -rc=$? -check_exit_code $rc "Failed to build the ${project}" -success "${project} built ok - log is at ${log_file}" -h2 "Building openapi2beans." -./openapi2beans/build-locally.sh +clean_temp_folder +build_tools +build_openapi2beans +$BASEDIR/test-locally.sh +rc=$? ; check_exit_code $rc "Failed to test galasabld" check_secrets \ No newline at end of file diff --git a/pkg/cmd/githubbranchcopy.go b/pkg/cmd/githubbranchcopy.go index 5e4c2fa..5701d58 100644 --- a/pkg/cmd/githubbranchcopy.go +++ b/pkg/cmd/githubbranchcopy.go @@ -58,7 +58,7 @@ func githubBranchCopyExecute(cmd *cobra.Command, args []string) { // First get the sha of the from branch - var url = "" + var url string if branchCopyFromBranch != "" { url = fmt.Sprintf("https://api.github.com/repos/galasa-dev/%v/git/ref/heads/%v", githubRepository, branchCopyFromBranch) } else { @@ -67,7 +67,7 @@ func githubBranchCopyExecute(cmd *cobra.Command, args []string) { req, err := http.NewRequest("GET", url, nil) if err != nil { - panic(nil) + panic(err) } req.Header.Set("Authorization", basicAuth) @@ -99,7 +99,7 @@ func githubBranchCopyExecute(cmd *cobra.Command, args []string) { req, err := http.NewRequest("GET", url, nil) if err != nil { - panic(nil) + panic(err) } req.Header.Set("Authorization", basicAuth) @@ -153,7 +153,7 @@ func githubBranchCopyExecute(cmd *cobra.Command, args []string) { } req, err = http.NewRequest(httpType, url, newReferenceBuffer) if err != nil { - panic(nil) + panic(err) } req.Header.Set("Authorization", basicAuth) diff --git a/pkg/cmd/githubbranchdelete.go b/pkg/cmd/githubbranchdelete.go index 7d747ca..798f95e 100644 --- a/pkg/cmd/githubbranchdelete.go +++ b/pkg/cmd/githubbranchdelete.go @@ -48,7 +48,7 @@ func githubBranchDeleteExecute(cmd *cobra.Command, args []string) { url := fmt.Sprintf("https://api.github.com/repos/galasa-dev/%v/git/ref/heads/%v", githubRepository, branchDeleteBranch) req, err := http.NewRequest("GET", url, nil) if err != nil { - panic(nil) + panic(err) } req.Header.Set("Authorization", basicAuth) @@ -73,7 +73,7 @@ func githubBranchDeleteExecute(cmd *cobra.Command, args []string) { req, err = http.NewRequest("DELETE", url, nil) if err != nil { - panic(nil) + panic(err) } req.Header.Set("Authorization", basicAuth) diff --git a/pkg/cmd/githubbranchtag.go b/pkg/cmd/githubbranchtag.go index be843c5..fbf9129 100644 --- a/pkg/cmd/githubbranchtag.go +++ b/pkg/cmd/githubbranchtag.go @@ -53,7 +53,7 @@ func githubBranchTagExecute(cmd *cobra.Command, args []string) { req, err := http.NewRequest("GET", url, nil) if err != nil { - panic(nil) + panic(err) } req.Header.Set("Authorization", basicAuth) @@ -97,7 +97,7 @@ func githubBranchTagExecute(cmd *cobra.Command, args []string) { req, err = http.NewRequest(httpType, url, newReferenceBuffer) if err != nil { - panic(nil) + panic(err) } req.Header.Set("Authorization", basicAuth) diff --git a/pkg/cmd/mavendeploy.go b/pkg/cmd/mavendeploy.go index 545bc21..3d4e832 100644 --- a/pkg/cmd/mavendeploy.go +++ b/pkg/cmd/mavendeploy.go @@ -78,7 +78,7 @@ func mavenDeploy( mavenDeployVersion string, basicAuth string) error { - var err error = nil + var err error var artifactDirectories []fs.DirEntry var mavenMetadataExists bool var versionDirectoryExists bool diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 7110636..429155f 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -17,7 +17,7 @@ var rootCmd = &cobra.Command{ Use: "galasabld", Short: "Build utilities for Galasa", Long: "", - Version: "0.0.7", + Version: "0.36.0", } func Execute() { diff --git a/pkg/cmd/secvulnOssindex.go b/pkg/cmd/secvulnOssindex.go index 73e97d2..aad14f2 100644 --- a/pkg/cmd/secvulnOssindex.go +++ b/pkg/cmd/secvulnOssindex.go @@ -194,7 +194,7 @@ func getDependencyChainAsString(dependencyChain []string) string { return dependencyChainString } -func processDependencyChain(submatches [][]string, cve, galasaArtifactString, vulnerability string) []string { +func processDependencyChain(submatches [][]string, cve, galasaArtifactString string, vulnerability string) []string { // Start forming the dependency chain var dependencyChain []string @@ -212,8 +212,9 @@ func processDependencyChain(submatches [][]string, cve, galasaArtifactString, vu for targetString != galasaArtifactString { if count == maxLoops { - fmt.Printf("Too many attempts to parse dependency chain from %s to %s\n", galasaArtifactString, vulnerability) - panic(nil) + msg := fmt.Sprintf("Too many attempts to parse dependency chain from %s to %s\n", galasaArtifactString, vulnerability) + fmt.Printf(msg) + panic(msg) } for _, submatch := range submatches { @@ -302,11 +303,13 @@ func createReport() *SecVulnYamlReport { if len(depChainMap[getGroupAndArtifact(innerProject)][getGroupArtifactVersion(vulnerableArtifact)]) == 1 { depChain = depChainMap[getGroupAndArtifact(innerProject)][getGroupArtifactVersion(vulnerableArtifact)][0] } else if len(depChainMap[getGroupAndArtifact(innerProject)][getGroupArtifactVersion(vulnerableArtifact)]) > 1 { - fmt.Printf("Multiple dependency chains found from %s to %s\n", innerProject, vulnerableArtifact) - panic(nil) + msg := fmt.Sprintf("Multiple dependency chains found from %s to %s\n", innerProject, vulnerableArtifact) + fmt.Printf(msg) + panic(msg) } else if len(depChainMap[getGroupAndArtifact(innerProject)][getGroupArtifactVersion(vulnerableArtifact)]) == 0 { - fmt.Printf("Unable to find dependency chain from %s to %s\n", innerProject, vulnerableArtifact) - panic(nil) + msg := fmt.Sprintf("Unable to find dependency chain from %s to %s\n", innerProject, vulnerableArtifact) + fmt.Printf(msg) + panic(msg) } transientProj := &TransientProject{ @@ -321,11 +324,13 @@ func createReport() *SecVulnYamlReport { if len(depChainMap[getGroupAndArtifact(directProject)][getGroupArtifactVersion(vulnerableArtifact)]) == 1 { directDepChain = depChainMap[getGroupAndArtifact(directProject)][getGroupArtifactVersion(vulnerableArtifact)][0] } else if len(depChainMap[getGroupAndArtifact(directProject)][getGroupArtifactVersion(vulnerableArtifact)]) > 1 { - fmt.Printf("Multiple dependency chains found from %s to %s\n", directProject, vulnerableArtifact) - panic(nil) + msg := fmt.Sprintf("Multiple dependency chains found from %s to %s\n", directProject, vulnerableArtifact) + fmt.Printf(msg) + panic(msg) } else if len(depChainMap[getGroupAndArtifact(directProject)][getGroupArtifactVersion(vulnerableArtifact)]) == 0 { - fmt.Printf("Unable to find dependency chain from %s to %s\n", directProject, vulnerableArtifact) - panic(nil) + msg := fmt.Sprintf("Unable to find dependency chain from %s to %s\n", directProject, vulnerableArtifact) + fmt.Printf(msg) + panic(msg) } directProject := &DirectProject{ diff --git a/pkg/cmd/versioning.go b/pkg/cmd/versioning.go new file mode 100644 index 0000000..f4a9d33 --- /dev/null +++ b/pkg/cmd/versioning.go @@ -0,0 +1,31 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package cmd + +import ( + "github.com/spf13/cobra" +) + +var ( + sourceCodeFolderPath string + + versioningCmd = &cobra.Command{ + Use: "versioning", + Short: "Setting/Clearing the build version suffix of source code.", + Long: "Commands to manipulate the versions of source code modules.", + } +) + +const SOURCE_FOLDER_PATH = "sourcefolderpath" + +func init() { + // The --sourcefolderepath flag. Refers to the top-level source folder to process. + versioningCmd.PersistentFlags().StringVarP(&sourceCodeFolderPath, SOURCE_FOLDER_PATH, "p", "", "Path to the source tree to manipulate.") + versioningCmd.MarkPersistentFlagRequired(SOURCE_FOLDER_PATH) + + rootCmd.AddCommand(versioningCmd) +} diff --git a/pkg/cmd/versioningList.go b/pkg/cmd/versioningList.go new file mode 100644 index 0000000..0684fe5 --- /dev/null +++ b/pkg/cmd/versioningList.go @@ -0,0 +1,37 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package cmd + +import ( + "galasa.dev/buildUtilities/pkg/utils" + "galasa.dev/buildUtilities/pkg/versioning" + "github.com/spf13/cobra" +) + +var ( + versioningListCmd = &cobra.Command{ + Use: "list", + Short: "Lists the versions of all the modules in the source folder recursively.", + Long: "Lists the versions of all the modules in the source folder recursively.", + Run: versioningListExecute, + } +) + +func init() { + versioningCmd.AddCommand(versioningListCmd) +} + +func versioningListExecute(cmd *cobra.Command, args []string) { + + fs := utils.NewOSFileSystem() + err := versioning.ListExecute(fs, sourceCodeFolderPath) + + if err != nil { + panic(err) + } + +} diff --git a/pkg/cmd/versioningSuffix.go b/pkg/cmd/versioningSuffix.go new file mode 100644 index 0000000..636aad1 --- /dev/null +++ b/pkg/cmd/versioningSuffix.go @@ -0,0 +1,23 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package cmd + +import ( + "github.com/spf13/cobra" +) + +var ( + versioningSuffixCmd = &cobra.Command{ + Use: "suffix", + Short: "Manipulates the suffix of source code recursively.", + Long: "Manipulates the suffix of source code recursively.", + } +) + +func init() { + versioningCmd.AddCommand(versioningSuffixCmd) +} diff --git a/pkg/cmd/versioningSuffixRemove.go b/pkg/cmd/versioningSuffixRemove.go new file mode 100644 index 0000000..3d15f0b --- /dev/null +++ b/pkg/cmd/versioningSuffixRemove.go @@ -0,0 +1,37 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package cmd + +import ( + "galasa.dev/buildUtilities/pkg/utils" + "galasa.dev/buildUtilities/pkg/versioning" + "github.com/spf13/cobra" +) + +var ( + versioningSuffixRemoveCmd = &cobra.Command{ + Use: "remove", + Short: "Removes the build version suffix of source code recursively.", + Long: "Removes the build version suffix of source code recursively.", + Run: versioningSuffixRemoveExecute, + } +) + +func init() { + versioningSuffixCmd.AddCommand(versioningSuffixRemoveCmd) +} + +func versioningSuffixRemoveExecute(cmd *cobra.Command, args []string) { + + fs := utils.NewOSFileSystem() + err := versioning.SuffixRemoveExecute(fs, sourceCodeFolderPath) + + if err != nil { + panic(err) + } + +} diff --git a/pkg/cmd/versioningSuffixSet.go b/pkg/cmd/versioningSuffixSet.go new file mode 100644 index 0000000..6ecbf30 --- /dev/null +++ b/pkg/cmd/versioningSuffixSet.go @@ -0,0 +1,45 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package cmd + +import ( + "galasa.dev/buildUtilities/pkg/utils" + "galasa.dev/buildUtilities/pkg/versioning" + "github.com/spf13/cobra" +) + +var versionSuffix string + +var ( + versioningSuffixSetCmd = &cobra.Command{ + Use: "set", + Short: "Sets the source module versions suffix recursively.", + Long: "Sets the source module versions suffix recursively.", + Run: versioningSuffixSetExecute, + } +) + +func init() { + // --suffix flag. Mandatory. Use 'suffix remove' if you want to remove the suffix. + versioningSuffixSetCmd.PersistentFlags().StringVarP(&versionSuffix, "suffix", "s", "-SNAPSHOT", + "The version suffix to set all modules to use. For example -SNAPSHOT"+ + " Suffixes must start with '_' or '-' ") + versioningSuffixSetCmd.MarkPersistentFlagRequired("suffix") + + versioningSuffixCmd.AddCommand(versioningSuffixSetCmd) +} + +func versioningSuffixSetExecute(cmd *cobra.Command, args []string) { + + fs := utils.NewOSFileSystem() + err := versioning.SuffixSetExecute(fs, sourceCodeFolderPath, versionSuffix) + + if err != nil { + panic(err) + } + +} diff --git a/pkg/utils/fileSystem.go b/pkg/utils/fileSystem.go index 516ea0f..32285b2 100644 --- a/pkg/utils/fileSystem.go +++ b/pkg/utils/fileSystem.go @@ -6,55 +6,57 @@ package utils import ( - "errors" - "fmt" - "io" - "io/fs" - "os" - pathUtils "path" - "path/filepath" - "runtime" + "errors" + "fmt" + "io" + "io/fs" + "os" + pathUtils "path" + "path/filepath" + "runtime" ) // FileSystem is a thin interface layer above the os package which can be mocked out type FileSystem interface { - // MkdirAll creates all folders in the file system if they don't already exist. - MkdirAll(targetFolderPath string) error - ReadTextFile(filePath string) (string, error) - WriteTextFile(targetFilePath string, desiredContents string) error - WriteBinaryFile(targetFilePath string, desiredContents []byte) error - Exists(path string) (bool, error) - DirExists(path string) (bool, error) - GetUserHomeDir() (string, error) - OutputWarningMessage(string) error - MkTempDir() (string, error) - DeleteDir(path string) - - ReadDir(path string) ([]os.DirEntry, error) - Open(fileName string) (io.ReadCloser, error) - WalkDir(root string, walkDirFunc fs.WalkDirFunc) error - - // Returns the normal extension used for executable files. - // ie: The .exe suffix in windows, or "" in unix-like systems. - GetExecutableExtension() string - - // GetPathSeparator returns the file path separator specific - // to this operating system. - GetFilePathSeparator() string + // MkdirAll creates all folders in the file system if they don't already exist. + MkdirAll(targetFolderPath string) error + ReadTextFile(filePath string) (string, error) + WriteTextFile(targetFilePath string, desiredContents string) error + WriteBinaryFile(targetFilePath string, desiredContents []byte) error + Exists(path string) (bool, error) + DirExists(path string) (bool, error) + GetUserHomeDir() (string, error) + OutputWarningMessage(string) error + MkTempDir() (string, error) + DeleteDir(path string) + + ReadDir(path string) ([]os.DirEntry, error) + Open(fileName string) (io.ReadCloser, error) + WalkDir(root string, walkDirFunc fs.WalkDirFunc) error + + // Returns the normal extension used for executable files. + // ie: The .exe suffix in windows, or "" in unix-like systems. + GetExecutableExtension() string + + // GetPathSeparator returns the file path separator specific + // to this operating system. + GetFilePathSeparator() string + + GetAllFilePaths(rootPath string) ([]string, error) } // TildaExpansion If a file starts with a tilda '~' character, expand it // to the home folder of the user on this file system. func TildaExpansion(fileSystem FileSystem, path string) (string, error) { - var err error = nil - if path != "" { - if path[0] == '~' { - var userHome string - userHome, err = fileSystem.GetUserHomeDir() - path = pathUtils.Join(userHome, path[1:]) - } - } - return path, err + var err error = nil + if path != "" { + if path[0] == '~' { + var userHome string + userHome, err = fileSystem.GetUserHomeDir() + path = pathUtils.Join(userHome, path[1:]) + } + } + return path, err } //------------------------------------------------------------------------------------ @@ -67,7 +69,7 @@ type OSFileSystem struct { // NewOSFileSystem creates an implementation of the thin file system layer which delegates // to the real os package calls. func NewOSFileSystem() FileSystem { - return new(OSFileSystem) + return new(OSFileSystem) } // ------------------------------------------------------------------------------------ @@ -75,109 +77,126 @@ func NewOSFileSystem() FileSystem { // ------------------------------------------------------------------------------------ func (osFS *OSFileSystem) GetFilePathSeparator() string { - return string(os.PathSeparator) + return string(os.PathSeparator) } func (osFS *OSFileSystem) GetExecutableExtension() string { - var extension string = "" - if runtime.GOOS == "windows" { - extension = ".exe" - } - return extension + var extension string = "" + if runtime.GOOS == "windows" { + extension = ".exe" + } + return extension } func (osFS *OSFileSystem) MkTempDir() (string, error) { - const DEFAULT_TEMP_FOLDER_PATH_FOR_THIS_OS = "" - tempFolderPath, err := os.MkdirTemp(DEFAULT_TEMP_FOLDER_PATH_FOR_THIS_OS, "galasa-*") - return tempFolderPath, err + const DEFAULT_TEMP_FOLDER_PATH_FOR_THIS_OS = "" + tempFolderPath, err := os.MkdirTemp(DEFAULT_TEMP_FOLDER_PATH_FOR_THIS_OS, "galasa-*") + return tempFolderPath, err } func (osFS *OSFileSystem) DeleteDir(path string) { - os.RemoveAll(path) + os.RemoveAll(path) } func (osFS *OSFileSystem) MkdirAll(targetFolderPath string) error { - err := os.MkdirAll(targetFolderPath, 0755) - if err != nil { - err = fmt.Errorf("failed to create folders at %s - %s", targetFolderPath, err.Error()) - } - return err + err := os.MkdirAll(targetFolderPath, 0755) + if err != nil { + err = fmt.Errorf("failed to create folders at %s - %s", targetFolderPath, err.Error()) + } + return err } func (osFS *OSFileSystem) WriteBinaryFile(targetFilePath string, desiredContents []byte) error { - err := os.WriteFile(targetFilePath, desiredContents, 0644) - if err != nil { - err = fmt.Errorf("failed to write file %s - %s", targetFilePath, err.Error()) - } - return err + err := os.WriteFile(targetFilePath, desiredContents, 0644) + if err != nil { + err = fmt.Errorf("failed to write file %s - %s", targetFilePath, err.Error()) + } + return err } func (osFS *OSFileSystem) WriteTextFile(targetFilePath string, desiredContents string) error { - bytes := []byte(desiredContents) - err := osFS.WriteBinaryFile(targetFilePath, bytes) - return err + bytes := []byte(desiredContents) + err := osFS.WriteBinaryFile(targetFilePath, bytes) + return err } func (*OSFileSystem) ReadTextFile(filePath string) (string, error) { - text := "" - bytes, err := os.ReadFile(filePath) - if err != nil { - err = fmt.Errorf("failed to read file %s - %s", filePath, err.Error()) - } else { - text = string(bytes) - } - return text, err + text := "" + bytes, err := os.ReadFile(filePath) + if err != nil { + err = fmt.Errorf("failed to read file %s - %s", filePath, err.Error()) + } else { + text = string(bytes) + } + return text, err } func (*OSFileSystem) ReadDir(dirPath string) ([]os.DirEntry, error) { - return os.ReadDir(dirPath) + return os.ReadDir(dirPath) } func (*OSFileSystem) Open(fileName string) (io.ReadCloser, error) { - return os.Open(fileName) + return os.Open(fileName) } func (*OSFileSystem) WalkDir(root string, walkDirFunc fs.WalkDirFunc) error { - return filepath.WalkDir(root, walkDirFunc) + return filepath.WalkDir(root, walkDirFunc) } func (*OSFileSystem) Exists(path string) (bool, error) { - isExists := true - _, err := os.Stat(path) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - // path/to/whatever does not exist - isExists = false - err = nil - } - } - return isExists, err + isExists := true + _, err := os.Stat(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // path/to/whatever does not exist + isExists = false + err = nil + } + } + return isExists, err } func (*OSFileSystem) DirExists(path string) (bool, error) { - isDirExists := true - metadata, err := os.Stat(path) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - // path/to/whatever does not exist - isDirExists = false - err = nil - } - } else { - isDirExists = metadata.IsDir() - } - return isDirExists, err + isDirExists := true + metadata, err := os.Stat(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + // path/to/whatever does not exist + isDirExists = false + err = nil + } + } else { + isDirExists = metadata.IsDir() + } + return isDirExists, err } func (*OSFileSystem) GetUserHomeDir() (string, error) { - dirName, err := os.UserHomeDir() - if err != nil { - err = fmt.Errorf("failed to get user home directory - %s", err.Error()) - } - return dirName, err + dirName, err := os.UserHomeDir() + if err != nil { + err = fmt.Errorf("failed to get user home directory - %s", err.Error()) + } + return dirName, err } func (OSFileSystem) OutputWarningMessage(message string) error { - _, err := os.Stderr.WriteString(message) - return err + _, err := os.Stderr.WriteString(message) + return err +} + +func (osFS *OSFileSystem) GetAllFilePaths(rootPath string) ([]string, error) { + var collectedFilePaths []string + + err := filepath.Walk( + rootPath, + func(path string, info os.FileInfo, err error) error { + if err == nil { + if !info.IsDir() { + // It's not a folder. Only add file names. + collectedFilePaths = append(collectedFilePaths, path) + } + } + return err + }) + return collectedFilePaths, err } diff --git a/pkg/utils/fileSystemMock.go b/pkg/utils/fileSystemMock.go index 3a7f150..46db7dc 100644 --- a/pkg/utils/fileSystemMock.go +++ b/pkg/utils/fileSystemMock.go @@ -6,64 +6,64 @@ package utils import ( - "bytes" - "io" - "io/fs" - "log" - "math" - "math/rand" - "os" - "path/filepath" - "strconv" - "strings" + "bytes" + "io" + "io/fs" + "log" + "math" + "math/rand" + "os" + "path/filepath" + "strconv" + "strings" ) // ------------------------------------------------------------------------------------ // The implementation of the file system interface built on an in-memory map. // ------------------------------------------------------------------------------------ type Node struct { - content []byte - isDir bool + content []byte + isDir bool } type MockFileSystem struct { - // Where the in-memory data is kept. - data map[string]*Node + // Where the in-memory data is kept. + data map[string]*Node - // A source of random numbers. So things are reproduceable. - random *rand.Rand + // A source of random numbers. So things are reproduceable. + random *rand.Rand - // Collects warnings messages - warningMessageBuffer *bytes.Buffer + // Collects warnings messages + warningMessageBuffer *bytes.Buffer - executableExtension string + executableExtension string - filePathSeparator string + filePathSeparator string - fileReadCloser *MockFile + fileReadCloser *MockFile - // The mock struct contains methods which can be over-ridden on a per-test basis. - VirtualFunction_MkdirAll func(targetFolderPath string) error - VirtualFunction_WriteTextFile func(targetFilePath string, desiredContents string) error - VirtualFunction_ReadTextFile func(filePath string) (string, error) - VirtualFunction_Exists func(path string) (bool, error) - VirtualFunction_DirExists func(path string) (bool, error) - VirtualFunction_GetUserHomeDir func() (string, error) - VirtualFunction_WriteBinaryFile func(targetFilePath string, desiredContents []byte) error - VirtualFunction_OutputWarningMessage func(string) error - VirtualFunction_MkTempDir func() (string, error) - VirtualFunction_DeleteDir func(path string) - VirtualFunction_ReadDir func(path string) ([]os.DirEntry, error) - VirtualFunction_Open func(fileName string) (io.ReadCloser, error) - VirtualFunction_WalkDir func(root string, walkDirFunc fs.WalkDirFunc) error + // The mock struct contains methods which can be over-ridden on a per-test basis. + VirtualFunction_MkdirAll func(targetFolderPath string) error + VirtualFunction_WriteTextFile func(targetFilePath string, desiredContents string) error + VirtualFunction_ReadTextFile func(filePath string) (string, error) + VirtualFunction_Exists func(path string) (bool, error) + VirtualFunction_DirExists func(path string) (bool, error) + VirtualFunction_GetUserHomeDir func() (string, error) + VirtualFunction_WriteBinaryFile func(targetFilePath string, desiredContents []byte) error + VirtualFunction_OutputWarningMessage func(string) error + VirtualFunction_MkTempDir func() (string, error) + VirtualFunction_DeleteDir func(path string) + VirtualFunction_ReadDir func(path string) ([]os.DirEntry, error) + VirtualFunction_Open func(fileName string) (io.ReadCloser, error) + VirtualFunction_WalkDir func(root string, walkDirFunc fs.WalkDirFunc) error } // NewMockFileSystem creates an implementation of the thin file system layer which delegates // to a memory map. This uses the default behaviour for all the virtual functions in our // MockFileSystem func NewMockFileSystem() FileSystem { - mockFileSystem := NewOverridableMockFileSystem() - return mockFileSystem + mockFileSystem := NewOverridableMockFileSystem() + return mockFileSystem } // NewOverridableMockFileSystem creates an implementation of the thin file system layer which @@ -71,75 +71,75 @@ func NewMockFileSystem() FileSystem { // it means the caller can set up different virtual functions, to change the behaviour. func NewOverridableMockFileSystem() *MockFileSystem { - // Allocate the structure - mockFileSystem := MockFileSystem{ - data: make(map[string]*Node)} - - mockFileSystem.warningMessageBuffer = &bytes.Buffer{} - - mockFileSystem.executableExtension = "" - - mockFileSystem.filePathSeparator = "/" - - mockFileSystem.fileReadCloser = NewOverridableMockFile() - - // Set up functions inside the structure to call the basic/default mock versions... - // These can later be over-ridden on a test-by-test basis. - mockFileSystem.VirtualFunction_MkdirAll = func(targetFolderPath string) error { - return mockFSMkdirAll(mockFileSystem, targetFolderPath) - } - mockFileSystem.VirtualFunction_WriteTextFile = func(targetFilePath string, desiredContents string) error { - return mockFSWriteTextFile(mockFileSystem, targetFilePath, desiredContents) - } - mockFileSystem.VirtualFunction_ReadTextFile = func(filePath string) (string, error) { - return mockFSReadTextFile(mockFileSystem, filePath) - } - mockFileSystem.VirtualFunction_Exists = func(path string) (bool, error) { - return mockFSExists(mockFileSystem, path) - } - mockFileSystem.VirtualFunction_DirExists = func(path string) (bool, error) { - return mockFSDirExists(mockFileSystem, path) - } - mockFileSystem.VirtualFunction_GetUserHomeDir = func() (string, error) { - return mockFSGetUserHomeDir() - } - mockFileSystem.VirtualFunction_WriteBinaryFile = func(path string, content []byte) error { - return mockFSWriteBinaryFile(mockFileSystem, path, content) - } - mockFileSystem.VirtualFunction_OutputWarningMessage = func(message string) error { - return mockFSOutputWarningMessage(mockFileSystem, message) - } - - mockFileSystem.VirtualFunction_MkTempDir = func() (string, error) { - return mockFSMkTempDir(mockFileSystem) - } - - mockFileSystem.VirtualFunction_DeleteDir = func(pathToDelete string) { - mockFSDeleteDir(mockFileSystem, pathToDelete) - } - - mockFileSystem.VirtualFunction_ReadDir = func(path string) ([]os.DirEntry, error) { - return mockFSReadDir(mockFileSystem, path) - } - mockFileSystem.VirtualFunction_Open = func(path string) (io.ReadCloser, error) { - return mockFSOpenFile(mockFileSystem, path) - } - mockFileSystem.VirtualFunction_WalkDir = func(root string, walkDirFunc fs.WalkDirFunc) error { - return mockFSWalkDir(mockFileSystem, root, walkDirFunc) - } - - randomSource := rand.NewSource(13) - mockFileSystem.random = rand.New(randomSource) - - return &mockFileSystem + // Allocate the structure + mockFileSystem := MockFileSystem{ + data: make(map[string]*Node)} + + mockFileSystem.warningMessageBuffer = &bytes.Buffer{} + + mockFileSystem.executableExtension = "" + + mockFileSystem.filePathSeparator = "/" + + mockFileSystem.fileReadCloser = NewOverridableMockFile() + + // Set up functions inside the structure to call the basic/default mock versions... + // These can later be over-ridden on a test-by-test basis. + mockFileSystem.VirtualFunction_MkdirAll = func(targetFolderPath string) error { + return mockFSMkdirAll(mockFileSystem, targetFolderPath) + } + mockFileSystem.VirtualFunction_WriteTextFile = func(targetFilePath string, desiredContents string) error { + return mockFSWriteTextFile(mockFileSystem, targetFilePath, desiredContents) + } + mockFileSystem.VirtualFunction_ReadTextFile = func(filePath string) (string, error) { + return mockFSReadTextFile(mockFileSystem, filePath) + } + mockFileSystem.VirtualFunction_Exists = func(path string) (bool, error) { + return mockFSExists(mockFileSystem, path) + } + mockFileSystem.VirtualFunction_DirExists = func(path string) (bool, error) { + return mockFSDirExists(mockFileSystem, path) + } + mockFileSystem.VirtualFunction_GetUserHomeDir = func() (string, error) { + return mockFSGetUserHomeDir() + } + mockFileSystem.VirtualFunction_WriteBinaryFile = func(path string, content []byte) error { + return mockFSWriteBinaryFile(mockFileSystem, path, content) + } + mockFileSystem.VirtualFunction_OutputWarningMessage = func(message string) error { + return mockFSOutputWarningMessage(mockFileSystem, message) + } + + mockFileSystem.VirtualFunction_MkTempDir = func() (string, error) { + return mockFSMkTempDir(mockFileSystem) + } + + mockFileSystem.VirtualFunction_DeleteDir = func(pathToDelete string) { + mockFSDeleteDir(mockFileSystem, pathToDelete) + } + + mockFileSystem.VirtualFunction_ReadDir = func(path string) ([]os.DirEntry, error) { + return mockFSReadDir(mockFileSystem, path) + } + mockFileSystem.VirtualFunction_Open = func(path string) (io.ReadCloser, error) { + return mockFSOpenFile(mockFileSystem, path) + } + mockFileSystem.VirtualFunction_WalkDir = func(root string, walkDirFunc fs.WalkDirFunc) error { + return mockFSWalkDir(mockFileSystem, root, walkDirFunc) + } + + randomSource := rand.NewSource(13) + mockFileSystem.random = rand.New(randomSource) + + return &mockFileSystem } func (fs *MockFileSystem) SetFilePathSeparator(newSeparator string) { - fs.filePathSeparator = newSeparator + fs.filePathSeparator = newSeparator } func (fs *MockFileSystem) SetExecutableExtension(newExtension string) { - fs.executableExtension = newExtension + fs.executableExtension = newExtension } //------------------------------------------------------------------------------------ @@ -147,71 +147,71 @@ func (fs *MockFileSystem) SetExecutableExtension(newExtension string) { //------------------------------------------------------------------------------------ func (fs *MockFileSystem) GetFilePathSeparator() string { - return fs.filePathSeparator + return fs.filePathSeparator } func (fs *MockFileSystem) GetExecutableExtension() string { - return fs.executableExtension + return fs.executableExtension } func (fs *MockFileSystem) DeleteDir(pathToDelete string) { - // Call the virtual function. - fs.VirtualFunction_DeleteDir(pathToDelete) + // Call the virtual function. + fs.VirtualFunction_DeleteDir(pathToDelete) } func (fs *MockFileSystem) MkTempDir() (string, error) { - // Call the virtual function. - return fs.VirtualFunction_MkTempDir() + // Call the virtual function. + return fs.VirtualFunction_MkTempDir() } func (fs *MockFileSystem) MkdirAll(targetFolderPath string) error { - // Call the virtual function. - return fs.VirtualFunction_MkdirAll(targetFolderPath) + // Call the virtual function. + return fs.VirtualFunction_MkdirAll(targetFolderPath) } func (fs *MockFileSystem) WriteBinaryFile(targetFilePath string, desiredContents []byte) error { - return fs.VirtualFunction_WriteBinaryFile(targetFilePath, desiredContents) + return fs.VirtualFunction_WriteBinaryFile(targetFilePath, desiredContents) } // WriteTextFile writes a string to a text file func (fs *MockFileSystem) WriteTextFile(targetFilePath string, desiredContents string) error { - // Call the virtual function. - return fs.VirtualFunction_WriteTextFile(targetFilePath, desiredContents) + // Call the virtual function. + return fs.VirtualFunction_WriteTextFile(targetFilePath, desiredContents) } func (fs *MockFileSystem) ReadTextFile(filePath string) (string, error) { - // Call the virtual function. - return fs.VirtualFunction_ReadTextFile(filePath) + // Call the virtual function. + return fs.VirtualFunction_ReadTextFile(filePath) } func (fs *MockFileSystem) Exists(path string) (bool, error) { - // Call the virtual function. - return fs.VirtualFunction_Exists(path) + // Call the virtual function. + return fs.VirtualFunction_Exists(path) } func (fs *MockFileSystem) DirExists(path string) (bool, error) { - // Call the virtual function. - return fs.VirtualFunction_DirExists(path) + // Call the virtual function. + return fs.VirtualFunction_DirExists(path) } func (fs *MockFileSystem) GetUserHomeDir() (string, error) { - return fs.VirtualFunction_GetUserHomeDir() + return fs.VirtualFunction_GetUserHomeDir() } func (fs MockFileSystem) OutputWarningMessage(message string) error { - return fs.VirtualFunction_OutputWarningMessage(message) + return fs.VirtualFunction_OutputWarningMessage(message) } func (fs *MockFileSystem) ReadDir(dirPath string) ([]os.DirEntry, error) { - return fs.VirtualFunction_ReadDir(dirPath) + return fs.VirtualFunction_ReadDir(dirPath) } func (fs *MockFileSystem) Open(fileName string) (io.ReadCloser, error) { - return fs.VirtualFunction_Open(fileName) + return fs.VirtualFunction_Open(fileName) } func (fs *MockFileSystem) WalkDir(root string, walkDirFunc fs.WalkDirFunc) error { - return fs.VirtualFunction_WalkDir(root, walkDirFunc) + return fs.VirtualFunction_WalkDir(root, walkDirFunc) } // ------------------------------------------------------------------------------------ @@ -219,117 +219,131 @@ func (fs *MockFileSystem) WalkDir(root string, walkDirFunc fs.WalkDirFunc) error // ------------------------------------------------------------------------------------ func mockFSDeleteDir(fs MockFileSystem, pathToDelete string) { - // Figure out which entries we are going to delete. - var keysToRemove []string = make([]string, 0) - for key := range fs.data { - if strings.HasPrefix(key, pathToDelete) { - keysToRemove = append(keysToRemove, key) - } - } + // Figure out which entries we are going to delete. + var keysToRemove []string = make([]string, 0) + for key := range fs.data { + if strings.HasPrefix(key, pathToDelete) { + keysToRemove = append(keysToRemove, key) + } + } - // Delete the entries we want to - for _, keyToRemove := range keysToRemove { - delete(fs.data, keyToRemove) - } + // Delete the entries we want to + for _, keyToRemove := range keysToRemove { + delete(fs.data, keyToRemove) + } } func mockFSMkTempDir(fs MockFileSystem) (string, error) { - tempFolderPath := "/tmp" + strconv.Itoa(fs.random.Intn(math.MaxInt)) - err := fs.MkdirAll(tempFolderPath) - return tempFolderPath, err + tempFolderPath := "/tmp" + strconv.Itoa(fs.random.Intn(math.MaxInt)) + err := fs.MkdirAll(tempFolderPath) + return tempFolderPath, err } func mockFSMkdirAll(fs MockFileSystem, targetFolderPath string) error { - nodeToAdd := Node{content: []byte(""), isDir: true} - fs.data[targetFolderPath] = &nodeToAdd - return nil + + nodeToAdd := Node{content: []byte(""), isDir: true} + + for { + if targetFolderPath == "" { + break + } + fs.data[targetFolderPath] = &nodeToAdd + index := strings.LastIndex(targetFolderPath, "/") + if index != -1 { + targetFolderPath = targetFolderPath[:index] + } else { + break + } + } + + return nil } func mockFSWriteBinaryFile(fs MockFileSystem, targetFilePath string, desiredContents []byte) error { - nodeToAdd := Node{content: desiredContents, isDir: false} - fs.data[targetFilePath] = &nodeToAdd - return nil + nodeToAdd := Node{content: desiredContents, isDir: false} + fs.data[targetFilePath] = &nodeToAdd + return nil } func mockFSWriteTextFile(fs MockFileSystem, targetFilePath string, desiredContents string) error { - nodeToAdd := Node{content: []byte(desiredContents), isDir: false} - fs.data[targetFilePath] = &nodeToAdd - return nil + nodeToAdd := Node{content: []byte(desiredContents), isDir: false} + fs.data[targetFilePath] = &nodeToAdd + return nil } func mockFSReadTextFile(fs MockFileSystem, filePath string) (string, error) { - text := "" - var err error = nil - node := fs.data[filePath] - if node == nil { - err = os.ErrNotExist - } else { - text = string(node.content) - } - return text, err + text := "" + var err error = nil + node := fs.data[filePath] + if node == nil { + err = os.ErrNotExist + } else { + text = string(node.content) + } + return text, err } func mockFSExists(fs MockFileSystem, path string) (bool, error) { - isExists := true - var err error = nil - node := fs.data[path] - if node == nil { - isExists = false - } - return isExists, err + isExists := true + var err error = nil + node := fs.data[path] + if node == nil { + isExists = false + } + return isExists, err } func mockFSDirExists(fs MockFileSystem, path string) (bool, error) { - isDirExists := true - var err error = nil - node := fs.data[path] - if node == nil { - isDirExists = false - } else { - isDirExists = node.isDir - } - return isDirExists, err + isDirExists := true + var err error = nil + node := fs.data[path] + if node == nil { + isDirExists = false + } else { + isDirExists = node.isDir + } + return isDirExists, err } func mockFSGetUserHomeDir() (string, error) { - return "/User/Home/testuser", nil + return "/User/Home/testuser", nil } func mockFSOutputWarningMessage(fs MockFileSystem, message string) error { - log.Printf("Mock warning message collected: %s", message) - fs.warningMessageBuffer.WriteString(message) - return nil + log.Printf("Mock warning message collected: %s", message) + fs.warningMessageBuffer.WriteString(message) + return nil } func mockFSReadDir(fs MockFileSystem, dirPath string) ([]os.DirEntry, error) { - var dirEntries []MockDirEntry - for key := range fs.data { - if strings.HasPrefix(key, dirPath) && key != dirPath { - dirEntries = append(dirEntries, MockDirEntry{DirName: filepath.Base(key)}) - } - } + var dirEntries []MockDirEntry + for key := range fs.data { + if strings.HasPrefix(key, dirPath) && key != dirPath { + dirEntries = append(dirEntries, MockDirEntry{DirName: filepath.Base(key)}) + } + } - entriesToReturn := make([]os.DirEntry, len(dirEntries)) - for index, value := range dirEntries { - entriesToReturn[index] = value - } - return entriesToReturn, nil + entriesToReturn := make([]os.DirEntry, len(dirEntries)) + for index, value := range dirEntries { + entriesToReturn[index] = value + } + return entriesToReturn, nil } func mockFSOpenFile(fs MockFileSystem, filePath string) (io.ReadCloser, error) { - fs.fileReadCloser.data = []byte("dummy data") - return fs.fileReadCloser, nil + fs.fileReadCloser.data = []byte("dummy data") + return fs.fileReadCloser, nil } func mockFSWalkDir(fs MockFileSystem, dirPath string, walkDirFunc fs.WalkDirFunc) error { - var err error = nil - for path := range fs.data { - if strings.HasPrefix(path, dirPath) { - dirEntry := MockDirEntry{DirName: filepath.Base(path)} - err = walkDirFunc(path, dirEntry, nil) - } - } - return err + var err error = nil + for path := range fs.data { + if strings.HasPrefix(path, dirPath) { + dirEntry := MockDirEntry{DirName: filepath.Base(path)} + err = walkDirFunc(path, dirEntry, nil) + } + } + return err } //------------------------------------------------------------------------------------ @@ -337,7 +351,23 @@ func mockFSWalkDir(fs MockFileSystem, dirPath string, walkDirFunc fs.WalkDirFunc //------------------------------------------------------------------------------------ func (fs MockFileSystem) GetAllWarningMessages() string { - messages := fs.warningMessageBuffer.String() - log.Printf("Mock reading back previously collected warnings messages: %s", messages) - return messages + messages := fs.warningMessageBuffer.String() + log.Printf("Mock reading back previously collected warnings messages: %s", messages) + return messages +} + +func (fs *MockFileSystem) GetAllFilePaths(rootPath string) ([]string, error) { + var collectedFilePaths []string + var err error + + for path, node := range fs.data { + if strings.HasPrefix(path, rootPath) { + if node.isDir == false { + // It's a file. Save it's path to return. + collectedFilePaths = append(collectedFilePaths, path) + } + } + } + + return collectedFilePaths, err } diff --git a/pkg/versioning/list.go b/pkg/versioning/list.go new file mode 100644 index 0000000..3b0b9ab --- /dev/null +++ b/pkg/versioning/list.go @@ -0,0 +1,208 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package versioning + +import ( + "errors" + "fmt" + "log" + "os" + "path" + "regexp" + "sort" + "strings" + + "galasa.dev/buildUtilities/pkg/utils" +) + +// To match the `version = "x.y.z"' pattern +// Note: (?m) enables multi-line matching. +var versionLineRegex = regexp.MustCompile(`(?m)^[ \t]*version[ \t]*[=]?[\t ]*["'](.*)['"].*$`) +var projectNameRegex = regexp.MustCompile(`(?m)^rootProject.name[\t ]*=[\t ]*["'](.*)["'].*$`) + +func ListExecute(fs utils.FileSystem, sourceCodeFolderPath string) error { + + modules, err := getModules(fs, sourceCodeFolderPath) + + if err == nil { + err = printModuleListing(modules) + } + + return err +} + +func getModules(fs utils.FileSystem, sourceCodeFolderPath string) ([]Module, error) { + var modules []Module + var err error + + err = checkFolderExists(fs, sourceCodeFolderPath) + + if err == nil { + // Get all the folders under the source code folder recursively which have a build.gradle file inside. + var buildGradleFolderPaths []string + buildGradleFolderPaths, err = gatherEligibleBuildGradleFolders(fs, sourceCodeFolderPath) + + if err == nil { + modules, err = extractModulesFromBuildGradleFolders(fs, buildGradleFolderPaths) + } + } + + return modules, err +} + +func checkFolderExists(fs utils.FileSystem, sourceCodeFolderPath string) error { + // Get the metadata about the folder path we are being pointed at. + isExisting, err := fs.DirExists(sourceCodeFolderPath) + if err == nil { + if !isExisting { + err = errors.New("sourceCodeFolderPath is not a folder which exists!") + } + } + return err +} + +func printModuleListing(modules []Module) error { + for _, module := range modules { + fmt.Fprintf(os.Stdout, "%s %s\n", module.GetProjectName(), module.GetVersion()) + } + return nil +} + +func extractModulesFromBuildGradleFolders(fs utils.FileSystem, buildGradleFolderPaths []string) ([]Module, error) { + var err error + var modules []Module = make([]Module, 0) + + for _, buildGradleFolderPath := range buildGradleFolderPaths { + var module Module + + module, err = extractModuleFromBuildGradleFolder(fs, buildGradleFolderPath) + if err != nil { + log.Printf("Error extracting the module from build gradle folder. %v", err) + break + } + + if module != nil { + modules = append(modules, module) + } + } + + // Sort the results by project name. + sort.Slice(modules, func(i, j int) bool { + return modules[i].GetProjectName() < modules[j].GetProjectName() + }) + + return modules, err +} + +func extractModuleFromBuildGradleFolder(fs utils.FileSystem, buildGradleFolderPath string) (Module, error) { + var module Module + + buildGradleFilePath := path.Join(buildGradleFolderPath, "build.gradle") + + contentsString, err := fs.ReadTextFile(buildGradleFilePath) + if err == nil { + matches := versionLineRegex.FindStringSubmatch(contentsString) + + if matches == nil { + // There was no version in this build.gradle file. Warning ? + log.Printf("Warning: build.gradle file has no version line so folder %s does not contain a module.\n", buildGradleFolderPath) + } else { + // There is a match. + version := matches[1] + + var projectName string + projectName, err = extractProjectNameFromGradleSettings(fs, buildGradleFolderPath) + + if err == nil { + + if projectName != "" { + module = NewModule(projectName, buildGradleFolderPath, version) + } + } + } + } + + return module, err +} + +func extractProjectNameFromGradleSettings(fs utils.FileSystem, folderPath string) (string, error) { + + var projectName string = "" + + gradlePropsFilePath := path.Join(folderPath, "settings.gradle") + + contentsString, err := fs.ReadTextFile(gradlePropsFilePath) + if err != nil { + if strings.Contains(err.Error(), "no such file") { + log.Printf("Warning: settings.gradle file is not present, so folder %v does not contain a module.\n", folderPath) + err = nil + } + } else { + matches := projectNameRegex.FindStringSubmatch(contentsString) + if matches == nil { + // There was no version in this settings.gradle file. Warning ? + log.Printf("Warning: settings.gradle file has no project name, so folder %v does not contain a module.\n", folderPath) + } else { + // There is a match. We know the project name now. + projectName = matches[1] + } + } + + return projectName, err + +} + +type ModuleImpl struct { + projectName string + path string + version string +} + +type Module interface { + GetProjectName() string + GetPath() string + GetVersion() string +} + +func NewModule(projectName string, path string, version string) Module { + module := new(ModuleImpl) + module.projectName = projectName + module.path = path + module.version = version + return module +} + +func (module *ModuleImpl) GetProjectName() string { + return module.projectName +} +func (module *ModuleImpl) GetPath() string { + return module.path +} +func (module *ModuleImpl) GetVersion() string { + return module.version +} + +func gatherEligibleBuildGradleFolders(fs utils.FileSystem, sourceCodeFolderPath string) ([]string, error) { + var buildFolders []string = make([]string, 0) + + filePaths, err := fs.GetAllFilePaths(sourceCodeFolderPath) + for _, filePath := range filePaths { + dirPart, filePart := path.Split(filePath) + if "build.gradle" == filePart { + + buildFolders = append(buildFolders, dirPart) + } + } + + if err != nil { + log.Printf("impossible to walk directories: %s", err) + } else { + sort.Strings(filePaths) + } + + return buildFolders, err +} diff --git a/pkg/versioning/list_test.go b/pkg/versioning/list_test.go new file mode 100644 index 0000000..b5d6384 --- /dev/null +++ b/pkg/versioning/list_test.go @@ -0,0 +1,188 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package versioning + +import ( + "testing" + + "galasa.dev/buildUtilities/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func createSingleModuleFs() *utils.MockFileSystem { + fs := utils.NewOverridableMockFileSystem() + + fs.MkdirAll("/my/second/random/folder/module2") + fs.MkdirAll("/my/random/folder/module1") + fs.WriteTextFile("/my/random/folder/module1/build.gradle", + `# Not a version yet + version = "0.36.0-SNAPSHOT" # Some comment. + `) + fs.WriteTextFile("/my/random/folder/module1/settings.gradle", + `rootProject.name = 'my.random.folder.module1'`) + return fs +} + +func createTwoModuleFs() *utils.MockFileSystem { + fs := utils.NewOverridableMockFileSystem() + + // Folders which are not a module. + fs.MkdirAll("/my/second/random/folder/module2") + + // Folders which hold a real module. + fs.MkdirAll("/my/random/folder/module1") + fs.WriteTextFile("/my/random/folder/module1/build.gradle", + `# Not a version yet + version = "0.36.0-SNAPSHOT" # Some comment. + `) + fs.WriteTextFile("/my/random/folder/module1/settings.gradle", + `rootProject.name = 'my.random.folder.module1'`) + + // Folders which hold a second module + fs.MkdirAll("/my/random/folder/module3") + fs.WriteTextFile("/my/random/folder/module3/build.gradle", + `# Not a version yet + version = "0.36.0-dev" # Some comment. + `) + fs.WriteTextFile("/my/random/folder/module3/settings.gradle", + `rootProject.name = 'my.random.folder.module3'`) + return fs +} + +func TestCanFindGradleFolder(t *testing.T) { + fs := createSingleModuleFs() + + buildGradleFolderPaths, err := gatherEligibleBuildGradleFolders(fs, "/my") + + assert.Nil(t, err) + assert.Len(t, buildGradleFolderPaths, 1) + assert.Contains(t, buildGradleFolderPaths, "/my/random/folder/module1/") +} + +func TestCanFindAModule(t *testing.T) { + fs := createSingleModuleFs() + + buildGradleFolderPaths, _ := gatherEligibleBuildGradleFolders(fs, "/my") + modules, err := extractModulesFromBuildGradleFolders(fs, buildGradleFolderPaths) + assert.Nil(t, err) + assert.Len(t, modules, 1) + assert.Equal(t, modules[0].GetProjectName(), "my.random.folder.module1") + assert.Equal(t, modules[0].GetPath(), "/my/random/folder/module1/") + assert.Equal(t, modules[0].GetVersion(), "0.36.0-SNAPSHOT") +} + +func TestCanFindTwoModules(t *testing.T) { + fs := createTwoModuleFs() + + buildGradleFolderPaths, _ := gatherEligibleBuildGradleFolders(fs, "/my") + modules, err := extractModulesFromBuildGradleFolders(fs, buildGradleFolderPaths) + assert.Nil(t, err) + assert.Len(t, modules, 2) + + assert.Equal(t, modules[0].GetProjectName(), "my.random.folder.module1") + assert.Equal(t, modules[0].GetPath(), "/my/random/folder/module1/") + assert.Equal(t, modules[0].GetVersion(), "0.36.0-SNAPSHOT") + + assert.Equal(t, modules[1].GetProjectName(), "my.random.folder.module3") + assert.Equal(t, modules[1].GetPath(), "/my/random/folder/module3/") + assert.Equal(t, modules[1].GetVersion(), "0.36.0-dev") +} + +func TestVersionRegexMatchesExampleDoubleQuotes(t *testing.T) { + matches := versionLineRegex.FindStringSubmatch(` version= "12.13.24-dev"`) + assert.NotNil(t, matches) + assert.Len(t, matches, 2) + assert.Equal(t, matches[1], "12.13.24-dev") +} + +func TestVersionRegexMatchesExampleSingleQuotes(t *testing.T) { + matches := versionLineRegex.FindStringSubmatch(` version= '12.13.24-dev' `) + assert.NotNil(t, matches) + assert.Len(t, matches, 2) + assert.Equal(t, matches[1], "12.13.24-dev") +} + +func TestVersionRegexMatchesRealExample(t *testing.T) { + contents := `plugins { + id 'galasa.manager' +} + +description = 'Galasa zOS File Manager - zOS/MF Implementation' + +version = '0.21.0' # hello + +dependencies { + implementation project (':galasa-managers-zos-parent:dev.galasa.zos.manager') +}` + matches := versionLineRegex.FindStringSubmatch(contents) + assert.NotNil(t, matches) + assert.Len(t, matches, 2) + assert.Equal(t, matches[1], "0.21.0") +} + +func createSingleModuleWithNoVersionInBuildGradleFs() *utils.MockFileSystem { + fs := utils.NewOverridableMockFileSystem() + + fs.MkdirAll("/my/second/random/folder/module2") + fs.MkdirAll("/my/random/folder/module1") + fs.WriteTextFile("/my/random/folder/module1/build.gradle", + `# Not a version yet + version = # There isn't a version here. Not a valid module. + `) + fs.WriteTextFile("/my/random/folder/module1/settings.gradle", + `rootProject.name = 'my.random.folder.module1'`) + return fs +} + +func TestIgnoresModuleWithNoVersionInBuildGradle(t *testing.T) { + fs := createSingleModuleWithNoVersionInBuildGradleFs() + + buildGradleFolderPaths, _ := gatherEligibleBuildGradleFolders(fs, "/my") + modules, err := extractModulesFromBuildGradleFolders(fs, buildGradleFolderPaths) + assert.Nil(t, err) + assert.Len(t, modules, 0) +} + +func TestVersionRegexMatchesVersionNMissingEquals(t *testing.T) { + contents := `plugins { + id 'galasa.manager' +} + +description = 'Galasa zOS File Manager - zOS/MF Implementation' + +version '0.34.0' + +dependencies { + implementation project (':galasa-managers-zos-parent:dev.galasa.zos.manager') +}` + matches := versionLineRegex.FindStringSubmatch(contents) + assert.NotNil(t, matches) + assert.Len(t, matches, 2) + assert.Equal(t, matches[1], "0.34.0") +} + +func TestVersionRegexMatchesVersionnMissingFullExample(t *testing.T) { + contents := `]]plugins { + id 'biz.aQute.bnd.builder' + id 'org.openapi.generator' version "5.0.1" + id 'galasa.api.server' + + // testCompile requires Java plugin. + id 'java' +} + +description 'Galasa API - RAS' + +version '0.34.0' + +dependencies { +` + matches := versionLineRegex.FindStringSubmatch(contents) + assert.NotNil(t, matches) + assert.Len(t, matches, 2) + assert.Equal(t, matches[1], "0.34.0") +} diff --git a/pkg/versioning/suffixRemove.go b/pkg/versioning/suffixRemove.go new file mode 100644 index 0000000..b7a82d4 --- /dev/null +++ b/pkg/versioning/suffixRemove.go @@ -0,0 +1,23 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package versioning + +import ( + "galasa.dev/buildUtilities/pkg/utils" +) + +func SuffixRemoveExecute(fs utils.FileSystem, sourceCodeFolderPath string) error { + var err error + + var modules []Module + modules, err = getModules(fs, sourceCodeFolderPath) + if err == nil { + err = setSuffixOnAllModules(fs, modules, "") + } + + return err +} diff --git a/pkg/versioning/suffixRemove_test.go b/pkg/versioning/suffixRemove_test.go new file mode 100644 index 0000000..6a9a64e --- /dev/null +++ b/pkg/versioning/suffixRemove_test.go @@ -0,0 +1,34 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package versioning + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCanSubstituteVersionsForBlankSuffix(t *testing.T) { + mockFs := createTwoModuleFs() + err := SuffixRemoveExecute(mockFs, "/my") + assert.Nil(t, err) + + // Now get the versions out again. + var modules []Module + modules, err = getModules(mockFs, "/my") + + assert.Nil(t, err) + assert.Len(t, modules, 2) + + assert.Equal(t, modules[0].GetProjectName(), "my.random.folder.module1") + assert.Equal(t, modules[0].GetPath(), "/my/random/folder/module1/") + assert.Equal(t, modules[0].GetVersion(), "0.36.0") + + assert.Equal(t, modules[1].GetProjectName(), "my.random.folder.module3") + assert.Equal(t, modules[1].GetPath(), "/my/random/folder/module3/") + assert.Equal(t, modules[1].GetVersion(), "0.36.0") +} diff --git a/pkg/versioning/suffixSet.go b/pkg/versioning/suffixSet.go new file mode 100644 index 0000000..527a0ba --- /dev/null +++ b/pkg/versioning/suffixSet.go @@ -0,0 +1,107 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package versioning + +import ( + "errors" + "path" + "strings" + + "galasa.dev/buildUtilities/pkg/utils" +) + +func SuffixSetExecute(fs utils.FileSystem, sourceCodeFolderPath string, desiredSuffix string) error { + var err error + + err = validateSuffix(desiredSuffix) + + if err == nil { + var modules []Module + modules, err = getModules(fs, sourceCodeFolderPath) + if err == nil { + err = setSuffixOnAllModules(fs, modules, desiredSuffix) + } + } + + return err +} + +func validateSuffix(suffixToValidate string) error { + var err error + + if strings.HasPrefix(suffixToValidate, "-") || strings.HasPrefix(suffixToValidate, "_") { + // It's valid. + } else { + err = errors.New("Invalid suffix. It must start with a '-' or '_' character, or be blank.") + } + + return err +} + +func setSuffixOnAllModules(fs utils.FileSystem, modules []Module, desiredSuffix string) error { + + var err error + for _, module := range modules { + currentVersion := module.GetVersion() + var desiredVersion string + desiredVersion = calculateDesiredVersion(currentVersion, desiredSuffix) + err = substitutedBuildGradleVersion(fs, module, desiredVersion) + } + return err +} + +func substitutedBuildGradleVersion(fs utils.FileSystem, module Module, desiredVersion string) error { + + buildGradleFilePath := path.Join(module.GetPath(), "build.gradle") + buildGradleFileContents, err := fs.ReadTextFile(buildGradleFilePath) + + if err == nil { + matches := versionLineRegex.FindStringSubmatchIndex(buildGradleFileContents) + + // matches[0] is the start of the whole string + // matches[1] is the end of the whole string + // matches[2] is the start of the versions part which needs replacing. + // matches[3] is the end of the versions part which needs replacing. + + startIndex := matches[2] + endIndex := matches[3] + + beforeMatch := buildGradleFileContents[:startIndex] + afterMatch := buildGradleFileContents[endIndex:] + + contentAfterSubstiitution := beforeMatch + desiredVersion + afterMatch + + err = fs.WriteTextFile(buildGradleFilePath, contentAfterSubstiitution) + } + + return err +} + +func calculateDesiredVersion(currentVersion string, desiredSuffix string) string { + var desiredVersion string + var nonSuffixedVersion string + + // Remove anything after one of the delimeters + nonSuffixedVersion = removeSuffix(currentVersion, "_") + nonSuffixedVersion = removeSuffix(nonSuffixedVersion, "-") + + // Now append the desired suffix. + desiredVersion = nonSuffixedVersion + desiredSuffix + + return desiredVersion +} + +func removeSuffix(stringToManipulate string, delimeter string) string { + var result string + index := strings.Index(stringToManipulate, delimeter) + if index != -1 { + result = stringToManipulate[:index] + } else { + result = stringToManipulate + } + return result +} diff --git a/pkg/versioning/suffixSet_test.go b/pkg/versioning/suffixSet_test.go new file mode 100644 index 0000000..b16582c --- /dev/null +++ b/pkg/versioning/suffixSet_test.go @@ -0,0 +1,59 @@ +/* + * Copyright contributors to the Galasa project + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package versioning + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCanCalculateVersionCanAddSuffixFromUnSuffixed(t *testing.T) { + newVersion := calculateDesiredVersion("0.0.1", "-SNAPSHOT") + assert.Equal(t, "0.0.1-SNAPSHOT", newVersion) +} + +func TestCanCalculateVersionCanReplaceSuffixWhichHasDashSeparator(t *testing.T) { + newVersion := calculateDesiredVersion("0.0.1-dev", "-SNAPSHOT") + assert.Equal(t, "0.0.1-SNAPSHOT", newVersion) +} + +func TestCanCalculateVersionCanReplaceSuffixWhichHasUnderscoreSeparator(t *testing.T) { + newVersion := calculateDesiredVersion("0.0.1_dev", "-SNAPSHOT") + assert.Equal(t, "0.0.1-SNAPSHOT", newVersion) +} + +func TestCanCalculateVersionCanReplaceSuffixWhichHasTwoSuffixesAlready(t *testing.T) { + newVersion := calculateDesiredVersion("0.0.1-dev-mine", "-SNAPSHOT") + assert.Equal(t, "0.0.1-SNAPSHOT", newVersion) +} + +func TestSetFailsIfSuffixIsInvalid(t *testing.T) { + err := SuffixSetExecute(nil, "", "notvalid") + assert.NotNil(t, err) +} + +func TestCanSubstituteVersions(t *testing.T) { + mockFs := createTwoModuleFs() + err := SuffixSetExecute(mockFs, "/my", "-alpha") + assert.Nil(t, err) + + // Now get the versions out again. + var modules []Module + modules, err = getModules(mockFs, "/my") + + assert.Nil(t, err) + assert.Len(t, modules, 2) + + assert.Equal(t, modules[0].GetProjectName(), "my.random.folder.module1") + assert.Equal(t, modules[0].GetPath(), "/my/random/folder/module1/") + assert.Equal(t, modules[0].GetVersion(), "0.36.0-alpha") + + assert.Equal(t, modules[1].GetProjectName(), "my.random.folder.module3") + assert.Equal(t, modules[1].GetPath(), "/my/random/folder/module3/") + assert.Equal(t, modules[1].GetVersion(), "0.36.0-alpha") +} diff --git a/test-locally.sh b/test-locally.sh new file mode 100755 index 0000000..da0aad0 --- /dev/null +++ b/test-locally.sh @@ -0,0 +1,167 @@ +#! /usr/bin/env bash + +# +# Copyright contributors to the Galasa project +# +# SPDX-License-Identifier: EPL-2.0 +# +#----------------------------------------------------------------------------------------- +# +# Objectives: Build this repository code locally. +# +#----------------------------------------------------------------------------------------- + +# Where is this script executing from ? +BASEDIR=$(dirname "$0");pushd $BASEDIR 2>&1 >> /dev/null ;BASEDIR=$(pwd);popd 2>&1 >> /dev/null +# echo "Running from directory ${BASEDIR}" +export ORIGINAL_DIR=$(pwd) +# cd "${BASEDIR}" + +cd "${BASEDIR}/.." +WORKSPACE_DIR=$(pwd) + + +#----------------------------------------------------------------------------------------- +# +# Set Colors +# +#----------------------------------------------------------------------------------------- +bold=$(tput bold) +underline=$(tput sgr 0 1) +reset=$(tput sgr0) +red=$(tput setaf 1) +green=$(tput setaf 76) +white=$(tput setaf 7) +tan=$(tput setaf 202) +blue=$(tput setaf 25) + +#----------------------------------------------------------------------------------------- +# +# Headers and Logging +# +#----------------------------------------------------------------------------------------- +underline() { printf "${underline}${bold}%s${reset}\n" "$@" ; } +h1() { printf "\n${underline}${bold}${blue}%s${reset}\n" "$@" ; } +h2() { printf "\n${underline}${bold}${white}%s${reset}\n" "$@" ; } +debug() { printf "${white}[.] %s${reset}\n" "$@" ; } +info() { printf "${white}[➜] %s${reset}\n" "$@" ; } +success() { printf "${white}[${green}✔${white}] ${green}%s${reset}\n" "$@" ; } +error() { printf "${white}[${red}✖${white}] ${red}%s${reset}\n" "$@" ; } +warn() { printf "${white}[${tan}➜${white}] ${tan}%s${reset}\n" "$@" ; } +bold() { printf "${bold}%s${reset}\n" "$@" ; } +note() { printf "\n${underline}${bold}${blue}Note:${reset} ${blue}%s${reset}\n" "$@" ; } + +#----------------------------------------------------------------------------------------- +# Functions +#----------------------------------------------------------------------------------------- +function clean_temp_folder() { + rm -fr $BASEDIR/temp + mkdir -p $BASEDIR/temp + LOGS_DIR=$BASEDIR/temp +} + +function setup_source_folder() { + rm -fr $BASEDIR/temp/src + mkdir -p $BASEDIR/temp/src + + mkdir -p $BASEDIR/temp/src/dev/galasa/examples/module1 + cat << EOF > $BASEDIR/temp/src/dev/galasa/examples/module1/build.gradle +# A test module mock-up. +version = "0.0.1-SNAPSHOT" // trailing comment +# trailing content +EOF + + cat << EOF > $BASEDIR/temp/src/dev/galasa/examples/module1/settings.gradle +# initial content +rootProject.name = "dev.galasa.examples/module1" +# trailing content +EOF + + mkdir -p $BASEDIR/temp/src/dev/galasa/examples/module2 + cat << EOF > $BASEDIR/temp/src/dev/galasa/examples/module2/build.gradle +# A test module mock-up. +version = "0.0.2-SNAPSHOT" // trailing comment +# trailing content +EOF + + cat << EOF > $BASEDIR/temp/src/dev/galasa/examples/module2/settings.gradle +# initial content +rootProject.name = "dev.galasa.examples/module2" +# trailing content +EOF + +} + +function check_versions_have_suffix() { + suffix=$1 + if [[ "$suffix" == "" ]]; then + info "Checking that the versions of the code are not using a suffix" + else + info "Checking that the versions of the code are using the $suffix suffix" + fi + + cat << EOF > $BASEDIR/temp/versions-list-expected.txt +[$GALASABLD versioning list --sourcefolderpath $BASEDIR/temp/src] +dev.galasa.examples/module1 0.0.1$suffix +dev.galasa.examples/module2 0.0.2$suffix +EOF + + diff $BASEDIR/temp/versions-list-expected.txt $BASEDIR/temp/versions-list-got.txt >> /dev/null + rc=$? ; if [[ "$rc" != "0" ]]; then error "Output from listing versions is not what we expected." ; exit 1 ; fi + success "The list of versions is what we expected." +} + +function clear_version_suffixes() { + info "Removing the suffixes" + cmd="$GALASABLD versioning suffix remove --sourcefolderpath $BASEDIR/temp/src " + + info "Command is $cmd" + $cmd > $BASEDIR/temp/versions-removed.txt + rc=$? ; if [[ "$rc" != "0" ]]; then error "Could not remove the version suffixes of the code. rc=$?" ; exit 1 ; fi + success "Version suffixes of modules removed OK." +} + +function gather_version_list() { + info "Listing the suffixes" + cmd="$GALASABLD versioning list --sourcefolderpath $BASEDIR/temp/src " + + info "Command is $cmd" + $cmd > $BASEDIR/temp/versions-list-got.txt + rc=$? ; if [[ "$rc" != "0" ]]; then error "Could not set the versions of the code. rc=$?" ; exit 1 ; fi + success "Versions of modules set OK." +} + +function set_version_suffixes() { + desired_suffix=$1 + info "Setting the suffixes prefixes to $desired_suffix" + cmd="$GALASABLD versioning suffix set --sourcefolderpath $BASEDIR/temp/src --suffix $desired_suffix" + + info "Command is $cmd" + $cmd > $BASEDIR/temp/versions-set.txt + rc=$? ; if [[ "$rc" != "0" ]]; then error "Could not set the version suffixes to $desired_suffix. rc=$?" ; exit 1 ; fi + success "Version suffixes of modules set to $desired_suffix OK." +} + +function test_versions_manipulation() { + h2 "Testing manipulations of versions" + + setup_source_folder + gather_version_list + check_versions_have_suffix "-SNAPSHOT" + + info "Removing the suffixes on versions" + clear_version_suffixes + gather_version_list + check_versions_have_suffix "" + + info "Setting the suffixes on versions" + set_version_suffixes "-alpha" + gather_version_list + check_versions_have_suffix "-alpha" + + success "Tested the galasabld versioning commands as best we can" +} + +export GALASABLD=${BASEDIR}/bin/galasabld-darwin-arm64 +clean_temp_folder +test_versions_manipulation \ No newline at end of file